Slide 36 of 37

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
	}
}
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.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.