Repository navigation
ActorGuard
Text compliant with Leucine version 0.7.x
Up to now we made use of natural lifetime management of the actors.
In other words, when there were no more messages, the actor
stopped and garbage collector ditched the object. For a larger
system, this is not adequate, and more control over this
process is needed. Also, the termination behavior depends
on the platform and runtime environment. With ActorGuard
we automatically put all actors under ... guard. At the same
time some extra capabilities become available as a
result of that. Note that the actor guard is always
present, regardless if it is being used or not.
The ActorGuard is an object, so there is only one
in the whole system, and the watch facility is always
located at the main entry point of your application
as last statement. Depending on the platform it may block
or not when you call watch.
The signature of watch is:
/**
* Start watching for actor system completion. This uses polling to see if all actors are done.
* Do not set the pollInterval to low, for this calls all actors under guard. The minimum is 1
* second. In practice this time defines the maximum time to wait for the application to terminate
* after all the work is done. So for servers running days or more 10 seconds to 1 minute may be a
* good value, for CLI apps, use 2 seconds. Use force if you want to terminate other processes as
* well when the actors are all completed to shutdown. The function complete() is called after all
* actors have stopped. Calling the watch method may be needed to start the actor system. */
def watch(force: Boolean, pollInterval: FiniteDuration, complete: () => Unit = () => ())(using context: ActorContext): UnitTo illustrate its use, less us look at the way this is used in the demos that come along with this library. The main entry point is defined as:
object Init extends LogInfo:
import PlatformContext.Platform
/* When you arrive here, you can be certain all actors are done */
def complete(): Unit = println("Demo complete")
@main
def main(): Unit =
/* Welcome message */
println(s"Started Actor examples on the ${actorContext.platform} platform.")
/* Open the console as an actor to get commands from the user. */
val console = new Console
/* Watch the actors for completion. Note that this blocks for the JVM and Native platforms
* but not on JS. There blocking is not possible and this call returns immediately. Normally
* you do not call watch on JS, for this does have not much added value. Except if you use
* stop(Silent) somewhere. */
ActorGuard.watch(false,3.seconds,complete)
/* The application should exit when all work is done. */We defined the polling for termination to be 3 seconds, so the user does not need to wait to long after completion.
Making use of watch enables an other feature and that is to stop an actor when
it has gone silent for a certain amount of time. The ActorGuard keeps a list
of all actors in the Stop.Silent mode and terminates those that are inactive.
This can be handy for services that are created on the fly but of which the
termination cannot be clearly defined. With Stop.Silent and ActorGuard.watch
they are terminated when no longer needed. Note that the reverse also holds true.
If ActorGuard.watch is not used in your system, Stop.Silent is ineffective.
It relies on the polling mechanism so that we do not need to define a timer for
each actor separately.
The ActorGuard has one other public method with which you may obtain
any actor of the system based on its path. It is much like the get on
the FamilyActor but it works system wide. This facility does not depend
on the use of ActorGuard.watch and is always at your disposal on the object
ActorGuard.
/**
* Get the actor with this path/name if it exists. It will recurse into the family tree if required
* All actors you gave a name manually are indexed. Here is the primary search point. If you are
* already inside a family actor, it is more efficient to search just that tree. */
def get(path: String)(using context: ActorContext): Option[Actor]It will return the actor if it exists, but the type will be an generic Actor. With
this interface you can stop it, and get its activity, but that's about it. If you
need to send a letter to it, do a match on the expected type.
You can register a function at ActorGuard.failed(..) with the signature:
/**
* Here you register a handler for failed messages. This includes all messages that
* are refused at sending, cause an exception that is not catched by the user, is
* unmatched (manually added) or is left over as unprocessed when the actor stops
* prematurely. The post handler can be called in any context, so the execution should
* be very brief. It is for logging, or post processing by sending it to an other actor.
* Nothing more. Define this handler at the beginning of your application and make sure
* it is only defined once. */
def failed(post: Actor.Post => Unit): Unitto get a report on all messages that were not regularly processed. This could be routed to a logger at the start of the application for example:
ActorGuard.failed(post => logger.warn(post.full))For more information on unhandled messages, see the page Catch unhandled messages.