I have a class that itself is an Actor:
class Client(server: Server, systemActor: ActorRef) extends Actor {
...
}
I have a list that manages connected clients. The actor that spawns the client actors watches a list off "servers" in the database, and spawns a client connection for each one like this:
private val clientList = mutable.ArrayBuffer.empty[Client]
class RegistrationWatcherActor(val systemActor: ActorSystem) extends Actor with Timers {
implicit val system: ActorSystem = context.system
timers.startSingleTimer(TickKey, FirstTick, FiniteDuration(1, TimeUnit.SECONDS)) // wait 10 seconds to give database time to initiate
private def checkRegistrations(): Unit = {
val database = DatabaseUtil.getInstance
val serversOutgoing: Seq[Server] = database.getAll[Server](classOf[Server])
for (server <- serversOutgoing) {
val client = clientList.find{ client => client.server == server }
if (client.isEmpty) { // client for this server was not found, so create one
}
}
}
def receive: Receive = {
case FirstTick =>
timers.startTimerWithFixedDelay(TickKey, Tick, FiniteDuration(1, TimeUnit.SECONDS))
case Tick =>
checkRegistrations()
}
}
The Problem
In the check if (client.isEmpty)
I would like to create an instance of Client
, spawn it as an actor, and put the reference into the list so it doesn't get created again. As you can see, my method of identifying if the client has been created or not is my checking its serverID
against the stored one in the database.
Ideally, I want to do:
val client = new Client(server, systemActor)
systemActor.actorOf(Props(client), server.serverID)
clientList.append(client)
Any suggestions please?
Alternative
I realised I can make clientList
a list of String
and store the serverID
in here. However, I would eventually like the RegistrationWatcher
to be able to have control over aspects of the client, so a reference to the Client
object would be preferable.
As a general principle, part of the point of the actor model is that actors are autonomous. Wanting the RegistrationWatcherActor
to "have control" over aspects of the Client
's behavior/state is thus a sign that the decomposition of responsibilities for the actors is off.
If there is some state that you truly need to share between actors (or between an actor and the outside world), note that the single-threaded illusion within the actors is being broken and you are basically back in the world of multithreaded concurrency (e.g. needing to use synchronized
blocks, volatile
s, concurrent collections, or atomics): even if you had a reference to the Client
, there's no actual guarantee that calling methods on the Client
from the RegistrationWatcherActor
would be visible until the next time the RegistrationWatcherActor
sent a message to the Client
and that message was about to be processed. It's probably better in this situation to have the shared state be wrapped in an AtomicReference
that's passed to the Client
and save the AtomicReference
in the RegistrationWatcherActor
.