Slide 49 de 53
Atores distribuídos
Um serviço neste nó
type Note {
Say { text: Str }
Count
Total { count: Int }
}
proc tally(state: Int, msg: Note, envelope: hive.syslink.Envelope): Int {
if msg is Note.Say(text) {
echo text
return state
} else if msg is Note.Count {
hive.syslink.answer(envelope, Note.Total(state))
return state + 1
}
return state
}
proc main(): void {
address := hive.syslink.spawn(tally, 0)
hive.syslink.register(#Cache, address)
async address(Note.Say("oi")) // dispara e esquece
reply := address(Note.Count()) with timeout 250 // envia e espera, limitado
if reply is Result.Ok(msg) {
echo msg
}
}
Respondendo depois, e não respondendo
type Note {
Ask
Slowly
Ignore
Answer { text: Str }
}
proc desk(state: Int, note: Note, from: hive.syslink.Envelope): Int {
if note is Note.Ask {
hive.syslink.answer(from, Note.Answer("aqui está"))
} else if note is Note.Slowly {
async answerLate(from) // o envelope sai do turno
}
return state + 1 // Note.Ignore passa direto: sem resposta
}
// Recebeu um envelope de uma chamada que ninguém espera, então a resposta
// pode chegar quando isto aqui terminar.
proc answerLate(from: hive.syslink.Envelope): void {
hive.task.sleep(300)
hive.syslink.answer(from, Note.Answer("valeu a espera"))
}
proc main(): void {
desk := hive.syslink.spawn(desk, 0)
if desk(Note.Ask()) with timeout 500 is Result.Ok(now) { echo now }
if desk(Note.Slowly()) with timeout 1000 is Result.Ok(late) { echo late }
// Volta na hora, não no prazo.
if desk(Note.Ignore()) with timeout 5000 is Result.Error(err) { echo err }
}
O mesmo serviço, verdadeiramente distribuído
type Note {
Say { text: Str }
HowMany
Counted { seen: Int }
}
proc inbox(seen: Int, note: Note, from: hive.syslink.Envelope): Int {
if note is Note.Say(text) {
echo "ouvi: {text}"
return seen + 1
} else if note is Note.HowMany {
hive.syslink.answer(from, Note.Counted(seen))
return seen
}
return seen
}
// Rode este mesmo programa em duas máquinas: um nó é identificado por onde
// ele está, então nada nos dois processos difere além dos endereços digitados
// abaixo — 192.168.1.10:9100 e 192.168.1.11:9100, um de cada lado.
proc main(): void {
echo "O ip:port deste nó?"
me := hive.term.read()
echo "O ip:port do outro nó?"
peer := hive.term.read()
if hive.syslink.listen(me) is Result.Error(err) {
panic err
}
box := hive.syslink.spawn(inbox, 0)
hive.syslink.register(#Inbox, box)
mine := hive.syslink.at(#Inbox)
theirs := hive.syslink.on(peer, #Inbox)
async theirs(Note.Say("olá de {me}"))
hive.task.sleep(500)
echo "peers conectados: {len(hive.syslink.peers())}"
if theirs(Note.HowMany()) with timeout 4000 is Result.Ok(reply) {
echo "o outro nó respondeu: {reply}"
}
}
hive.syslink dá a um programa serviços endereçáveis, neste processo ou em outra máquina, alcançados pela mesma instrução de qualquer forma. spawn inicia um e register o publica sob um átomo; at alcança um serviço neste nó e on alcança um em outro, ambos sem nenhum I/O. O handler de um serviço é um fold sobre sua caixa de mensagens — proc(State, Message, Envelope): State — então não há mutex em lugar nenhum: o fold é o mutex, e answer dentro dele é como uma resposta adiada é enviada. self lê o próprio endereço de um handler, monitor pede para ser avisado se outro endereço parar, e stop termina um de vez. Uma falha dentro de um serviço mata apenas aquele serviço e deixa o resto do nó rodando.Esquecer a resposta em um ramo é o erro mais fácil de cometer em código de serviço, e esperar um timeout até o fim é uma forma miserável de ser avisado disso: aponta para a rede quando o problema é uma linha que falta. Então answer é um no-op quando quem enviou usou async e nada está esperando — um mesmo handler serve às duas formas sem se importar com qual foi — e uma requisição que um serviço trata sem nunca responder volta na hora como NoReply, nomeando o serviço, em vez de voltar no prazo. Você não escreve nada para ter isso.A menos que o envelope saia do turno. Se ele for para qualquer outro lugar — guardado no estado retornado, entregue a uma chamada que o handler não espera, passado a um proc seu — uma resposta pode genuinamente ainda estar a caminho, e o runtime continua esperando. Essa é a resposta adiada: entregue o envelope a uma chamada async e responda quando o trabalho terminar.listen abre este nó para outros em um endpoint — um ip:port — e node/peers dizem quem ele é e com quem mais está conectado atualmente. Dois nós não precisam de nada compartilhado além de uma chave de cluster, escrita em ~/.hive/syslink.key na primeira execução: copie esse arquivo para a segunda máquina, ou defina HIVE_SYSLINK_KEY da mesma forma nas duas, e toda conexão entre elas é TLS 1.3 de qualquer forma.