Skip to content

Commit f2c9b28

Browse files
committed
Reject reentrant stop-the-world transactions
Use non-reused logical requester identities to fail before waiting on the same thread, while preserving cross-thread restart and fork ownership. Assisted-by: OpenAI Codex: model identifier unavailable
1 parent 43eae7b commit f2c9b28

1 file changed

Lines changed: 29 additions & 8 deletions

File tree

‎crates/vm/src/vm/mod.rs‎

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,20 +198,39 @@ pub(crate) struct StopTheWorldTransaction {
198198
}
199199

200200
#[cfg(feature = "threading")]
201-
static STW_TRANSACTION: AtomicBool = AtomicBool::new(false);
201+
static STW_TRANSACTION: AtomicU64 = AtomicU64::new(0);
202+
203+
#[cfg(feature = "threading")]
204+
static NEXT_STW_REQUESTER: AtomicU64 = AtomicU64::new(1);
205+
206+
#[cfg(feature = "threading")]
207+
thread_local! {
208+
// OS thread IDs can be reused after a requester exits, even while another
209+
// thread still owes its matching restart. Logical IDs are never reused.
210+
static STW_REQUESTER: u64 = NEXT_STW_REQUESTER
211+
.try_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
212+
.expect("stop-the-world requester IDs exhausted");
213+
}
202214

203215
#[cfg(feature = "threading")]
204216
impl StopTheWorldTransaction {
205217
pub(crate) fn acquire() -> Self {
218+
let requester = STW_REQUESTER.with(|id| *id);
206219
loop {
207-
if STW_TRANSACTION
208-
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
209-
.is_ok()
210-
{
211-
return Self { _private: () };
220+
match STW_TRANSACTION.compare_exchange(
221+
0,
222+
requester,
223+
Ordering::AcqRel,
224+
Ordering::Relaxed,
225+
) {
226+
Ok(_) => return Self { _private: () },
227+
Err(owner) => assert_ne!(
228+
owner, requester,
229+
"stop-the-world transactions cannot be nested on the same thread"
230+
),
212231
}
213232
let wait = || {
214-
while STW_TRANSACTION.load(Ordering::Acquire) {
233+
while STW_TRANSACTION.load(Ordering::Acquire) != 0 {
215234
std::thread::yield_now();
216235
}
217236
};
@@ -228,7 +247,7 @@ impl StopTheWorldTransaction {
228247
#[cfg(feature = "threading")]
229248
impl Drop for StopTheWorldTransaction {
230249
fn drop(&mut self) {
231-
STW_TRANSACTION.store(false, Ordering::Release);
250+
STW_TRANSACTION.store(0, Ordering::Release);
232251
}
233252
}
234253

@@ -460,6 +479,8 @@ impl StopTheWorldState {
460479
/// Public calls own one process-wide transaction until the matching start.
461480
/// The caller must finish stop before invoking start, including when those
462481
/// calls occur on different threads; concurrent duplicate starts are invalid.
482+
/// Transactions are non-reentrant: a nested stop on the requesting thread
483+
/// panics before changing any stop state or releasing the outer transaction.
463484
pub fn stop_the_world(&self, state: &PyGlobalState) {
464485
let transaction = StopTheWorldTransaction::acquire();
465486
self.stop_with_transaction(state, &transaction);

0 commit comments

Comments
 (0)