Skip to content

StashAid

devlaam edited this page Jun 6, 2023 · 5 revisions

StashAid: Save received letters for later processing

Text compliant with Leucine version 0.4.3

Introduction

The messages that enter the mailbox appear in (pseudo) random order. The only situation where there is certainty about the order, is when two messages originate from the same actor and are send directly to the mailbox. They will keep their order.

However, in the processing a certain order may be required, or it may only be possible to process a message when in a certain (future) state. So we want to be able to put away a message for later processing. That is where StashAid is for.

The mixin StashAid

When the StashAid is mixed in, a new object Stash object appears that holds the following methods:

    /**
     * Store a letter and sender manually on the stash. With this method, you may replace one
     * letter with an other, or spoof the sender, and reprocess later. If the actor was asked to
     * finish, store will still work, since the letter was from before that request. */
    def store[Sender <: Accept](letter: Letter[Sender], sender: Sender): Unit

    /**
     * Automatically stores the current letter (and sender) that is processed on the stash. If the
     * actor was asked to finish, store will still work, since the letter was from before that request. */
    def store(): Unit

    /** Clear the stash instantly. */
    def clear(): Unit

    /**
     * Flush the stash to the mailbox. This is usually the last instruction before you switch to a
     * new state to handle the stashed messages once more. If the actor was asked to finish,
     * flush will still work, since the stored letters were from before that request. */
    def flush(): Unit

    /** See how many letters are on the stash. This is fast: O(1). */
    def size: Int

    /** See if there are any letters on the stash. */
    def isEmpty: Boolean

The order in which the messages are stash is preserved. And at a flush(), the stashed messages are put in front of the current mailbox. This implies that after flush() the next message to be expected is the one that was stashed first.

The actor is implemented making use of a two lists, like the Scala Queue but tailor made for this needs. Its size is not limited other than by machine limits.

The signature of the first store(...) method depends on the actor in which it is used. We have:

  • WideActor => def store(letter: Letter, sender: Actor): Unit
  • AcceptActor => def store(letter: Letter): Unit
  • SelectActor => def store(letter: Letter, sender: Sender): Unit
  • RestrictActor => def store[Sender <: Accept](letter: Letter[Sender], sender: Sender): Unit

Example with a Stack.

To illustrate its function, we define an actor that behaves like a programmable stack for integers. The specialty about this stack is that it stores odd numbers directly whereas if keeps the even numbers that arrive apart, until the Stack is Released. First the even numbers are added to the stack, and from that moment on, numbers are stored in the order that they arrive.

The stack itself is contained in the State (so leave out Stateless here), as well as the block boolean, which keeps even numbers apart.

Its function becomes clear when looking at the letters:

object Stack extends AcceptDefine :
  sealed trait Letter extends Actor.Letter[Actor]
  /* Write one integer to the stack.  */
  case class Write(value: Int) extends Letter
  /* Release the blocking of even numbers */
  case object Release extends Letter
  /* Print and clean the current Stack. */
  case object Spool extends Letter
  /* The State contains the Stack and the block situation.*/
  case class State(values: List[Int], block: Boolean) extends Actor.State :
    def show: String = values.mkString(",")
  /* We must always specify how to start. */
  val initial = Stack.State(Nil,true)

To implement these functions we define:

class Stack extends AcceptActor(Stack), StashAid :

  def receive(letter: Letter): Receive = (state: State) => letter match
    /* Handle the case a new number arrives. */
    case  Stack.Write(value)  =>
      /* If we are blocked and the number is even ... */
      if state.block && value%2==0
      /* ... store away this message*/
      then { Stash.store(); state }
      /* otherwise add it to the Stack .*/
      else state.copy(value::state.values)
    /* The block on the stash is released */
    case  Stack.Release  =>
      /* reprocess the stashed messages. */
      Stash.flush();
      /* Unblock the Stack */
      state.copy(block=false)
    /* We want to know the contents */
    case  Stack.Spool =>
      /* Show what we have got. */
      println(s"Stack content: ${state.show}")
      /* Return to the initial Stack State */
      Stack.initial

And to test we manipulate a sequence of numbers (1..12) as follows:

/* Main entry point for the demo. */
object Main :
  /* We must provide a default sender. */
  given Actor.Anonymous = Actor.Anonymous
  /* Create the Stack. */
  private val stack = new Stack

  def main(args: Array[String]): Unit =
    ( 1 to  8).foreach(i => stack ! Stack.Write(i))
    stack ! Stack.Release
    ( 9 to 12).foreach(i => stack ! Stack.Write(i))
    stack ! Stack.Spool
    stack.stop(Actor.Stop.Finish)
    Thread.sleep(200)

This produces:

Stack content: 12,11,10,9,8,6,4,2,7,5,3,1

Internals of an actor

Clone this wiki locally