Fundo
Tenho um akka Actor
chamado Client
que gerencia http
e https
conecta a um servidor. O cliente tem muitos recursos, incluindo um ping
serviço e um tokenFetcher
serviço.
O client
representa uma 'conexão' entre um servidor e outro. Ele é projetado para permitir que um server
converse com outro.
O processo client
é o seguinte:
- Periodicamente
ping
o outroserver
para ver se está online - Se o outro
server
estiver online, façaauth
e ganhe um token - Se o token for válido, limpe todas as chamadas que foram solicitadas de nós
É com o passo 3 que estou tendo dificuldades. Gostaria de saber como implementaria isso com segurança entre threads (atores).
O que eu tentei:
Estou usando uma série Seq
de mensagens que o cliente armazenaria, como estas:
case class SendApiCall(apiCall: ApiCall, route: String, var sent: Boolean = false)
class Client(server: Server) extends Actor {
private var apiCalls: Seq[SendApiCall] = Seq.empty[SendApiCall]
...
override def receive: Receive = {
case sendApiCall@SendApiCall(_, _, _) =>
if (server.onlineStatus == OFFLINE) {
apiCalls = apiCalls.appended(sendApiCall)
}
else {
sendApiCall(sendApiCall)
}
case ServerOnline() => // <- this is send to us from the ping service when it first detects the server is online
apiCalls.iterator.foreach( apiCallRequest =>
if (!apiCallRequest.sent) {
sendApiCall(apiCallRequest)
apiCallRequest.sent = true
}
apiCallRequest
)
apiCalls = apiCalls.filterNot(apiCallRequest => apiCallRequest.sent)
}
}
No entanto, acredito apiCalls
que é um mutable
estado neste cenário? Gostaria de saber:
- Este tópico é seguro?
- Como eu tornaria isso seguro para threads, se não for?
Considerando que seu código está sendo executado dentro de um ator e você parece estar seguindo "as regras", ele parece seguro para threads para mim.
Os atores trabalham em termos de uma "caixa de correio", que pode receber mensagens de muitas fontes, possivelmente simultaneamente, mas só despachará as mensagens para a
receive
mensagem do seu ator sequencialmente . Então, embora areceive
função nem sempre possa ser executada no mesmo thread, você pode efetivamente considerar seu ator como single-threaded. Contanto que você não faça nada para fazer o código dentro da suareceive
função ser executado simultaneamente com ele mesmo, a sequencialidade do modelo ator/caixa de correio o torna seguro.Para ilustrar algo não seguro para threads que comprometeria sua segurança de threads, considere uma função que você poderia chamar para acionar alguma ação assíncrona com um retorno de chamada; se seu retorno de chamada fosse capaz de interagir diretamente com o estado interno do seu ator, isso não seria seguro para threads:
O exemplo acima não seria seguro para threads, porque a função de retorno de chamada assíncrona acionada por
SomeMessage
poderia ser executada simultaneamente com a lógica de recebimento paraSomeOtherMessage
, causando modificações conflitantes emmyInternalState
.Uma maneira possível de tornar o exemplo acima seguro é fazer com que o retorno de chamada assíncrono interaja com a caixa de correio do ator em vez de diretamente com seu estado interno:
No exemplo que você deu em sua postagem, não parece que você está fazendo nada para causar modificações simultâneas em seu estado interno, então ele é seguro para threads.
Os atores Akka são inerentemente thread-safe com relação ao seu estado interno: o
receive
método não pode ser invocado na mesma instância de ator simultaneamente. Como tal, sua abordagem funciona de uma perspectiva de thread-safety.