Advanced Topics in Concurrency and Reactive Programming: Akka Majeed Kassis
Become/Unbecome Allows changing the behavior of the Actor after instantiation. “Hot Swapping” – changing during runtime Can be done in two ways: Explicit Call It will replace the current behavior (i.e. the top of the behavior stack) No use of unbecome! The needed behavior is explicitly installed. Behavior Stack This behavior is not the default one used in Akka! Must ensure that the number of “pop” operations (i.e. unbecome) matches the number of “push” ones in the long run. otherwise this amounts to a memory leak. http://doc.akka.io/docs/akka/current/java/actors.html#become-unbecome
Initializing an Actor Initialization via constructor Done by implementing a constructor Initialization via prestart Done by overriding prestart() method Initialization via message passing Done by overriding createReceive() method http://doc.akka.io/docs/akka/current/java/actors.html#Initialization_patterns
Initialization via constructor Done by implementing the actor constructor And creating a new instance using “new” prior adding it to the system Benefit: Allows using val fields to store any state that does not change during the life of the actor instance Drawback: The constructor is invoked for every incarnation of the actor This means child Actors will also be initialized to their original state.
Initialization via prestart Done by overwriting preStart() function. Instantiation: preStart() of an actor is only called once directly during the initialization of the first instance, that is, at creation of its ActorRef. In the case of restarts, preStart() is called from postRestart(), therefore if not overridden, preStart() is called on every incarnation. However, overriding postRestart() one can disable this behavior, and ensure that there is only one call to preStart(). One useful usage of this pattern is to disable creation of new ActorRefs for children during restarts. This can be achieved by overriding preRestart() Which means child Actors are not reset due to parent Actor restart.
Initialization via message passing Done by overwriting createReceive() function Behavior of the Actor is implemented using become() function. Benefits: Allows dynamic creation of new Actors during runtime Sometimes parameters cannot all be passed to constructor Due to it being unavailable at that time
Stopping Actors First, the actor suspends its mailbox processing and sends a stop command to all its children. Then, it keeps processing the internal termination notifications from its children until the last one is gone Finally terminating itself: Invoking postStop() – used to clean resources on shutdown. Dumping mailbox Publishing Terminated on the DeathWatch Telling its supervisor This ensures that actor system sub-trees terminate in an orderly fashion: Propagating the stop command to the leaves Collecting their confirmation Sending it back to the stopped supervisor If one of the actors does not respond, this whole process will be stuck! Once an Actor is stopped: New messages are sent to “deadLetters” mailbox of the ActorSystem. http://doc.akka.io/docs/akka/current/java/actors.html#Stopping_actors No response from actor is due to processing a message for extended periods of time and therefore not receiving the stop command.
Stopping Actor: Methods Code: actorSystemRef.stop(anActorRef) Shuts down an Actor immediately. PoisonPill: akka.actor.PoisonPill message can be sent to stop an Actor. The actor will stop once the message is processed. PoisonPill is enqueued as ordinary messages! It is handled after the messages already queued in the mailbox. Code: victim.tell(akka.actor.PoisonPill.getInstance(), ActorRef.noSender()); Graceful Stop: Allows termination or compose ordered termination of several actors. Uses Futures in order to receive a notification once the Actor has stopped.
Graceful Stop: Code Example 1 2 3 4 5 6 7 8 9 10 public static final Shutdown SHUTDOWN = Shutdown.Shutdown; try { CompletionStage<Boolean> stopped = gracefulStop(actorRef, Duration.create(5, TimeUnit.SECONDS), SHUTDOWN); stopped.toCompletableFuture().get(6, TimeUnit.SECONDS); // the actor has been stopped } catch (AskTimeoutException e) { // the actor wasn't stopped within 5 seconds } CompletionStage<Boolean> is the Future object.
Router’s Specially Handled Messages Broadcast Messages Message sent to router and routed automatically to all routees PoisonPill Used to stop the router Can also be used to stop all routees once owrapped by a broadcast message Kill Messages Used to kill the router so it can be stopped/resumed/restarted Management Messages Used to add/remove routes and receive information from router
Broadcast Messages Used to send a message to all of a router's routees. When a router receives a Broadcast message, It will broadcast that message's payload to all routees No matter how that router would normally route its messages! Code: router.tell(new Broadcast("Watch out for Davy Jones' locker"), getTestActor()); http://doc.akka.io/docs/akka/current/java/routing.html#Specially_Handled_Messages
PoisonPill Messages If a router receives this message, the router itself will be stopped. It will not be sent on to routees! To send a PoisonPill message to Router routees: Wrap a PoisonPill message inside a Broadcast message Each routee will receive the PoisonPill message. Note: this will stop all routees, even if the routees aren't children of the router. Code: router.tell(new Broadcast(PoisonPill.getInstance()), getTestActor()); If all routees of a router are stopped, the router itself will stop automatically. Only dynamic router, e.g. using a resizer stays on after all its routees have stopped. http://doc.akka.io/docs/akka/2.5.0/java/routing.html#note-router-terminated-children-java
Kill Messages Kill message received is handled internally by the router Can be wrapped by BroadCast message to be routed to routees. The router will throw an ActorKilledException and fail. It will then be either resumed, restarted or terminated, depending on supervision strategy. Routees that are children of the router will also be suspended, They will be affected by the supervision directive that is applied to the router. Routees that are not the routers children will not be affected. i.e. those that were created externally to the router, will not be affected. Kill Router: router.tell(Kill.getInstance(), getTestActor()); Kill Routees: router.tell(new Broadcast(Kill.getInstance()), getTestActor());
Management Messages These messages are sent to routers. akka.routing.GetRoutees: Will make it send back its currently used routees in an akka.routing.Routees message. akka.routing.AddRoutee Will add that routee to its collection of routees. akka.routing.RemoveRoutee Will remove that routee to its collection of routees. akka.routing.AdjustPoolSize to a pool router actor Will add or remove that number of routees to its collection of routees. These management messages may be handled after other messages, so if you send AddRoutee immediately followed by an ordinary message you are not guaranteed that the routees have been changed when the ordinary message is routed. If you need to know when the change has been applied you can send AddRoutee followed by GetRoutees and when you receive the Routees reply you know that the preceding change has been applied.
Remote Actor HelloWorld Ability to send message from client to server – remotely! Client may send two types of messages to server: Message.GREET Message.DONE Changes: Modify “application.conf” to support remote communication Modify Client actor to “select” Server Actor for message passing.
Modifying Server “application.conf” akka { loglevel = INFO actor { provider = remote } remote { enabled-transports = ["akka.remote.netty.tcp"] netty.tcp { hostname = "127.0.0.1" port = 3553 } } }
Server Main import akka.actor.ActorSystem; import akka.actor.Props; public class Main2 { public static void main(String[] args) { //creating the system ActorSystem system = ActorSystem.create("HelloWorldSystem"); //creating system actors system.actorOf(Props.create(Greeter.class), "Greeter"); } }
Server “Greeter” Actor enum Msg implements Serializable { GREET, DONE; } public class Greeter extends AbstractActor { @Override public Receive createReceive() { return receiveBuilder() .matchEquals(Msg.GREET, m -> { System.out.println("Hello World!"); sender().tell(Msg.DONE, self()); }) .matchEquals(Msg.DONE, m -> { System.out.println("Client Disconnected!"); }) .build(); } } Serializable is not recommended. User Akka serialization instead
Modifying Client “application.conf” akka { loglevel = INFO actor { provider = remote } remote { enabled-transports = ["akka.remote.netty.tcp"] netty.tcp { hostname = "127.0.0.1" port = 2552 } } }
Client Main public class Main { public static void main(String[] args) { akka.Main.main(new String[] { HelloWorld.class.getName() }); } } Simple client main file with 1 Actor only. You may use the server main implementation instead if this implementation is too simple. This defines HelloWorld class as the system Actor.
Client Remote HelloWorld enum Msg implements Serializable { GREET, DONE; } public class HelloWorld extends AbstractActor { @Override public Receive createReceive() { return receiveBuilder() .matchEquals(Msg.DONE, m -> { // when the greeter is done, stop this actor and with it the application sender().tell(Msg.DONE, self()); getContext().stop(self()); }) .build(); } @Override public void preStart() { // this is the first step once the Actor is added to the system. ActorSelection greeter = getContext().actorSelection( "akka.tcp://HelloWorldSystem@127.0.0.1:3553/user/Greeter"); greeter.tell(Msg.GREET, self()); } } matchEquals() is used to check if the message itself equals the value. i.e. .matchEquals(Msg.DONE, …) match() is used to check whether the message object type equals some class. i.e. .match(Msg.class, …)
Required Dependencies: Actor and Remote These dependencies are added to the pom.xml file. They are automatically downloaded using Maven on first execution. <dependency> <groupId>com.typesafe.akka</groupId> <artifactId>akka-actor_2.12</artifactId> <version>2.5.0</version> </dependency> <dependency> <groupId>com.typesafe.akka</groupId> <artifactId>akka-remote_2.12</artifactId> <version>2.5.0</version> </dependency> These are added to the pom.xml file, Maven will automatically download the needed Akka Jars
What about GUI? Simple: Text Only! Preffered: What about user list in channel? Use a command to print user list Window will contain channel output You may use logging capabilities to provide output Preffered: Web UI! Play Framework: http://www.lightbend.com/activator/template/play-akka-cluster-sample
Default Method Advantage Allows adding new functionalities to interfaces without breaking the classes that implement that interface. Allows implementing methods directly in the interface! We add default keyword before the access modifier of the method we wish to implement. This implementation will be the default implementation for all classes implementing this interface, It can be overridden if needed.
Java8: Default Methods interface Stack<E> { void push(E something); E pop(); int size(); boolean isEmpty(); } interface Stack<E> { void push(E something); E pop(); int size(); default boolean isEmpty(){ return (size())==0); } Implementation of isEmpty() wil act as the default implementation as long as the class does not overwrite it. If the same default method is implemented in two different interfaces, and our class extends both interfaces, we must overwrite the default method.
Lambdas – Java8 Allows writing a method in the same place to be used. Especially useful in places where a method is being used only once, and the method definition is short. Saves the effort of declaring and writing a separate method to the containing class. Note: Lambdas implement an interface that consists of one non default function only. Syntax Example: (arg1, arg2...) -> { body } (type1 arg1, type2 arg2...) -> { body }
Code Examples (int a, int b) -> { return a + b; } () -> System.out.println("Hello World"); (String s) -> { System.out.println(s); } () -> 42 () -> { return 3.1415 };