0
votes

I have a system that spawns a single actor who will spawn many futures. Some of these futures will run into scenarios that need to spawn more futures (but tell the actor about it). How do I send a message from a future to an actor on the completion of the future's operations?

I've looked at the pipeTo documentation but I am having trouble referencing the actors in my system in my future class.

Here is what my Future class looks like:

class crawler(string: String) {

  val status: Future[Boolean] = Future[Boolean] {
     //Do something with content
     println("I am a future working on cert crawling. My cert contents are: " + cert.content)
     true
  }

  status onComplete {

    case Success(true) =>
        for(chars <- string.toCharArray) {
          //send actor a message for each character of the string.
        }

    case Failure(t) => println("An error has occured: " + t.getMessage)
    }
}

Where the actor's receive method does the following:

def receive = {
  case c:Char => if(!certCache.containsKey(c)){
      println("actor >>>> Need to begin crawl on " + c + ".")
      sender() ! new crawler("give sender the future")
  case _ => println("That's not the right input!")
}

And, my Actor is spawned like:

object Main extends App {

  val system = ActorSystem("MySystem")
  val actor = system.actorOf(Props[actorClass], name = "actor")

  actor ! 'a'
}
1
It's really hard to answer a question like this if you don't provide a minimal example that illustrates what you are trying to do.Aldo Stracquadanio
Can't you make your future-based workflow self-contained and only notify the actor (using pipeTo) at the completion of the last nested future? If you have a computation that needs to pass back multiple results, maybe akka-stream would be a better match than futures?Rüdiger Klaehn
I updated my question to include snippets of code @AldoStracquadanioahagouel
The two classes you are providing seem to be unrelated, how is the actor using the crawler? Also, @RüdigerKlaehn gave a good advice about avoiding the multiple results if they are not really needed: if an actor receives a lot of characters, why can't it receive a string?Aldo Stracquadanio
I've updated my question again to show the Actor's receive method. In the context of this specific example, it might be useful to use streams, but in my end result I don't want to do something like what @RüdigerKlaehn has said.ahagouel

1 Answers

2
votes

Directly

You could dependency inject the ActorRef into your Future (not recommended, see Abstracted) :

import akka.actor.ActorRef

//dependency injection of the ActorRef with a default value of noSender
class crawler(string : String, actorRef : ActorRef = ActorRef.noSender) {
  ...

  status OnComplete {
    //send each Char in string to the actorRef
    case Success(true) => string.foreach(actorRef ! _)

  ...
}

Then in your Actor you can use self to pass the ActorRef into the crawler:

def receive = {
  case c : Char => if(!certCache.containsKey(c)) {
    sender() ! new crawler("give sender the future", self)
  }
}

Abstracted

Further, you could abstract away the use of ActorRef entirely so that crawler doesn't need to know the details of messaging passing. This is the more "functional" approach which has the benefit of being extendable if you ever switch to Futures or even akka.stream.scaladsl.Source for reactive streams (see example):

//no akka imports or dependencies
class crawler(string : String, sendChar : (Char) => Unit) {
  ...
  case Success(true) => string foreach sendChar
}

And in your Actor you can pass an anonymous function to crawler which sends a Char to the Actor via self:

def receive = {
  case c : Char => if(!certCache.containsKey(c)) {
    sender ! new crawler("give sender the future", self ! _)
  } 
}

You can even get robust and provide default "do nothing" behavior for your sendChar function:

class crawler(string : String, sendChar : (Char) => Unit = {_=>}) {
  ...
}

val crawler = crawler("foo") //still get regular Future behavior for status