Skip to content

feat: drive Lua apps with an init/update/fini lifecycle in the flua host - #33

Merged
esrrhs merged 1 commit into
masterfrom
feat/app-lifecycle-host
Oct 16, 2026
Merged

esrrhs merged 1 commit into
masterfrom
feat/app-lifecycle-host

Conversation

@esrrhs

@esrrhs esrrhs commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Why

flua took a single entry function (default main) and left the frame loop to the script.
fakelua has no GC and its frame arena is only reset when a top-level Call starts (reentrant
count back to zero), so a while true loop inside one Call keeps the arena alive and
per-request allocations pile up without bound -- the majiang server leaked 2.3GB in 85 seconds
that way, and every app had to hand-roll its own loop.

What changes

Entry points are fixed and mandatory, always in package main; there are no flags to rename
them, so every app is written the same way:

package "main"

function init() ... end      -- called once at startup
function update() ... end    -- called every frame
function fini() ... end      -- called once before exit
  • each phase is its own top-level Call, so the frame arena is reclaimed on every tick; the
    host adds no pacing (a script that wants a fixed cadence calls os.sleep(ms) itself)
  • missing entry points are reported up front with the fixed layout spelled out, instead of
    failing later with "function not found"
  • graceful shutdown, both forms run fini first: a non-zero return from update, or
    flua.set_exit(code) from Lua. os.exit stays a hard exit that skips fini
  • flua deliberately installs no signal handler -- reacting to SIGINT / SIGTERM is the
    application's own decision
  • --once runs init + fini without the update loop (one-shot script / self-check)
  • the script argument may be a directory: every .lua inside is compiled in filename order
  • three additive wrappers (GetNativeIntArg / NativeNil / HasEntryFunction) let a host
    write native callbacks and probe for entries without touching inter::, which stays an
    internal detail of the interpreter and JIT

Test

cmd/test.lua asserts the contract from inside the script -- call counts and order,
cross-frame state surviving only in container.map, flua.set_exit semantics (the rest of
the current frame still runs), pcall/error across frame boundaries, exit code 0. It runs in
the Linux, macOS and Windows CI jobs, replacing the bare flua invocation that only proved
the binary starts.

Expected stdout:

INIT UPDATE-1 UPDATE-2 UPDATE-3 SETEXIT AFTER-SET-EXIT FINI

Compatibility

  • library behaviour is unchanged; include/fakelua.h only gains the three helpers, so hosts
    that embed fakelua keep working
  • CLI: --entry / --repeat are gone (nothing depended on them), --init/--update/--fini
    are replaced by the fixed entries plus --once
  • the coverage job now runs an init-only script under the fixed paradigm instead of
    --repeat=2 plus function main()

@esrrhs
esrrhs force-pushed the feat/app-lifecycle-host branch 13 times, most recently from 189dc77 to 92b11ae Compare October 16, 2026 11:59
…lua host

flua used to take one entry function (default `main`) and leave the frame loop
to the script. fakelua has no GC and its frame arena is only reset when a
top-level Call starts (reentrant count back to zero), so a `while true` loop
inside one Call makes the arena grow without bound and per-request allocations
pile up forever. Every app had to hand-roll its own loop, and getting that wrong
is exactly how the majiang server ended up leaking 2.3GB in 85 seconds.

Entry points are now fixed and mandatory, always in package "main" -- no flags
to rename them, so every app is written the same way:

    package "main"
    function init() ... end      called once at startup
    function update() ... end    called every frame
    function fini() ... end      called once before exit

- each phase is its own top-level Call, so the frame arena is reclaimed every
  tick; the host does not pace the loop (a script that wants a fixed cadence
  calls os.sleep(ms) itself)
- missing entry points are reported up front with the fixed layout spelled out,
  instead of failing later with "function not found"
- graceful shutdown, both forms run fini first: a non-zero return from update, or
  flua.set_exit(code) from Lua. os.exit stays a hard exit that skips fini.
- flua deliberately installs no signal handler: reacting to SIGINT / SIGTERM is
  the application's decision, so the host stays out of it
- --once runs init + fini without the update loop (one-shot script / self-check)
- the script argument may be a directory: every .lua file inside is compiled in
  filename order
- three small wrappers (GetNativeIntArg / NativeNil / HasEntryFunction) so a host
  can write native callbacks and probe for entries without touching inter::,
  which stays an internal detail of the interpreter and JIT
- new cmd/test.lua asserts the lifecycle contract from inside the script (call
  counts and order, cross-frame state only surviving in container.map,
  flua.set_exit semantics, pcall/error across frame boundaries); it runs in the
  Linux, macOS and Windows CI jobs, replacing the bare `flua` invocation that
  only proved the binary starts
- READMEs document the fixed layout

The library behaviour is unchanged: include/fakelua.h only gains the three
additive helpers above, so hosts that embed fakelua keep working as before.
@esrrhs
esrrhs force-pushed the feat/app-lifecycle-host branch from 92b11ae to 92d7424 Compare October 16, 2026 12:17
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.07%. Comparing base (4e5aef0) to head (92d7424).
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##           master      #33   +/-   ##
=======================================
  Coverage   88.07%   88.07%           
=======================================
  Files         124      124           
  Lines       25486    25494    +8     
=======================================
+ Hits        22446    22454    +8     
  Misses       3040     3040           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@esrrhs
esrrhs merged commit a76232f into master Oct 16, 2026
9 checks passed
@esrrhs
esrrhs deleted the feat/app-lifecycle-host branch October 16, 2026 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants