Safe Functions - laforge49/Asynchronous-Functional-Programming GitHub Wiki

Sometimes the overhead of ensuring thread safety can be discarded. We can do this with safe functions, which always run synchronously and place the burden of thread safety squarely on the shoulders of the developer. With safe functions you have the option of either immediately returning a response or first sending a request that may be processed asynchronously and sending a response later.

Safe functions are implemented as a separate object which is bound in an actor to a message class. The reason for this is that the implicit ActiveActor for a safe function is the actor which sent the request rather than the actor receiving the request. Time for an example. We will begin by defining an actor, SafeActor, which prints the content of the Print message asynchronously.

case class Print(value: Any)

class SafeActor extends Actor {
  setMailbox(new ReactorMailbox)
  bind(classOf[Print], printFunc)
  private def printFunc(msg: AnyRef, rf: Any => Unit) {
    println(msg.asInstanceOf[Print].value)
    rf(null)
  }
}

Now we want to add a new message, PrintEven, for which we will print only the even numbers.

case class PrintEven(value: Int)

case class SafePrintEven(safeActor: SafeActor)
  extends Safe {
  override def func(target: Actor, msg: AnyRef, rf: Any => Unit)(implicit sender: ActiveActor) {
    val printEven = msg.asInstanceOf[PrintEven]
    val value = printEven.value
    if (value % 2 == 0) safeActor(Print(value))(rf)
    else rf(null)
  }
}

Now all we need to do is to add a call to bindSafe in the constructor of SafeActor.

bindSafe(classOf[PrintEven], SafePrintEven(this))

That's it. Now when we send a PrintEven message to SafeActor, we get an immediate response if the number is odd; otherwise the number is printed asynchronously.

Testing is a bit more difficult than usual, as Future does not work with safe functions. So we need a test driver.

case class AMsg()

class Driver extends Actor {
  setMailbox(new ReactorMailbox)
  bind(classOf[AMsg], aMsgFunc)
  private def aMsgFunc(msg: AnyRef, rf: Any => Unit) {
    val safeActor = new SafeActor
    safeActor(PrintEven(1)){rsp =>
      safeActor(PrintEven(2)){rsp => rf(null)}
    }
  }
}

The driver actor then chains two calls to SafeActor. Here's the final output.

2

SafeTest

Tutorial