Fundo:
Eu tenho um Actor chamado Peer
. Dentro da minha aplicação, a Peer
representa uma conexão entre o servidor que o criou e outro servidor. É essencialmente um http-client
.
Quando a instância é executada pela primeira vez, o PeerManager
consulta o banco de dados para obter a lista de entradas do servidor. Em seguida, ele cria um Peer
ator para esse servidor. O estado do Peer
depende de seu RegistrationStatus
campo ( PENDING_OUT , PENDING_IN , REGISTERED , ...)
A Peer
tem uma função de recebimento padrão que se parece com esta:
override def receive: Receive = {
case FirstTick =>
system.log.info("receive FirstTick")
timers.startTimerWithFixedDelay(TickKey, Tick, FiniteDuration(2, TimeUnit.SECONDS))
case Tick =>
system.log.info("receive Tick")
// process every state until all are complete
var statesLeft = false
states = states.sortWith{ (s1, s2) => s1.order > s2.order }
for (state <- states) {
if (!state.complete) {
statesLeft = true
if (state.waitForPing) {
context.become(pingService(state))
} else {
context.become(state.behavior)
}
}
}
if (!statesLeft) context.become(pingService(null))
}
Isso basicamente permite a Peer
transição por uma lista de estados, executando o comportamento de cada estado até que ele esteja completo.
Um conjunto de estados pode se parecer com isto:
case PENDING_OUT =>
Seq(
new InitState(0, peer),
new SessionHandshake(1, peer),
new WaitMessages(2, peer)
)
Um exemplo de um dos estados:
class WaitAccept(order: Int, peer: Peer)(implicit system: ActorSystem) extends PeerState(order, false, true) {
override def behavior: Receive = {
case Tick =>
system.log.info("Waiting for accept")
if (complete) peer.getContext.unbecome()
case rA@RegistrationAccept(serverID, serverIP, _) =>
system.log.info(s"Processing RegistrationAccept: ${rA.toProtoString}")
complete = true
}
}
PeerState
superclasse:
abstract case class PeerState(var order: Int, var complete: Boolean, var waitForPing: Boolean) {
def behavior: Receive // <- must be overridden
}
Esse estado faz com que o peer fique sentado esperando por uma mensagem 'RegistrationAccept', gerada quando o usuário clica em 'Aceitar' na página da web.
O problema:
Alguns dos estados (por exemplo, Handshake
o estado que é responsável por trocar chaves de criptografia) enviam http
mensagens para o server
que o Peer
é gerado para. Se a instância que recebe a mensagem encontrar um Peer
com o solicitado serverID
, o PeerManager
encaminhará essa mensagem para o Peer
usando ask
assim:
case peerRequest@PeerRequest(serverID, SessionRequest(sessionRequest), _) =>
// get the relevant peer for the requested serverID
val peerEntry = peerMap.get(serverID)
if (peerEntry.isDefined) {
// ask the peer Actor for a response to the sessionRequest, block and wait for this response, then send it back to the peerManager
val peerState = queryPeerState(peerEntry.get) // <-- the issue
if (peerState.isInstanceOf[SessionHandshake]) { // <-- the issue
try {
sender() ! Await.result((peerEntry.get ? (self, sessionRequest)).mapTo[PeerResponse.Response], timeout.duration)
} catch {
case e: TimeoutException =>
sender() ! PeerResponse.Response.StatusResponse(HydraStatusCodes.PEER_MANAGER_TIMEOUT.getStatusResponse)
}
} else {
sender() ! PeerResponse.Response.StatusResponse(HydraStatusCodes.PEER_NOT_READY.getStatusResponse)
}
} else {
sender() ! PeerResponse.Response.StatusResponse(HydraStatusCodes.PEER_NOT_READY.getStatusResponse)
}
O problema ocorre quando a outra instância Peer
não está no estado correto para receber a mensagem. Se isso acontecer, eu gostaria de responder com o apropriado HydraStatusCode
. A abordagem mais normal para mim é de alguma forma perguntar em Peer
que estado ela está antes de encaminhar a mensagem e, se estiver no estado errado, retornar o StatusCode apropriado.
No entanto, para conseguir isso, eu teria que implementar QueryState
case em todos os lugares em que tenho Receive
comportamento. Isso parece desajeitado e bagunçado e, como um engenheiro habilidoso, não sinto que seja a maneira certa de fazer isso.
Gostaria de implementar isso de alguma forma na PeerState
classe como comportamento padrão que sempre é chamado para um QueryState
, ou de alguma forma permitir que eles PeerManager
chamem uma referência ao Peer
objeto diretamente em vez de ao ActorRef (mas sei que isso não é possível)
Existe alguma outra abordagem para isso ou a maneira mais bagunçada é provavelmente a melhor?