how to watch akka actor and capture its context in the Terminated message receive - scala

I want to supervise child actors using context.watch(childActor) API
I see that when the child actor has an uncaught exception, indeed the receive message "Terminated" is called on the parent.
But since I am creating many children actors, each with different contexts and messages, I don't know which particular child actor (and surrounding context) actually failed.
For example:
class ParentActor extends Actor {
override def receive = {
case "delegateWorkToChildrenActors" => {
(0 to 100) foreach {
i =>
val child = context.actorOf(Props(new ChildActor(i, "child-actor")
context.watch(child)
val message = SomeSortOfComplexMessageWithManyParameters(...)
val res = child ? message
res.onComplete {
x: Try[Any] => x match {
case Failure(exception: Throwable) => // can only get timeout exception here
case Success(value) => // continue as usual
}
}
}
}
case m # Terminated(actor) => {
// a child actor was terminated but what is its context ? i.e. which message did it try to handle ?
}
}
override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 1, withinTimeRange = 1 minute) {
case m # _ => {
Stop // Assume that the actor was stopped or crashed etc.
}
}
}
class ChildActor(number : Int) extends Actor {
override def receive: Receive = {
case SomeSortOfComplexMessageWithManyParameters => {
// might of got some sort of exception here...
// the parent which is monitoring it received the "Terminate" message
}
}
}
How can I get the message that the child actor worked upon?

Related

How to work with Source.Queue in Akka-Stream

I am toying around trying to use a source.queue from an Actor. I am stuck in parttern match the result of an offer operation
class MarcReaderActor(file: File, sourceQueue: SourceQueueWithComplete[Record]) extends Actor {
val inStream = file.newInputStream
val reader = new MarcStreamReader(inStream)
override def receive: Receive = {
case Process => {
if (reader.hasNext()) {
val record = reader.next()
pipe(sourceQueue.offer(record)) to self
}
}
case f:Future[QueueOfferResult] =>
}
}
}
I don't know how to check if it was Enqueued or Dropped or Failure
if i write f:Future[QueueOfferResult.Enqueued] the compile complain
Since you use pipeTo, you do no need to match on futures - the contents of the future will be sent to the actor when this future is completed, not the future itself. Do this:
override def receive: Receive = {
case Process =>
if (reader.hasNext()) {
val record = reader.next()
pipe(sourceQueue.offer(record)) to self
}
case r: QueueOfferResult =>
r match {
case QueueOfferResult.Enqueued => // element has been consumed
case QueueOfferResult.Dropped => // element has been ignored because of backpressure
case QueueOfferResult.QueueClosed => // the queue upstream has terminated
case QueueOfferResult.Failure(e) => // the queue upstream has failed with an exception
}
case Status.Failure(e) => // future has failed, e.g. because of invalid usage of `offer()`
}

Is it possible for the supervisor to pass the exception to the calling actor if the child actor fails even after retrying N times?

I have two actors Computer and Printer. Computer is the parent of Printer and has a one for one strategy defined for Printer.
I have listed the code below.
class Computer extends Actor with ActorLogging{
import Computer._
import Printer._
implicit val timeout: Timeout = 2 seconds
val printer: ActorRef = context.actorOf(Props[Printer], "printer-actor")
override def receive: Receive = {
case Print(text) => {
val printJob: Future[Any] = printer ? PrintJob(Random.nextInt, text)
printJob.mapTo[Page].map {
case Page(text) => {
log.info(s"Received page containing text ${text}")
context.system.shutdown()
}
}.onFailure {
case t: Throwable => sender ! akka.actor.Status.Failure(t)
}
}
}
override val supervisorStrategy =
OneForOneStrategy(maxNrOfRetries = 3, withinTimeRange = 1 minute) {
case e : Exception => {
log.info(s"caught exception of type ${e.getClass}")
SupervisorStrategy.Restart
}
}
}
class Printer extends Actor with ActorLogging{
import Printer._
override def receive: Receive = {
case PrintJob(id, text) => {
log.info(s"Received ${PrintJob(id, text)}")
if (Random.nextBoolean) sender ! Page(text)
else throw new NoPaperException(id)
}
}
override def preRestart(cause: Throwable, message: Option[Any]) = {
log.info(s"Restarting actor ${self} because of ${cause}. Queueing message ${message}")
postStop()
message.map(self forward _)
}
}
The Printer throws an exception based on the random generator. The code works fine, the supervisor restarts the and retries the child actor on failure just as instructed.
However the ask pattern val printJob: Future[Any] = printer ? PrintJob(Random.nextInt, text) fails with a AkkaTimeoutException in case all attempts to get the Printer actor work fails.
Is there a way to pass back the exact exception which caused the actor to fail ? In this case NoPapperException.
Cheers,
Utsav
to pass the exception back to the sender you need to sender ! Status.Failure(e) - where e is the exception
You can either do that directly from the actor, or if you want to do that from the supervisor you need to have a subclass of exception that would hold the sender ref with it so that the supervisor would be able to send the exception back

Update state in actor from within a future

Consider the following code sample:
class MyActor (httpClient: HttpClient) {
var canSendMore = true
override def receive: Receive = {
case PayloadA(name: String) => send(urlA)
case PayloadB(name: String) => send(urlB)
def send(url: String){
if (canSendMore)
httpClient.post(url).map(response => canSendMore = response.canSendMore)
else {
Thread.sleep(5000) //this will be done in a more elegant way, it's just for the example.
httpClient.post(url).map(response => canSendMore = response.canSendMore)
}
}
}
Each message handling will result in an async http request. (post return value is a Future[Response])
My problem is that I want to safely update counter ( At the moment there is a race condition)
BTW, I must somehow update counter in the same thread, or at least before any other message is processed by this actor.
Is this possible?
You can use become + stash combination to keep on stashing messages when the http request future is in process.
object FreeToProcess
case PayloadA(name: String)
class MyActor (httpClient: HttpClient) extends Actor with Stash {
def canProcessReceive: Receive = {
case PayloadA(name: String) => {
// become an actor which just stashes messages
context.become(canNotProcessReceive, discardOld = false)
httpClient.post(urlA).onComplete({
case Success(x) => {
// Use your result
self ! FreeToProcess
}
case Failure(e) => {
// Use your failure
self ! FreeToProcess
}
})
}
}
def canNotProcessReceive: Receive = {
case CanProcess => {
// replay stash to mailbox
unstashAll()
// start processing messages
context.unbecome()
}
case msg => {
stash()
}
}
}

postRestart and preRestart methods are not getting invoke in akka actots

I am following this tutorial here is my code
case class ArtGroupDeleteFromES (uuidList:List[String])
class ArtGroupDeleteESActor extends Actor{
val log = LoggerFactory.getLogger(this.getClass)
override def preStart() {
log.debug("preStart Starting ArtGroupDeleteESActor instance hashcode # {}",
this.hashCode())
}
override def postStop() {
log.debug("postStop Stopping ArtGroupDeleteESActor instance hashcode # {}",
this.hashCode())
}
override def preRestart(reason: Throwable, message: Option[Any]) {
log.debug("I am restarting")
log.debug("ArtGroupDeleteESActor: preRestart")
log.debug(s" MESSAGE: ${message.getOrElse("")}")
log.debug(s" REASON: ${reason.getMessage}")
super.preRestart(reason, message)
}
override def postRestart(reason: Throwable) {
log.debug("restart completed!")
log.debug("ArtGroupDeleteESActor: postRestart")
log.debug(s" REASON: ${reason.getMessage}")
super.postRestart(reason)
}
def receive = {
case ArtGroupDeleteFromES(uuidList) =>
throw new Exception("Booom")
sender ! true
}
case message =>
log.warn("Received unknown message: {}", message)
unhandled(message)
}
}
and here is the how i am sending this actor a message
class ArtGroupDeletionActor extends Actor{
val log = LoggerFactory.getLogger(this.getClass)
override val supervisorStrategy = OneForOneStrategy(
maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
case _:Exception => Restart
}
val artGroupDeleteESActor=context.actorOf(Props[ArtGroupDeleteESActor]
.withDispatcher("akka.actor.ArtGroupDeleteESActor-dispatcher")
,name = "ArtGroupDeleteESActor")
def receive = {
case DeleteArtGroup(uuidList) =>
val future1 = ask(artGroupDeleteESActor, ArtGroupDeleteFromES(uuidList)).mapTo[Boolean]
var isDeletedfromES = Await.result(future1, timeout.duration)
case message =>
log.warn("Unhandled message received : {}", message)
unhandled(message)
}
}
object test extends App{
val artGroupDeletionActor=system.actorOf(Props[ArtGroupDeletionActor]
.withDispatcher("akka.actor.ArtGroupDeletionActor-dispatcher")
,name = "ArtGroupDeletionActor")
artGroupDeletionActor ! DeleteArtGroup(List("123"))
}
the PostRestart() and preRestart() methods are not invoking,but preStart() and postStop() gets called, please guide me where i am doing wrong
(for simplicity I'll call your actors Parent and Child from now on)
What happens here is that when an exception occurs inside Child.receive, it doesn't send a response to Parent, instead, the actor system sends some control instruction for the supervision strategy. However, Parent is blocked on Await waiting for completion of future1, which only happens after the timeout exceeds, and then, in turn, a TimeoutException is thrown inside Parent.receive, killing (restarting) the Parent actor itself, and thus the supervising message of an exception in Child is then passed to deadLetters, never restarting the Child.
You should never, ever, ever block inside an actor, so this is incorrect:
val future1 = ask(artGroupDeleteESActor, ArtGroupDeleteFromES(uuidList)).mapTo[Boolean]
var isDeletedfromES = Await.result(future1, timeout.duration)
Instead, you have to either utilize some kind of message identification to distinguish one reply from another in concurrent environment, or add an onComplete to the Future and send a message to self in the closure (beware: no logic other than sending a message should be executed inside the closure to the Future!).
So, option A:
case class ArtGroupDeleteFromES(id: Long, uuidList: List[String])
case class ArtGroupDeleteFromESResult(id: Long, success: Boolean)
class Parent extends Actor {
override val supervisionStrategy = ...
var msgId = 0L
var pendingRequesters = Map.empty[Long, ActorRef]
val child = context.actorOf(Props[Child])
def nextId = {
msgId += 1
msgId
}
def receive = {
case DeleteArtGroup(uuidList) =>
val id = nextId
pendingRequesters += id -> sender() // store a reference to the sender so that you can send it a message when everything completes
child ! DeleteArtGroupFromES(nextId, uuidList)
case ArtGroupDeleteFromESResult(id, success) =>
// process result...
pendingRequesters(id) ! "done"
pendingRequesters -= id
}
}
And option B:
case class ArtGroupDeleteFromES(uuidList: List[String])
case class ArtGroupDeleteFromESResult(replyTo: ActorRef, success: Boolean)
class Parent extends Actor {
override val supervisionStrategy = ...
val child = context.actorOf(Props[Child])
def receive = {
case DeleteArtGroup(uuidList) =>
val requester = sender() // when the future completes, sender may have already changed, so you need to remember it
(child ? DeleteArtGroupFromES(uuidList)).onComplete {
case Success(success) => self ! ArtGroupDeleteFromESResult(requester, success)
case Failure(e) =>
log.warn("Could not delete...", e)
self ! ArtGroupDeleteFromESResult(requester, success = false)
}
}

Akka supervisor actor do not handle exception when child actor throws an exception within onFailure of a future

I'm facing a problem with an Akka supervisor actor. When the child actor throws an exception within onFailure method of a future result, the supervisor does not handle the error (I want to restart the child in the case of a ConnectException).
I'm using Akka 2.3.7.
This is the supervisor actor:
class MobileUsersActor extends Actor with ActorLogging {
import Model.Implicits._
import Model.MobileNotifications
override val supervisorStrategy =
OneForOneStrategy(maxNrOfRetries = 3, withinTimeRange = 1 minute) {
case _: java.net.ConnectException => {
Logger.error("API connection error. Check your proxy configuration.")
Restart
}
}
def receive = {
case Start => findMobileUsers
}
private def findMobileUsers = {
val notis = MobileNotificationsRepository().find()
notis.map(invokePushSender)
}
private def invokePushSender(notis: List[MobileNotifications]) = {
notis.foreach { n =>
val pushSender = context.actorOf(PushSenderActor.props)
pushSender ! Send(n)
}
}
}
And this is the child actor:
class PushSenderActor extends Actor with ActorLogging {
def receive = {
case Send(noti) => {
val response = sendPushNotification(noti) onFailure {
case e: ConnectException => throw e
}
}
}
private def sendPushNotification(noti: MobileNotifications): Future[WSResponse] = {
val message = "Push notification message example"
Logger.info(s"Push Notification >> $message to users " + noti.users)
PushClient.sendNotification(message, noti.users)
}
}
I tried to notify sender with an akka.actor.Status.Failure(e) as is suggested here, but did not work, the exception keep unhandled by the supervisor.
As a workaround, I found this way to get it work:
class PushSenderActor extends Actor with ActorLogging {
def receive = {
case Send(noti) => {
val response = sendPushNotification(noti) onFailure {
case e: ConnectException => self ! APIConnectionError
}
}
case APIConnectionError => throw new ConnectException
}
private def sendPushNotification(noti: MobileNotifications): Future[WSResponse] = {
val message = "Push notification message example"
Logger.info(s"Push Notification >> $message to users " + noti.users)
PushClient.sendNotification(message, noti.users)
}
}
Is this an Akka bug or am I doing something wrong?
Thanks!
I think that the problem is that the exception thrown inside the Future doesn't belong to the same thread (potentially) as the one the Actor is running (someone more experienced can elaborate on this). So, the problem is that the exception thrown inside the Future body is "swallowed" and not propagated to the Actor. Since this is the case, the Actor doesn't fail and so there's no need to apply the supervision strategy. So, the first solution that comes to my mind is to wrap the exception inside the Future in some message, send it to yourself, and then throw it from inside the Actor context itself. This time, the Exception will be caught and the supervision strategy will be applied. Note, however, that unless you send the Send(noti) message again, you will not see the Exception happening since the Actor was restarted. All in all, the code would be like this:
class PushSenderActor extends Actor with ActorLogging {
case class SmthFailed(e: Exception)
def receive = {
case Send(noti) => {
val response = sendPushNotification(noti) onFailure {
case e: ConnectException => self ! SmthFailed(e) // send the exception to yourself
}
}
case SmthFailed(e) =>
throw e // this one will be caught by the supervisor
}
private def sendPushNotification(noti: MobileNotifications): Future[WSResponse] = {
val message = "Push notification message example"
Logger.info(s"Push Notification >> $message to users " + noti.users)
PushClient.sendNotification(message, noti.users)
}
}
Hope it helped.