fix: make Ctrl+C cancellation deterministic in the bridge (#4) - #6
Merged
Merged
Conversation
After Ctrl+C with a device connected, an interactive bridge script never stopped: blim hung forever at "Stopping script". The cooperative-cancellation count hook (LUA_MASKCOUNT) is not honored inside LuaJIT-compiled traces, so an FFI-hot script loop spun forever and L.DoString never returned. Make cancellation deterministic at the API boundary instead of relying on the hook: - blim.sleep now raises "script execution cancelled" on ctx.Done() (re-locking stateMutex before the raise, since RaiseError walks the Lua stack — the #3 corruption class). This Go-side raise is not catchable by Lua pcall, so a polling loop (including blim.term.read_char, which sleeps on every step) aborts within one iteration. - Bound the post-cancellation wait in safeExecuteScript: a script with no cancellable boundary (a pure JIT/FFI loop) can never be stopped cooperatively, so after scriptCancellationTimeout give up, dump goroutines, and return ErrScriptCancellationTimeout instead of hanging forever. - Restore the terminal on bridge exit: a script may switch it to raw via blim.term.enable_raw and cancellation aborts before disable_raw runs, leaving the shell stuck in raw mode. runBridge is split into a thin cobra wrapper and runBridgeCtx(ctx, ...); the latter snapshots the terminal on entry and restores it on exit (x/term), the way ssh does. The ctx split also lets the test drive cancellation without a process-global signal. Adds deterministic tests: blim.sleep raises on cancellation, the bounded wait returns a timeout error for an unstoppable script, and a black-box bridge test (fake-tty stdin + mock device) that a raw-mode script leaves the terminal restored after cancellation. Fixes #4
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.
Summary
Fixes the hang where, after
Ctrl+Cwith a device connected, an interactivebridge script never stops (blim hangs forever at "Stopping script"). The
cooperative-cancellation count hook (
LUA_MASKCOUNT) is not honored insideLuaJIT-compiled traces, so an FFI-hot script loop spins forever and
L.DoStringnever returns.Cancellation is made deterministic at the API boundary instead of relying on
the hook.
Changes
blim.sleepraises on cancellation. Onctx.Done()it re-locksstateMutex(RaiseError walks the Lua stack — the SIGSEGV when a synchronous BLE read is called from inside a subscription callback #3 corruption class) andraises
"script execution cancelled". This Go-side raise is not catchable byLua
pcall, so a polling loop — includingblim.term.read_char, which sleepson every step — aborts within one iteration.
pure JIT/FFI loop) can never be stopped cooperatively.
safeExecuteScriptnow gives up after
scriptCancellationTimeout, dumps goroutines, and returnsErrScriptCancellationTimeoutinstead of hanging forever.via
blim.term.enable_raw; cancellation aborts it beforedisable_rawruns,leaving the shell stuck in raw mode.
runBridgeis split into a thin cobrawrapper and
runBridgeCtx(ctx, ...); the latter snapshots the terminal onentry and restores it on exit (
x/term), the way ssh does. The ctx splitalso lets the test drive cancellation without a process-global signal.
Tests
TestSleepRaisesOnContextCancellation—blim.sleepaborts the script oncancellation (code after the sleep never runs).
TestExecuteScript2_CancellationTimeout— an unstoppable script yieldsErrScriptCancellationTimeout(checked viaerrors.Is), not an unbounded hang.TestBridgeRestoresTerminalOnExit— black-box: fake-tty stdin + mock device;a raw-mode script leaves the terminal restored after cancellation.
-race -count=1clean,go vet+gofmtclean.Follow-ups (out of scope, documented)
characteristic.read/write,blim.subscribe's CCCD write) so cancellation returns immediately rather thanafter the op's own completion. Now a responsiveness nicety, not a hang —
the bounded wait already caps the worst case.
scriptExecutionCtx(not introducedhere).
Fixes #4