Skip to content

fix: reject blocking/state-releasing Lua ops inside callbacks (#3) - #5

Merged
srgg merged 2 commits into
mainfrom
fix/issue-3-callback-sleep-guard
Jul 6, 2026
Merged

fix: reject blocking/state-releasing Lua ops inside callbacks (#3)#5
srgg merged 2 commits into
mainfrom
fix/issue-3-callback-sleep-guard

Conversation

@srgg

@srgg srgg commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the SIGSEGV (and related engine-freeze) when a synchronous blocking Lua
operation is called from inside a subscription/PTY callback.

Two hazard classes on the single, stateMutex-guarded lua_State:

  • State-releasing (the crash vector): blim.sleep and io.read — and
    transitively blim.term.read_char(wait_ms) via its polling loop — release the
    mutex mid-L.Call, letting another DoWithState reenter the in-flight state
    from another goroutine and corrupt it → SIGSEGV in lua_getinfo.
  • Blocking-while-holding-state: characteristic.read()/write() and
    blim.subscribe do a synchronous BLE round-trip while holding the mutex,
    freezing the main loop and every other callback and risking a deadlock with
    the BLE event path.

Change

  • Single chokepoint guard LuaEngine.raiseIfInCallback(L, op): while a callback
    holds the state (inCallbackCount > 0) these five ops raise a clean,
    recoverable Lua error instead of proceeding. Self-cancel via the callback's
    cancel() argument stays allowed.
  • inCallbackCount moved to LuaEngine (owner of the state and mutex) so
    LuaEngine-registered io.read can consult it without an upward dependency
    on LuaAPI; its increment/decrement is made panic-safe (deferred) so a
    StackTrace-crash panic in L.Call can no longer leak the counter.

Tests

  • Deterministic regression tests for all five guarded ops, grouped under
    TestCallbackBlockingOpGuards.
  • Verified: full internal/lua suite green, -race -count=1 clean, go vet
    and gofmt clean; legitimate main-loop blim.sleep / characteristic.read
    paths unaffected.

Follow-ups (out of scope, documented)

  • Safe blocking inside callbacks (coroutine-per-callback scheduler) is captured
    as deferred work in RFC-001-lua-callback-coroutine-scheduler.md.
  • Panic-safety of the counter is a defensive fix; a direct regression test needs
    a fault-injection seam (not deterministically reproducible in the harness).

Fixes #3

srgg added 2 commits July 6, 2026 14:00
Calling a synchronous blocking primitive from inside a subscription/PTY
callback either corrupted or froze the shared lua_State:

- blim.sleep and io.read (and blim.term.read_char via its polling loop) release
  the single stateMutex mid-L.Call, letting another DoWithState reenter the
  in-flight state from another goroutine -> SIGSEGV in lua_getinfo.
- characteristic.read()/write() and blim.subscribe do not release the mutex but
  block the callback goroutine on a synchronous BLE round-trip while holding the
  state, freezing the main loop and every other callback and risking a deadlock
  with the BLE event path.

Add a single chokepoint guard, LuaEngine.raiseIfInCallback(L, op): while a
callback holds the state (inCallbackCount > 0) these ops raise a clean,
recoverable Lua error instead of proceeding. Self-cancel via the callback's
cancel() argument stays allowed. inCallbackCount moves to LuaEngine (owner of
the state and mutex) so LuaEngine-registered io.read can consult it without
depending upward on LuaAPI, and its increment/decrement is made panic-safe
(deferred) so a StackTrace-crash panic in L.Call can no longer leak the counter.

Add deterministic regression tests for all five guarded ops, grouped under a
dedicated TestCallbackBlockingOpGuards. Document the safe-blocking (coroutine
scheduler) redesign as deferred future work in RFC-001 and a project memory.

Fixes #3
Add .github/workflows/ci.yaml running gofmt check, go vet and the race
detector (`-race -count=1`) with the luajit+test build tags on pull requests
and pushes to main. It regenerates the .gitignore'd mocks / *_depend_test.go
via `make generate` before building.

Extract the shared Go + LuaJIT + CGO/pkg-config setup into a local composite
action .github/actions/setup-luajit and refactor release.yaml to use it
(static-linking: true for release binaries, dynamic for CI).
@srgg
srgg merged commit 93b00f9 into main Jul 6, 2026
1 check passed
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.

SIGSEGV when a synchronous BLE read is called from inside a subscription callback

1 participant