Skip to content

Commit bc3b47f

Browse files
naomiaroclaude
andcommitted
fix(engine): thread-local CURRENT_UNIT_NOTE_BITS under cfg(test)
The engine is single-threaded by contract, but cargo test runs tests on parallel threads: every reconcile_one writes the same Shared static holding a BroadcastSlot (an Rc with non-atomic refcounts), so parallel tests corrupt the refcount and a later drop segfaults (~1/10 runs, SIGSEGV on Linux CI, SIGABRT/SIGSEGV locally). Apply the PARAMS_SIGNAL cfg(test) thread_local idiom already used for the same race in params.rs. 0/30 failures after, full workspace passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 006ca3c commit bc3b47f

1 file changed

Lines changed: 17 additions & 2 deletions

File tree

crates/engine/src/lib.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,14 +463,29 @@ pub(crate) static MONITOR_OUTPUT: Shared<[f32; monitor::MONITOR_CHANNELS * RENDE
463463

464464
// The note-bits slot of the unit CURRENTLY reconciling (its instruments capture it at construction —
465465
// composite slots share their unit's slot). Set around `reconcile_one`'s chain work, cleared after.
466+
#[cfg(not(test))]
466467
static CURRENT_UNIT_NOTE_BITS: Shared<Option<engine_env::telemetry::BroadcastSlot>> = Shared::new(None);
468+
#[cfg(test)]
469+
std::thread_local! {
470+
// Tests run on parallel threads; the production engine is single-threaded, so the Shared cell is only
471+
// sound there. Per-thread isolation keeps the tests deterministic (the PARAMS_SIGNAL pattern): the
472+
// slot holds an Rc, and racing its non-atomic refcount across test threads corrupts it.
473+
static CURRENT_UNIT_NOTE_BITS: core::cell::RefCell<Option<engine_env::telemetry::BroadcastSlot>> =
474+
const { core::cell::RefCell::new(None) };
475+
}
467476

468477
pub(crate) fn current_unit_note_bits() -> Option<engine_env::telemetry::BroadcastSlot> {
469-
unsafe { CURRENT_UNIT_NOTE_BITS.get() }.clone()
478+
#[cfg(not(test))]
479+
{ unsafe { CURRENT_UNIT_NOTE_BITS.get() }.clone() }
480+
#[cfg(test)]
481+
{ CURRENT_UNIT_NOTE_BITS.with(|cell| cell.borrow().clone()) }
470482
}
471483

472484
pub(crate) fn set_current_unit_note_bits(slot: Option<engine_env::telemetry::BroadcastSlot>) {
473-
*unsafe { CURRENT_UNIT_NOTE_BITS.get() } = slot;
485+
#[cfg(not(test))]
486+
unsafe { *CURRENT_UNIT_NOTE_BITS.get() = slot; }
487+
#[cfg(test)]
488+
CURRENT_UNIT_NOTE_BITS.with(|cell| *cell.borrow_mut() = slot);
474489
}
475490

476491
/// One link in a unit's event PULL CHAIN (the `NoteEventSource` chain, sequencer -> fx -> ... -> the

0 commit comments

Comments
 (0)