Skip to content

ProtectAid

devlaam edited this page Jun 6, 2023 · 4 revisions

ProtectAid: Keep mailboxes of manageable size.

Text compliant with Leucine version 0.4.3

Introduction

In normal operation the mailbox will not quickly be full. In fact, there is no other limit than the total memory of the system or Int.MaxValue, which ever comes first for a mailbox in any actor. (Formally this depends on the setting in SystemParameters, which can be influenced by the user to be a lower value. The ActorContext.system works with default settings at which this holds true.) But, it should not come this far. If the system is designed well, mailboxes should be empty or contain very few messages most of the time. This is because handling the messages should be swift enough to make it so. If the system is not able to handle the load, it should be throttled at the start of the message pipeline. This could be, for example, the location where user requests come in, typically a web server or so. Or, if the system is crunching data from a database at reading that data.

In any case, there may be reasons why you want to limit the number of messages that are send to an actor. This limit can also be used to create back pressure and implement a reactive stream.

Protecting you actor.

In order to protect you actor use the mixin ProtectAid. This adds a field and a method that must be implemented.

/**
 * The number of letters in the mailbox that issues an alarm. This must be a constant, since
 * it is called in a synchronized environment, and is called on every letter posted. */
protected val protectLevel: Int

/**
 * Implement an event handler for the situation the mailbox exceeds the limit set in protectLevel,
 * (full = true) and for when mailbox, stash and event queues are all depleted (full = false).
 * Each call happens only once, and you always receive an call signalling empty queues after a
 * a call with full=true was made and before the next call with full=true is made. When stop(Finish)
 * is called the call sizeAlarm(false) will come after the last letter is processed and but before
 * stopped() is called. When stop(Direct) is called, the sizeAlarm(true/false) may not come at all.
 * The size parameter reflects the total number of size alarms during the lifetime of the actor
 * up to now, this one included. */
protected def protectAlarm(full: Boolean): Unit

With protectLevel you set a high level watermark that, if reached triggers the protectAlarm(true) callback. The message is not refused and still added to the mailbox. This also applies to each subsequent message, but the protectAlarm in not called again. The idea is that you implement a handler that prohibits the further sending of new messages to this actor, (where a few extra that cannot be stopped are not a problem) until protectAlarm(false) is called. The latter happens only when the actor has completely handled all messages left, including those that were posted after the alarm was raised.

The call to protectAlarm comes, like all other callbacks, in its own thread inside the actor and never when a letter is handled. It is completely save to access the actors internal state from within the callback. Typically you send an other actor a message to slow down a little. Only after the protectAlarm has been completed the next letter will be processed.

Since there may be some time between reaching the high level watermark and the subsequent execution of the protectAlarm callback, it may happen that the number of letters has changed in the mean time. This does not erase the callback unless in the unlikely event the message box has become completely empty. So if you check the mailbox size inside the protectAlarm handler you may find any value.

Internals of an actor

Clone this wiki locally