Address Model
Actors are isolated from each other and can only communicate through Address instances. The Address model provides compile-time type safety and flexible routing strategies.
Address Trait
Address[-M <: Call] is the root trait (contravariant in M). It provides the core messaging API:
trait Address[-M <: Call] {
def notice(notice: M & Notice): Unit
def ask(ask: M & Ask[? <: Reply]): FutureState[? <: Reply]
def ask(ask: M & Ask[? <: Reply], future: Future[? <: Reply]): Unit
def reply(reply: Reply, replyId: Long, sender: AbstractActor[?]): Unit
}
The contravariance means Address[Call] can send any message type, while Address[MyNotice] can only send MyNotice messages.
PhysicalAddress
PhysicalAddress[M] is the concrete address bound to a single ActorHouse. It is the base class for all address types that directly route messages to a specific actor's mailboxes.
Message Routing
When sending a message via PhysicalAddress:
Ask (ask method):
packaging(ask, sender)— Creates anEnvelopefrom the pool, sets the sender, messageId, and contentsender.attachStack(envelope.messageId, future)— Wires the promise to the sender's currentStackandFutureDispatcherhouse.putAsk(envelope)— Places into the target actor'saskMailbox
Notice (notice method):
- Creates an
Envelope(no sender needed) house.putNotice(envelope)— Places into the target actor'snoticeMailbox
Reply (reply method):
- Creates an
Envelopewith areplyId(single) orreplyIds(array) house.putReply(envelope)— Places into the sender actor'sreplyMailbox
Timeout Support
When sending an Ask with a timeout:
address.ask(myAsk, future, timeout)
After the ask is sent, a timer task is registered via sender.timer.registerAskTimeout(...). If the timeout fires before a reply arrives, a TimeoutReply is delivered instead.
ActorAddress
ActorAddress[M] is the simplest address type -- a final class extending PhysicalAddress. Each ActorHouse creates exactly one ActorAddress for its actor.
final class ActorAddress[M <: Call](house: ActorHouse) extends PhysicalAddress[M]
RobinAddress
RobinAddress[M] provides round-robin load balancing over an array of ActorAddress instances. It is created when ActorSystem.buildActor is called with num > 1.
Routing Strategies
Notice routing:
Simple round-robin: noticeCursor % underlying.length
Ask routing — two modes:
-
Thread affinity (Load Balance mode): When
sender.isLoadBalance && isLBis true, it routes tounderlying(sender.mountedThreadId)-- sends to the worker on the same thread, avoiding cross-thread mailbox writes. This is the optimal path. -
Round-robin: Otherwise, uses
askCursor % underlying.length
When RobinAddress is Created
num == pool.size: One actor per thread, all markedisLoadBalance = true. TheRobinAddresswithisLB = trueenables thread-affinity routing.num > 1(other): Distributes actors across selected threads with simple round-robin.
EventableAddress
EventableAddress is a trait with an inform(event) method for timer/event delivery. PhysicalAddress overrides this to put events into the house's eventMailbox.
There is also ActorThreadAddress, which routes events to the thread's eventQueue (ConcurrentLinkedQueue) — used for ResourceTimeoutEvents that need thread-level processing.
Address and Type Safety
The type system ensures that you cannot send the wrong message type to an Actor:
case class Ping extends Notice
case class Pong extends Notice
case class AskData extends Ask[DataReply]
case class DataReply(value: String) extends Reply
class PingActor extends StateActor[Ping | AskData]
// Creating the actor returns a typed Address:
val address: Address[Ping | AskData] = system.buildActor[PingActor](ActorFactory[PingActor])
// Compile-time safe:
address.notice(Ping()) // OK
address.ask(AskData()) // OK
// Compile-time error:
// address.notice(Pong()) // Error: Pong is not Ping | AskData