[Bugfix] mcrfpy.step()/advance() from a background thread (without the lock) silently runs a frame off the main thread #411
Labels
No labels
Alpha Release Requirement
Bugfix
Demo Target
Documentation
Major Feature
Minor Feature
priority:tier1-active
priority:tier2-foundation
priority:tier3-future
priority:tier4-deferred
Refactoring & Cleanup
system:animation
system:documentation
system:grid
system:input
system:performance
system:procgen
system:python-binding
system:rendering
system:ui-hierarchy
Tiny Feature
workflow:blocked
workflow:needs-benchmark
workflow:needs-documentation
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
john/McRogueFace#411
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while implementing the guard that makes
step()/advance()raise when the calling thread holdsmcrfpy.lock(). That guard closes the deadlock case. The adjacent case is still wide open and is filed rather than folded in, because deciding it is a semantics change rather than a bug fix.Measured, on the current tree
A background thread that has not taken
mcrfpy.lock()can callmcrfpy.step()and it just works: the simulation clock advances 500 ms,current_frameincrements, and a fullrunSimFrame()body runs —currentScene()->update(), timer callbacks,Scene.update()Python hooks, animations, scene transitions — all on a thread that is not the clock's owner, with no synchronisation against the main loop doing the same thing.If the main thread is also stepping, two frame bodies run concurrently over the same scene graph, timer map and animation manager. That is not a race the frame barrier protects against:
mcrfpy.lock()synchronises holders against the frame, not two threads both driving frames.Why this is not just theoretical
CLAUDE.md's own embedding story is a network client on a background thread inside the engine process, handing results back under
mcrfpy.lock(). "Advance the world when an event arrives" is an obvious next thing for such a thread to try, and it appears to work — it returns a plausible dt and the clock moves. There is nothing to tell the author it is wrong until something corrupts intermittently under load.There should be exactly one thing that moves
simulation_time. Right now any thread can.Options (not decided here)
step()/advance()raise unlessResources::game->isMainThread(). Simplest and matches the one-clock-owner model. Risk: an embedder that legitimately drives the world from a dedicated non-main thread (with no main-thread loop at all) breaks. That is a real pattern for a headless server.step(). Handles both patterns and is still Fail-Early.Option 2 is probably right, but it is a semantics decision about who owns the clock, and it belongs with #395 rather than inside a coverage task.
What is already done
step()/advance()raisesRuntimeError—tests/regression/frame_lock_reentrancy_test.py.docs/frame-lock.md.Related: #395, #398, #410.