Slide 49 of 53
Distributed actors
A service on this node
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("hi")) // fire and forget
reply := address(Note.Count()) with timeout 250 // send and wait, bounded
if reply is Result.Ok(msg) {
echo msg
}
}
Answering later, and not answering at all
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("here you go"))
} else if note is Note.Slowly {
async answerLate(from) // the envelope leaves the turn
}
return state + 1 // Note.Ignore falls through: no answer
}
// Handed an envelope by a call nothing waits for, so the reply is allowed
// to arrive whenever this is done.
proc answerLate(from: hive.syslink.Envelope): void {
hive.task.sleep(300)
hive.syslink.answer(from, Note.Answer("worth the wait"))
}
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 }
// Comes back at once, not at the deadline.
if desk(Note.Ignore()) with timeout 5000 is Result.Error(err) { echo err }
}
The same service, truly distributed
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 "heard: {text}"
return seen + 1
} else if note is Note.HowMany {
hive.syslink.answer(from, Note.Counted(seen))
return seen
}
return seen
}
// Run this exact program on two machines: a node is identified by where it
// is, so nothing about the two processes differs but the addresses typed
// in below — 192.168.1.10:9100 and 192.168.1.11:9100, one each way round.
proc main(): void {
echo "This node's own ip:port?"
me := hive.term.read()
echo "The other node's ip:port?"
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("hello from {me}"))
hive.task.sleep(500)
echo "peers connected: {len(hive.syslink.peers())}"
if theirs(Note.HowMany()) with timeout 4000 is Result.Ok(reply) {
echo "the other node answered: {reply}"
}
}
hive.syslink gives a program addressable services, in this process or on another machine, reached by the identical statement either way. spawn starts one and register publishes it under an atom; at reaches a service on this node and on reaches one on another, both without any I/O. A service's handler is a fold over its mailbox — proc(State, Message, Envelope): State — so there is no mutex anywhere: the fold is the mutex, and answer inside it is how a deferred reply is sent. self reads a handler's own address, monitor asks to be told if another address stops, and stop ends one outright. A crash inside one service kills only that service and leaves the rest of the node running.Missing a reply on one branch is the easiest mistake there is to make in service code, and waiting out a timeout is a miserable way to be told about it: it points at the network when the problem is a missing line. So answer is a no-op when the sender used async and nothing is waiting — one handler serves both shapes without caring which it was — and a request a service handles without ever answering comes straight back as NoReply, naming the service, at once rather than at the deadline. You write nothing to get that.Unless the envelope leaves the turn. If it goes anywhere else — stored in the returned state, handed to a call the handler does not wait for, passed to a proc of your own — a reply may genuinely still be on its way, and the runtime keeps waiting. That is the deferred reply: hand the envelope to an async call and answer whenever the work is done.listen opens this node to others at an endpoint — an ip:port — and node/peers say who it is and who else it is currently connected to. Two nodes need nothing shared beyond a cluster key, written to ~/.hive/syslink.key on first run: copy that file to the second machine, or set HIVE_SYSLINK_KEY the same on both, and every connection between them is TLS 1.3 either way.