docs(adr): propose the Luerl VM owning the state (ADR 0015) - #541
Merged
Merged
Conversation
Records the decision the asobi#536 spike exists to inform: stop copying the persistent Luerl state into a per-callback worker, and hold it in a process the bridge talks to instead. Proposed, not accepted, and nothing is implemented. Carries both measurement tables, the migration surface counted on main, and the price - a runaway callback would cost the bridge's Lua state rather than one tick, which zones can absorb through asobi_zone_snapshotter and matches cannot. Five rejected alternatives, including the Luerl trace-hook deadline and snapshot-before-call.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposes ADR 0015. Nothing is implemented and this PR changes no code - it
is one file under
docs/adr/, inProposedstatus, so the decision can beargued in one place before anyone writes the refactor.
Follows #538 (asobi#536, v0.94.1). That change made the per-eval heap budget
mean what it says and made the Luerl state size observable. It did not remove
the per-callback copy, and this ADR is about the copy.
The number the ADR turns on
Every bounded Lua callback runs in a process spawned per call, and the spawn
copies the whole persistent Luerl state into it. For a callback that does
nothing at all:
call/4(worker + copy)call/3(inline)Roughly 7 ms per MB. At the 400-690 MB #536 reported, that is three to five
seconds per callback before the script runs a line - which is the "the world
is empty" symptom on its own, with or without a heap kill.
The spike holds the state in a process the bridge talks to instead, so the
bridge passes small messages and opaque refs. Re-measured against this branch's
base:
Not faster in kind - flat. O(work) where the current path is O(state). It
wins at a state of no size too, because spawn-copy-monitor costs more than a
gen_serverround trip.What it would cost
A runaway callback would cost the bridge's Lua state rather than one tick,
because the only killable thing becomes the process holding it. Zones can
absorb that -
dump_zone_state/1already decodesgame_stateto a plain mapand
asobi_zone:maybe_restore_from_snapshot/1already rebuilds from the DBsnapshot on boot - but matches cannot, since
asobi_match_serverkeepsgame_statein the FSM and never persists it. That asymmetry is the partwanting a ruling.
Two things fall out for free if it is accepted: the per-zone ETS template table
exists only because a callback's
self()is an ephemeral worker, andhandle_input's sandbox exemption exists only because a copy per input framewas unaffordable. Both would be retirable. The
game.zone.spawndeadlockhazard would not go away.
Migration surface, counted on main: 51 direct
luerl:*call sites outside theloader, 97 references to
lua_statethreaded through bridge state maps, 31asobi_lua_loader:callsites.Rejected alternatives, recorded
Leaving it as a documented property; running callbacks inline and unguarded;
capping state size and refusing callbacks past it; bounding the eval with a
Luerl trace hook (it stores a
funin the state and fires per statement);snapshotting before each callback (the snapshot is the copy).
The spike
spike/lua-vm-processcarries the workingasobi_lua_vm_spikegen_server andthe bench that produced the second table. It is deliberately not part of this
PR: the bench asserts nothing and would add ~8 seconds to every CI eunit run.
Check it out to re-run the numbers:
What accepting this does and does not commit to
Accepting means the direction is agreed and the refactor can be scheduled - not
that it lands next. Rejecting means the copy stays a documented cost, #538's
telemetry is how you watch it, and #540's ceiling is how you survive it.