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.