syndicate-nim/src/syndicate/actors.nim

748 lines
23 KiB
Nim
Raw Normal View History

2023-05-06 19:09:45 +00:00
# SPDX-FileCopyrightText: ☭ Emery Hemingway
2021-09-24 19:25:47 +00:00
# SPDX-License-Identifier: Unlicense
2024-03-08 12:01:20 +00:00
import std/[deques, hashes, monotimes, options, sets, tables, times]
import pkg/cps
import pkg/sys/ioqueue
2022-03-19 00:09:19 +00:00
import preserves
2024-03-11 14:40:42 +00:00
import ../syndicate/protocols/[protocol, sturdy, trace]
2021-09-24 19:25:47 +00:00
2023-07-13 13:15:13 +00:00
const tracing = defined(traceSyndicate)
when tracing:
import std/streams
from std/os import getEnv
2021-11-03 18:21:52 +00:00
export Handle
2021-09-24 19:25:47 +00:00
type
Oid = sturdy.Oid
2023-12-31 17:15:06 +00:00
Caveat = sturdy.Caveat
2023-05-18 10:20:44 +00:00
Attenuation = seq[Caveat]
2023-12-31 17:15:06 +00:00
Rewrite = sturdy.Rewrite
2021-09-24 19:25:47 +00:00
2023-07-13 13:15:13 +00:00
AssertionRef* = ref object
2023-12-31 17:15:06 +00:00
value*: Value
# if the Enity methods take a Value object then the generated
2023-07-13 13:15:13 +00:00
# C code has "redefinition of struct" problems when orc is enabled
2021-09-24 19:25:47 +00:00
Entity* = ref object of RootObj
2024-03-08 12:01:20 +00:00
facet*: Facet
2021-09-24 19:25:47 +00:00
oid*: Oid # oid is how Entities are identified over the wire
2024-01-07 22:11:59 +00:00
Cap* {.preservesEmbedded.} = ref object of EmbeddedObj
2021-09-24 19:25:47 +00:00
target*: Entity
2024-03-08 12:01:20 +00:00
relay*: Facet
# Entity has facet but a Cap is also scoped to a relay Facet
2021-09-24 19:25:47 +00:00
attenuation*: Attenuation
2023-07-24 15:13:36 +00:00
Ref* {.deprecated: "Ref was renamed to Cap".} = Cap
2021-09-24 19:25:47 +00:00
OutboundAssertion = ref object
handle: Handle
2023-07-24 15:13:36 +00:00
peer: Cap
2021-09-24 19:25:47 +00:00
established: bool
OutboundTable = Table[Handle, OutboundAssertion]
Actor* = ref object
2024-03-08 12:01:20 +00:00
next: Actor
2021-09-24 19:25:47 +00:00
name: string
handleAllocator: ref Handle
# a fresh actor gets a new ref Handle and
# all actors spawned from it get the same ref.
2021-09-24 19:25:47 +00:00
root: Facet
exitReason: ref Exception
exitHooks: seq[TurnAction]
2023-07-13 13:15:13 +00:00
id: ActorId
2024-03-08 12:01:20 +00:00
facetIdAllocator: uint
2023-10-26 12:13:03 +00:00
exiting, exited: bool
2021-09-24 19:25:47 +00:00
2024-03-08 15:40:07 +00:00
TurnAction* = proc (t: var Turn) {.closure.}
2021-09-24 19:25:47 +00:00
2024-03-08 12:01:20 +00:00
Turn* = ref object
facet: Facet # active facet that may change during a turn
work: Deque[tuple[facet: Facet, act: TurnAction]]
2024-03-14 13:14:51 +00:00
effects: Table[Actor, Turn]
when tracing:
2023-12-31 17:15:06 +00:00
desc: TurnDescription
2021-09-24 19:25:47 +00:00
Facet* = ref FacetObj
FacetObj = object
actor*: Actor
2023-07-13 13:15:13 +00:00
parent: Facet
2021-09-24 19:25:47 +00:00
children: HashSet[Facet]
outbound: OutboundTable
shutdownActions: seq[TurnAction]
inertCheckPreventers: int
2023-07-13 13:15:13 +00:00
id: FacetId
2021-09-24 19:25:47 +00:00
isAlive: bool
2024-03-08 12:01:20 +00:00
var turnQueue {.threadvar.}: Deque[Turn]
2023-07-13 13:15:13 +00:00
when tracing:
2024-03-08 12:01:20 +00:00
proc openTraceStream: FileStream =
let path = getEnv("SYNDICATE_TRACE_FILE")
case path
of "": stderr.writeLine "$SYNDICATE_TRACE_FILE unset"
of "-": result = newFileStream(stderr)
else: result = openFileStream(path, fmWrite)
2023-07-13 13:15:13 +00:00
2024-03-08 12:01:20 +00:00
let traceStream = openTraceStream()
var turnIdAllocator: uint
proc nextTurnId(): TurnId =
inc(turnIdAllocator)
turnIdAllocator.toPreserves
2023-07-13 13:15:13 +00:00
proc trace(actor: Actor; act: ActorActivation) =
2024-03-08 12:01:20 +00:00
if not traceStream.isNil:
2023-12-31 17:15:06 +00:00
var entry = TraceEntry(
2023-07-13 13:15:13 +00:00
timestamp: getTime().toUnixFloat(),
2023-12-31 17:15:06 +00:00
actor: initRecord("named", actor.name.toPreserves),
2023-07-13 13:15:13 +00:00
item: act)
2024-03-08 12:01:20 +00:00
traceStream.write(entry.toPreserves)
2023-07-13 13:15:13 +00:00
2023-12-31 17:15:06 +00:00
proc path(facet: Facet): seq[trace.FacetId] =
2023-07-13 13:15:13 +00:00
var f = facet
while not f.isNil:
2023-12-31 17:15:06 +00:00
result.add f.id.toPreserves
2023-07-13 13:15:13 +00:00
f = f.parent
2024-03-14 13:14:51 +00:00
proc initEnqueue(turn: Turn; cap: Cap): ActionDescription =
result = ActionDescription(orKind: ActionDescriptionKind.enqueue)
result.enqueue.event.target.actor = turn.facet.actor.id.toPreserves
result.enqueue.event.target.facet = turn.facet.id.toPreserves
result.enqueue.event.target.oid = cap.target.oid.toPreserves
proc toDequeue(act: sink ActionDescription): ActionDescription =
result = ActionDescription(orKind: ActionDescriptionKind.dequeue)
result.dequeue.event = move act.enqueue.event
proc toTraceTarget(cap: Cap): trace.Target =
assert not cap.target.isNil
assert not cap.target.facet.isNil
result.actor = cap.target.facet.actor.id
result.facet = cap.target.facet.id
result.oid = cap.target.oid.toPreserves
2024-03-08 15:40:07 +00:00
method publish*(e: Entity; turn: var Turn; v: AssertionRef; h: Handle) {.base.} = discard
method retract*(e: Entity; turn: var Turn; h: Handle) {.base.} = discard
method message*(e: Entity; turn: var Turn; v: AssertionRef) {.base.} = discard
method sync*(e: Entity; turn: var Turn; peer: Cap) {.base.} = discard
2024-03-14 13:14:51 +00:00
converter toActor(f: Facet): Actor = f.actor
converter toActor(t: Turn): Actor = t.facet.actor
converter toFacet(a: Actor): Facet = a.root
converter toFacet(t: Turn): Facet = t.facet
2021-09-24 19:25:47 +00:00
using
actor: Actor
facet: Facet
2024-03-08 15:40:07 +00:00
turn: var Turn
2021-09-24 19:25:47 +00:00
action: TurnAction
proc labels(f: Facet): string =
2024-03-14 13:14:51 +00:00
assert not f.isNil
assert not f.actor.isNil
result.add f.actor.name
2021-09-24 19:25:47 +00:00
proc catLabels(f: Facet; labels: var string) =
2023-07-13 13:15:13 +00:00
if not f.parent.isNil:
catLabels(f.parent, labels)
2021-09-24 19:25:47 +00:00
labels.add ':'
2023-07-13 13:15:13 +00:00
labels.add $f.id
2021-09-24 19:25:47 +00:00
catLabels(f, result)
2024-03-12 12:32:50 +00:00
proc `$`*(f: Facet): string =
"<Facet:" & f.labels & ">"
2024-03-14 13:14:51 +00:00
proc `$`*(actor: Actor): string =
"<Actor:" & actor.name & ">" # TODO: ambigous
2024-03-11 14:40:42 +00:00
when tracing:
2021-09-24 19:25:47 +00:00
2024-03-11 14:40:42 +00:00
proc `$`*(r: Cap): string =
"<Ref:" & r.relay.labels & ">"
2021-09-24 19:25:47 +00:00
2024-03-11 14:40:42 +00:00
proc `$`*(t: Turn): string =
"<Turn:" & $t.desc.id & ">"
2024-03-08 12:01:20 +00:00
2024-03-08 16:11:02 +00:00
proc attenuate*(r: Cap; a: Attenuation): Cap =
2021-09-24 19:25:47 +00:00
if a.len == 0: result = r
2023-07-24 15:13:36 +00:00
else: result = Cap(
2021-09-24 19:25:47 +00:00
target: r.target,
2024-03-08 12:01:20 +00:00
relay: r.relay,
2021-09-24 19:25:47 +00:00
attenuation: a & r.attenuation)
2024-03-14 13:14:51 +00:00
proc hash*(actor): Hash =
actor.unsafeAddr.hash
2021-09-24 19:25:47 +00:00
proc hash*(facet): Hash =
2024-03-14 13:14:51 +00:00
facet.unsafeAddr.hash
2021-09-24 19:25:47 +00:00
2023-07-24 15:13:36 +00:00
proc hash*(r: Cap): Hash = !$(r.relay.hash !& r.target.unsafeAddr.hash)
2021-09-24 19:25:47 +00:00
2024-03-08 15:40:07 +00:00
proc actor*(turn): Actor = turn.facet.actor
2021-09-24 19:25:47 +00:00
proc nextHandle(facet: Facet): Handle =
result = succ(facet.actor.handleAllocator[])
facet.actor.handleAllocator[] = result
2021-09-24 19:25:47 +00:00
2024-03-12 12:32:50 +00:00
template recallFacet(turn: var Turn; body: untyped): untyped =
let facet = turn.facet
block:
body
assert facet.actor == turn.facet.actor
turn.facet = facet
2024-03-08 15:40:07 +00:00
proc queueWork*(turn: var Turn; facet: Facet; act: TurnAction) =
2024-03-08 12:01:20 +00:00
assert not facet.isNil
turn.work.addLast((facet, act,))
2022-04-23 21:05:48 +00:00
2024-03-08 12:01:20 +00:00
proc queueTurn*(facet: Facet; act: TurnAction) =
var turn = Turn(facet: facet)
assert not facet.isNil
turn.work.addLast((facet, act,))
when tracing:
turn.desc.id = nextTurnId()
turnQueue.addLast(turn)
2024-03-08 15:40:07 +00:00
proc queueTurn*(prev: var Turn; facet: Facet; act: TurnAction) =
2024-03-08 12:01:20 +00:00
var next = Turn(facet: facet)
assert not facet.isNil
next.work.addLast((facet, act,))
when tracing:
next.desc.id = nextTurnId()
next.desc.cause = TurnCause(orKind: TurnCauseKind.turn)
next.desc.cause.turn.id = prev.desc.id
turnQueue.addLast(next)
proc run*(facet: Facet; action: TurnAction) = queueTurn(facet, action)
## Alias to queueTurn_.
proc facet*(turn: Turn): Facet = turn.facet
proc queueEffect*(turn: var Turn; target: Facet; act: TurnAction) =
2024-03-14 13:14:51 +00:00
let fremd = target.actor
if fremd == turn.facet.actor:
turn.work.addLast((target, act,))
else:
2024-03-14 13:14:51 +00:00
var fremdTurn = turn.effects.getOrDefault(fremd)
if fremdTurn.isNil:
fremdTurn = Turn(facet: target)
turn.effects[fremd] = fremdTurn
when tracing:
fremdTurn.desc.id = nextTurnId()
fremdTurn.desc.cause = TurnCause(orKind: TurnCauseKind.turn)
fremdTurn.desc.cause.turn.id = turn.desc.id
fremdTurn.work.addLast((target, act,))
2021-09-24 19:25:47 +00:00
2023-12-31 17:15:06 +00:00
type Bindings = Table[Value, Value]
2021-09-24 19:25:47 +00:00
2024-01-01 18:18:30 +00:00
proc match(bindings: var Bindings; p: Pattern; v: Value): bool =
2022-03-07 23:14:57 +00:00
case p.orKind
of PatternKind.Pdiscard: result = true
of PatternKind.Patom:
result = case p.patom
of PAtom.Boolean: v.isBoolean
2024-03-01 14:01:42 +00:00
of PAtom.Double: v.isFloat
2022-03-07 23:14:57 +00:00
of PAtom.Signedinteger: v.isInteger
of PAtom.String: v.isString
of PAtom.Bytestring: v.isByteString
of PAtom.Symbol: v.isSymbol
of PatternKind.Pembedded:
result = v.isEmbedded
of PatternKind.Pbind:
if match(bindings, p.pbind.pattern, v):
2023-12-31 17:15:06 +00:00
bindings[p.pbind.pattern.toPreserves] = v
2022-03-07 23:14:57 +00:00
result = true
of PatternKind.Pand:
for pp in p.pand.patterns:
result = match(bindings, pp, v)
if not result: break
of PatternKind.Pnot:
var b: Bindings
result = not match(b, p.pnot.pattern, v)
of PatternKind.Lit:
result = p.lit.value == v
of PatternKind.PCompound:
case p.pcompound.orKind
of PCompoundKind.rec:
if v.isRecord and
p.pcompound.rec.label == v.label and
p.pcompound.rec.fields.len == v.arity:
result = true
for i, pp in p.pcompound.rec.fields:
if not match(bindings, pp, v[i]):
result = false
break
of PCompoundKind.arr:
if v.isSequence and p.pcompound.arr.items.len == v.sequence.len:
result = true
for i, pp in p.pcompound.arr.items:
if not match(bindings, pp, v[i]):
result = false
break
of PCompoundKind.dict:
if v.isDictionary:
2021-09-24 19:25:47 +00:00
result = true
2022-03-07 23:14:57 +00:00
for key, pp in p.pcompound.dict.entries:
let vv = step(v, key)
if vv.isNone or not match(bindings, pp, get vv):
2022-03-07 23:14:57 +00:00
result = true
break
2024-01-01 18:18:30 +00:00
proc match(p: Pattern; v: Value): Option[Bindings] =
2021-09-24 19:25:47 +00:00
var b: Bindings
2022-03-07 23:14:57 +00:00
if match(b, p, v):
result = some b
2021-09-24 19:25:47 +00:00
2024-01-01 18:18:30 +00:00
proc instantiate(t: Template; bindings: Bindings): Value =
2022-03-07 23:14:57 +00:00
case t.orKind
of TemplateKind.Tattenuate:
let v = instantiate(t.tattenuate.template, bindings)
2024-01-01 18:18:30 +00:00
let cap = v.unembed(Cap)
if cap.isNone:
2022-03-07 23:14:57 +00:00
raise newException(ValueError, "Attempt to attenuate non-capability")
2024-01-01 18:18:30 +00:00
result = attenuate(get cap, t.tattenuate.attenuation).embed
2022-03-07 23:14:57 +00:00
of TemplateKind.TRef:
2023-05-18 10:20:44 +00:00
let n = $t.tref.binding.int
2023-12-31 17:15:06 +00:00
try: result = bindings[n.toPreserves]
2022-03-07 23:14:57 +00:00
except KeyError:
raise newException(ValueError, "unbound reference: " & n)
of TemplateKind.Lit:
result = t.lit.value
of TemplateKind.Tcompound:
case t.tcompound.orKind
of TCompoundKind.rec:
result = initRecord(t.tcompound.rec.label, t.tcompound.rec.fields.len)
for i, tt in t.tcompound.rec.fields:
result[i] = instantiate(tt, bindings)
of TCompoundKind.arr:
2023-12-31 17:15:06 +00:00
result = initSequence(t.tcompound.arr.items.len)
2022-03-07 23:14:57 +00:00
for i, tt in t.tcompound.arr.items:
result[i] = instantiate(tt, bindings)
of TCompoundKind.dict:
2023-12-31 17:15:06 +00:00
result = initDictionary()
2022-03-07 23:14:57 +00:00
for key, tt in t.tcompound.dict.entries:
result[key] = instantiate(tt, bindings)
2021-09-24 19:25:47 +00:00
2024-01-01 18:18:30 +00:00
proc rewrite(r: Rewrite; v: Value): Value =
2021-09-24 19:25:47 +00:00
let bindings = match(r.pattern, v)
if bindings.isSome:
result = instantiate(r.template, get bindings)
2024-01-01 18:18:30 +00:00
proc examineAlternatives(cav: Caveat; v: Value): Value =
2021-09-24 19:25:47 +00:00
case cav.orKind
2023-05-18 10:20:44 +00:00
of CaveatKind.Rewrite:
2021-09-24 19:25:47 +00:00
result = rewrite(cav.rewrite, v)
2023-05-18 10:20:44 +00:00
of CaveatKind.Alts:
2021-09-24 19:25:47 +00:00
for r in cav.alts.alternatives:
result = rewrite(r, v)
if not result.isFalse: break
2023-05-18 10:20:44 +00:00
of CaveatKind.Reject: discard
of CaveatKind.unknown: discard
2021-09-24 19:25:47 +00:00
2024-01-01 18:18:30 +00:00
proc runRewrites*(a: Attenuation; v: Value): Value =
2021-09-24 19:25:47 +00:00
result = v
for stage in a:
result = examineAlternatives(stage, result)
if result.isFalse: break
2024-03-08 15:40:07 +00:00
proc publish(turn: var Turn; cap: Cap; v: Value; h: Handle) =
2024-03-08 12:01:20 +00:00
var a = runRewrites(cap.attenuation, v)
2021-09-24 19:25:47 +00:00
if not a.isFalse:
2024-03-08 12:01:20 +00:00
let e = OutboundAssertion(handle: h, peer: cap)
turn.facet.outbound[h] = e
2024-03-14 13:14:51 +00:00
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.enqueue)
act.enqueue.event.target.actor = turn.facet.actor.id.toPreserves
act.enqueue.event.target.facet = turn.facet.id.toPreserves
act.enqueue.event.target.oid = cap.target.oid.toPreserves
act.enqueue.event.detail = trace.TurnEvent(orKind: TurnEventKind.assert)
act.enqueue.event.detail.assert.assertion.value.value =
mapEmbeds(v) do (cap: Value) -> Value: discard
act.enqueue.event.detail.assert.handle = h
turn.desc.actions.add act
2024-03-08 15:40:07 +00:00
queueEffect(turn, cap.relay) do (turn: var Turn):
2021-09-24 19:25:47 +00:00
e.established = true
2024-03-14 13:14:51 +00:00
when tracing:
turn.desc.actions.add act.toDequeue
2024-03-08 12:01:20 +00:00
publish(cap.target, turn, AssertionRef(value: a), e.handle)
2023-07-13 13:15:13 +00:00
2024-03-08 15:40:07 +00:00
proc publish*(turn: var Turn; r: Cap; a: Value): Handle {.discardable.} =
result = turn.facet.nextHandle()
2021-09-24 19:25:47 +00:00
publish(turn, r, a, result)
2024-03-08 15:40:07 +00:00
proc publish*[T](turn: var Turn; r: Cap; a: T): Handle {.discardable.} =
2023-12-31 17:15:06 +00:00
publish(turn, r, a.toPreserves)
2021-09-24 19:25:47 +00:00
2024-03-08 15:40:07 +00:00
proc retract(turn: var Turn; e: OutboundAssertion) =
2024-03-14 13:14:51 +00:00
when tracing:
var act = initEnqueue(turn, e.peer)
act.enqueue.event.detail = trace.TurnEvent(orKind: TurnEventKind.retract)
act.enqueue.event.detail.retract.handle = e.handle
turn.desc.actions.add act
2024-03-08 15:40:07 +00:00
queueEffect(turn, e.peer.relay) do (turn: var Turn):
2024-03-14 13:14:51 +00:00
when tracing:
turn.desc.actions.add act.toDequeue
2021-09-24 19:25:47 +00:00
if e.established:
e.established = false
e.peer.target.retract(turn, e.handle)
2024-03-08 15:40:07 +00:00
proc retract*(turn: var Turn; h: Handle) =
2021-09-24 19:25:47 +00:00
var e: OutboundAssertion
if turn.facet.outbound.pop(h, e):
2021-09-24 19:25:47 +00:00
turn.retract(e)
2024-03-08 15:40:07 +00:00
proc message*(turn: var Turn; r: Cap; v: Value) =
var a = runRewrites(r.attenuation, v)
2021-09-24 19:25:47 +00:00
if not a.isFalse:
2024-03-14 13:14:51 +00:00
when tracing:
var act = initEnqueue(turn, r)
act.enqueue.event.detail = trace.TurnEvent(orKind: TurnEventKind.message)
act.enqueue.event.detail.message.body.value.value =
mapEmbeds(a) do (cap: Value) -> Value: discard
turn.desc.actions.add act
2024-03-08 15:40:07 +00:00
queueEffect(turn, r.relay) do (turn: var Turn):
2024-03-14 13:14:51 +00:00
when tracing:
turn.desc.actions.add act.toDequeue
r.target.message(turn, AssertionRef(value: a))
2021-09-24 19:25:47 +00:00
proc message*[T](turn: var Turn; r: Cap; v: T) =
message(turn, r, v.toPreserves)
2021-10-28 16:57:09 +00:00
2024-03-08 15:40:07 +00:00
proc sync*(turn: var Turn; r, peer: Cap) =
2024-03-14 13:14:51 +00:00
when tracing:
var act = initEnqueue(turn, peer)
act.enqueue.event.detail = trace.TurnEvent(orKind: TurnEventKind.sync)
act.enqueue.event.detail.sync.peer = peer.toTraceTarget
turn.desc.actions.add act
2024-03-08 15:40:07 +00:00
queueEffect(turn, r.relay) do (turn: var Turn):
2024-03-14 13:14:51 +00:00
when tracing:
turn.desc.actions.add act.toDequeue
r.target.sync(turn, peer)
2021-09-24 19:25:47 +00:00
2024-03-08 15:40:07 +00:00
proc replace*[T](turn: var Turn; cap: Cap; h: Handle; v: T): Handle =
2023-07-24 15:13:36 +00:00
result = publish(turn, cap, v)
if h != default(Handle):
retract(turn, h)
2021-09-24 19:25:47 +00:00
2024-03-08 15:40:07 +00:00
proc replace*[T](turn: var Turn; cap: Cap; h: var Handle; v: T): Handle {.discardable.} =
2022-05-21 18:21:02 +00:00
var old = h
2023-07-24 15:13:36 +00:00
h = publish(turn, cap, v)
if old != default(Handle):
retract(turn, old)
2022-05-21 18:21:02 +00:00
h
2024-03-08 15:40:07 +00:00
proc stop*(turn: var Turn)
2021-09-24 19:25:47 +00:00
2023-07-13 13:15:13 +00:00
proc newFacet(actor; parent: Facet; initialAssertions: OutboundTable): Facet =
2024-03-08 12:01:20 +00:00
inc actor.facetIdAllocator
2021-09-24 19:25:47 +00:00
result = Facet(
2024-03-08 12:01:20 +00:00
id: actor.facetIdAllocator.toPreserves,
2021-09-24 19:25:47 +00:00
actor: actor,
parent: parent,
outbound: initialAssertions,
isAlive: true)
2023-07-13 13:15:13 +00:00
if not parent.isNil: parent.children.incl result
2021-09-24 19:25:47 +00:00
2023-07-13 13:15:13 +00:00
proc newFacet(actor; parent: Facet): Facet =
2021-09-24 19:25:47 +00:00
var initialAssertions: OutboundTable
newFacet(actor, parent, initialAssertions)
proc isInert(facet): bool =
2024-03-08 12:01:20 +00:00
let
noKids = facet.children.len == 0
noOutboundHandles = facet.outbound.len == 0
isRootFacet = facet.parent.isNil
noInertCheckPreventers = facet.inertCheckPreventers == 0
2024-03-12 12:32:50 +00:00
result = noKids and (noOutboundHandles or isRootFacet) and noInertCheckPreventers
2021-09-24 19:25:47 +00:00
2024-03-12 12:32:50 +00:00
proc preventInertCheck*(turn: Turn) =
inc turn.facet.inertCheckPreventers
2021-09-24 19:25:47 +00:00
2024-03-12 12:32:50 +00:00
proc terminateActor(turn; reason: ref Exception)
2021-09-24 19:25:47 +00:00
2024-03-14 13:14:51 +00:00
proc terminateFacetOrderly(turn: var Turn) =
let facet = turn.facet
2021-09-24 19:25:47 +00:00
if facet.isAlive:
facet.isAlive = false
2024-03-14 13:14:51 +00:00
var i = 0
while i < facet.shutdownActions.len:
facet.shutdownActions[i](turn)
inc i
setLen facet.shutdownActions, 0
for e in facet.outbound.values:
retract(turn, e)
clear facet.outbound
2021-09-24 19:25:47 +00:00
2024-03-12 12:32:50 +00:00
proc inertCheck(turn: var Turn) =
if (not turn.facet.parent.isNil and
(not turn.facet.parent.isAlive)) or
turn.facet.isInert:
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.facetStop)
2024-03-14 13:14:51 +00:00
act.facetstop.path = turn.facet.path
2024-03-12 12:32:50 +00:00
act.facetstop.reason = FacetStopReason.inert
turn.desc.actions.add act
stop(turn)
2024-03-14 13:14:51 +00:00
proc terminateFacet(turn: var Turn) =
let facet = turn.facet
for child in facet.children:
queueWork(turn, child, terminateFacetOrderly)
# terminate all children
facet.children.clear()
# detach all children
queueWork(turn, facet, terminateFacetOrderly)
# self-termination
2021-09-24 19:25:47 +00:00
proc stopIfInertAfter(action: TurnAction): TurnAction =
2024-03-12 12:32:50 +00:00
proc work(turn: var Turn) =
queueWork(turn, turn.facet, action)
queueEffect(turn, turn.facet, inertCheck)
work
2021-09-24 19:25:47 +00:00
2024-03-08 15:40:07 +00:00
proc newFacet(turn: var Turn): Facet = newFacet(turn.facet.actor, turn.facet)
2023-11-08 15:06:49 +00:00
2024-03-15 10:19:43 +00:00
proc inFacet*(turn: var Turn; bootProc: TurnAction): Facet =
result = newFacet(turn)
recallFacet turn:
turn.facet = result
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.facetstart)
act.facetstart.path.add result.path
turn.desc.actions.add act
stopIfInertAfter(bootProc)(turn)
2024-03-08 12:01:20 +00:00
proc newActor(name: string; parent: Facet): Actor =
2021-09-24 19:25:47 +00:00
result = Actor(
name: name,
2024-03-08 12:01:20 +00:00
id: name.toPreserves,
2023-07-22 10:32:52 +00:00
)
2024-03-08 12:01:20 +00:00
if parent.isNil:
new result.handleAllocator
else:
result.handleAllocator = parent.actor.handleAllocator
result.root = newFacet(result, parent)
2023-07-13 13:15:13 +00:00
when tracing:
2023-12-31 17:15:06 +00:00
var act = ActorActivation(orKind: ActorActivationKind.start)
act.start.actorName = Name(orKind: NameKind.named)
act.start.actorName.named.name = name.toPreserves
2023-07-13 13:15:13 +00:00
trace(result, act)
2024-03-08 12:01:20 +00:00
proc run(actor: Actor; bootProc: TurnAction; initialAssertions: OutboundTable) =
queueTurn(newFacet(actor, actor.root, initialAssertions), stopIfInertAfter(bootProc))
2021-09-24 19:25:47 +00:00
2024-03-08 12:01:20 +00:00
proc bootActor*(name: string; bootProc: TurnAction): Actor {.discardable.} =
## Boot a top-level actor.
result = newActor(name, nil)
new result.handleAllocator
var turn = Turn(facet: result.root)
assert not result.root.isNil
turn.work.addLast((result.root, bootProc,))
2023-07-13 13:15:13 +00:00
when tracing:
2024-03-08 12:01:20 +00:00
turn.desc.id = nextTurnId()
turn.desc.cause = TurnCause(orKind: TurnCauseKind.external)
turn.desc.cause.external.description = "bootActor".toPreserves
turnQueue.addLast turn
2024-03-08 15:40:07 +00:00
proc spawnActor*(name: string; turn: var Turn; bootProc: TurnAction; initialAssertions = initHashSet[Handle]()): Actor {.discardable.} =
2024-03-08 12:01:20 +00:00
let actor = newActor(name, turn.facet)
2024-03-08 15:40:07 +00:00
queueEffect(turn, actor.root) do (turn: var Turn):
2021-09-24 19:25:47 +00:00
var newOutBound: Table[Handle, OutboundAssertion]
for key in initialAssertions:
discard turn.facet.outbound.pop(key, newOutbound[key])
2023-07-13 13:15:13 +00:00
when tracing:
2023-12-31 17:15:06 +00:00
var act = ActionDescription(orKind: ActionDescriptionKind.spawn)
act.spawn.id = actor.id.toPreserves
2023-07-13 13:15:13 +00:00
turn.desc.actions.add act
run(actor, bootProc, newOutBound)
2023-07-22 10:32:52 +00:00
actor
2021-09-24 19:25:47 +00:00
2024-03-08 15:40:07 +00:00
proc spawn*(name: string; turn: var Turn; bootProc: TurnAction; initialAssertions = initHashSet[Handle]()): Actor {.discardable.} =
2024-03-04 18:20:59 +00:00
spawnActor(name, turn, bootProc, initialAssertions)
2024-03-08 12:01:20 +00:00
type StopOnRetract = ref object of Entity
2024-03-08 15:40:07 +00:00
method retract*(e: StopOnRetract; turn: var Turn; h: Handle) =
2024-03-08 12:01:20 +00:00
stop(turn)
proc halfLink(facet, other: Facet) =
let h = facet.nextHandle()
facet.outbound[h] = OutboundAssertion(
handle: h,
peer: Cap(relay: other, target: StopOnRetract(facet: facet)),
established: true,
)
2024-03-08 15:40:07 +00:00
proc spawnLink*(name: string; turn: var Turn; bootProc: TurnAction; initialAssertions = initHashSet[Handle]()): Actor {.discardable.} =
2024-03-08 12:01:20 +00:00
result = spawnActor(name, turn, bootProc, initialAssertions)
halfLink(turn.facet, result.root)
halfLink(result.root, turn.facet)
2024-03-08 16:11:02 +00:00
var inertActor {.threadvar.}: Actor
2023-07-24 15:13:36 +00:00
proc newInertCap*(): Cap =
2024-03-08 16:11:02 +00:00
if inertActor.isNil:
inertActor = bootActor("inert") do (turn: var Turn): turn.stop()
Cap(relay: inertActor.root)
2021-09-24 19:25:47 +00:00
proc atExit*(actor; action) = actor.exitHooks.add action
2024-03-12 12:32:50 +00:00
proc terminateActor(turn; reason: ref Exception) =
let actor = turn.actor
2021-09-24 19:25:47 +00:00
if not actor.exiting:
2023-10-26 12:13:03 +00:00
actor.exiting = true
actor.exitReason = reason
2023-07-13 13:15:13 +00:00
when tracing:
2023-12-31 17:15:06 +00:00
var act = ActorActivation(orKind: ActorActivationKind.stop)
2023-07-13 13:15:13 +00:00
if not reason.isNil:
act.stop.status = ExitStatus(orKind: ExitStatusKind.Error)
act.stop.status.error.message = reason.msg
trace(actor, act)
2024-03-12 12:32:50 +00:00
while actor.exitHooks.len > 0:
var hook = actor.exitHooks.pop()
try: hook(turn)
except CatchableError as err:
if reason.isNil:
terminateActor(turn, err)
return
2024-03-08 15:40:07 +00:00
proc finish(turn: var Turn) =
2024-03-08 12:01:20 +00:00
assert not actor.root.isNil, actor.name
2024-03-14 13:14:51 +00:00
terminateFacet(turn)
actor.root = nil
2023-10-26 12:13:03 +00:00
actor.exited = true
2024-03-08 12:01:20 +00:00
queueTurn(actor.root, finish)
2021-09-24 19:25:47 +00:00
2024-03-14 13:14:51 +00:00
proc terminateFacet*(facet; e: ref Exception) =
2024-03-08 15:40:07 +00:00
run(facet.actor.root) do (turn: var Turn):
2024-03-12 12:32:50 +00:00
terminateActor(turn, e)
2024-03-08 12:01:20 +00:00
2024-03-08 15:40:07 +00:00
proc stop*(turn: var Turn, facet: Facet) =
2024-03-14 13:14:51 +00:00
queueEffect(turn, facet) do (turn: var Turn):
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.facetStop)
act.facetstop.path = facet.path
act.facetstop.reason = FacetStopReason.explicitAction
turn.desc.actions.add act
terminateFacet(turn)
2024-03-08 12:01:20 +00:00
2024-03-08 15:40:07 +00:00
proc stop*(turn: var Turn) =
stop(turn, turn.facet)
2021-09-24 19:25:47 +00:00
proc onStop*(facet: Facet; act: TurnAction) =
2024-03-08 15:40:07 +00:00
## Add a `proc (turn: var Turn)` action to `facet` to be called as it stops.
add(facet.shutdownActions, act)
2024-03-08 15:40:07 +00:00
proc onStop*(turn: var Turn; act: TurnAction) =
2024-03-08 12:01:20 +00:00
onStop(turn.facet, act)
2021-09-24 19:25:47 +00:00
2024-03-14 13:14:51 +00:00
proc isAlive(actor): bool =
not(actor.exited or actor.exiting)
2024-03-04 18:20:59 +00:00
proc stop*(actor: Actor) =
2024-03-14 13:14:51 +00:00
if actor.isAlive:
queueTurn(actor.root) do (turn: var Turn):
assert(not turn.facet.isNil)
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.facetStop)
act.facetstop.path = turn.facet.path
act.facetstop.reason = FacetStopReason.actorStopping
turn.desc.actions.add act
stop(turn, turn.facet)
2024-03-04 18:20:59 +00:00
2024-03-08 12:01:20 +00:00
proc stopActor*(facet: Facet) =
stop(facet.actor)
2024-03-08 15:40:07 +00:00
proc stopActor*(turn: var Turn) =
2024-03-08 12:01:20 +00:00
stop(turn, turn.facet.actor.root)
2024-03-08 15:40:07 +00:00
proc freshen*(turn: var Turn, act: TurnAction) {.deprecated.} =
run(turn.facet, act)
2021-09-24 19:25:47 +00:00
2024-03-14 13:14:51 +00:00
proc newCap*(relay: Facet; entity: Entity): Cap =
## Create a new capability for `entity` via `relay`.
# An Entity has an owning facet and a Cap does as well?
if entity.facet.isNil: entity.facet = relay
Cap(relay: relay, target: entity)
2021-09-24 19:25:47 +00:00
2023-07-24 15:13:36 +00:00
proc newCap*(turn; e: Entity): Cap =
2024-03-14 13:14:51 +00:00
newCap(turn.facet, e)
2024-01-18 22:03:32 +00:00
proc newCap*(e: Entity; turn): Cap =
2024-03-14 13:14:51 +00:00
newCap(turn.facet, e)
2024-01-18 22:03:32 +00:00
2024-01-14 10:09:49 +00:00
type SyncContinuation {.final.} = ref object of Entity
action: TurnAction
2024-03-08 15:40:07 +00:00
method message(entity: SyncContinuation; turn: var Turn; v: AssertionRef) =
2024-01-14 10:09:49 +00:00
entity.action(turn)
2024-03-08 15:40:07 +00:00
proc sync*(turn: var Turn; refer: Cap; act: TurnAction) =
2024-01-14 10:09:49 +00:00
sync(turn, refer, newCap(turn, SyncContinuation(action: act)))
2021-09-24 19:25:47 +00:00
2023-10-26 12:13:03 +00:00
proc running*(actor): bool =
result = not actor.exited
if not (result or actor.exitReason.isNil):
raise actor.exitReason
2024-03-08 12:01:20 +00:00
2024-03-08 15:40:07 +00:00
proc run(turn: var Turn) =
2024-03-08 12:01:20 +00:00
while turn.work.len > 0:
var (facet, act) = turn.work.popFirst()
assert not act.isNil
turn.facet = facet
act(turn)
when tracing:
var act = ActorActivation(orKind: ActorActivationKind.turn)
act.turn = move turn.desc
trace(turn.facet.actor, act)
# TODO: catch exceptions here
2024-03-14 13:14:51 +00:00
for eff in turn.effects.mvalues:
assert not eff.facet.isNil
turnQueue.addLast(move eff)
2024-03-14 13:14:51 +00:00
turn.facet = nil # invalidate the turn
2024-03-08 12:01:20 +00:00
proc run* =
## Run actors to completion
var ready: seq[Continuation]
while true:
while turnQueue.len > 0:
var turn = turnQueue.popFirst()
# TODO: check if actor is still valid
2024-03-12 12:32:50 +00:00
try: run(turn)
except CatchableError as err:
stderr.writeLine("actor ", turn.actor.name, " threw an error during a turn")
terminateActor(turn, err)
2024-03-08 12:01:20 +00:00
ioqueue.poll(ready)
if ready.len == 0: break
while ready.len > 0:
2024-03-12 12:32:50 +00:00
try:
discard trampoline:
ready.pop()
except CatchableError as err:
stderr.writeLine "ioqueue continuation threw an error"
raise err
2024-03-12 12:33:23 +00:00
type FacetGuard* = object
facet: Facet
proc initGuard*(f: Facet): FacetGuard =
result.facet = f
inc result.facet.inertCheckPreventers
proc disarm*(g: var FacetGuard) =
if not g.facet.isNil:
assert g.facet.inertCheckPreventers > 0
dec g.facet.inertCheckPreventers
g.facet = nil
2024-03-14 13:14:51 +00:00
proc `=destroy`*(g: FacetGuard) =
if not g.facet.isNil:
dec g.facet.inertCheckPreventers
2024-03-12 12:33:23 +00:00
proc `=copy`*(dst: var FacetGuard, src: FacetGuard) =
dst.facet = src.facet
inc dst.facet.inertCheckPreventers