Skip to content

Commit 9568d7e

Browse files
committed
Auto merge of #2641 - DrMeepster:init_once_acquire, r=RalfJung
InitOnce: synchronize with completion when already complete The completion of an InitOnce happens-before the threads waiting on it wake up. However, this is not the case for threads that call `InitOnceBeginInitialize` after the completion, leading to data races and outdated weak memory loads as observed in the CI for #2638. This PR fixes this.
2 parents c20217f + bc05e6b commit 9568d7e

File tree

3 files changed

+97
-33
lines changed

3 files changed

+97
-33
lines changed

src/concurrency/init_once.rs

+55-31
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::num::NonZeroU32;
33

44
use rustc_index::vec::Idx;
55

6-
use super::sync::EvalContextExtPriv;
6+
use super::sync::EvalContextExtPriv as _;
77
use super::thread::MachineCallback;
88
use super::vector_clock::VClock;
99
use crate::*;
@@ -52,6 +52,43 @@ impl<'mir, 'tcx> VisitTags for InitOnce<'mir, 'tcx> {
5252
}
5353
}
5454

55+
impl<'mir, 'tcx: 'mir> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
56+
trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
57+
/// Synchronize with the previous initialization attempt of an InitOnce.
58+
#[inline]
59+
fn init_once_observe_attempt(&mut self, id: InitOnceId) {
60+
let this = self.eval_context_mut();
61+
let current_thread = this.get_active_thread();
62+
63+
if let Some(data_race) = &this.machine.data_race {
64+
data_race.validate_lock_acquire(
65+
&this.machine.threads.sync.init_onces[id].data_race,
66+
current_thread,
67+
);
68+
}
69+
}
70+
71+
#[inline]
72+
fn init_once_wake_waiter(
73+
&mut self,
74+
id: InitOnceId,
75+
waiter: InitOnceWaiter<'mir, 'tcx>,
76+
) -> InterpResult<'tcx> {
77+
let this = self.eval_context_mut();
78+
let current_thread = this.get_active_thread();
79+
80+
this.unblock_thread(waiter.thread);
81+
82+
// Call callback, with the woken-up thread as `current`.
83+
this.set_active_thread(waiter.thread);
84+
this.init_once_observe_attempt(id);
85+
waiter.callback.call(this)?;
86+
this.set_active_thread(current_thread);
87+
88+
Ok(())
89+
}
90+
}
91+
5592
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
5693
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
5794
fn init_once_get_or_create_id(
@@ -141,20 +178,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
141178
// Wake up everyone.
142179
// need to take the queue to avoid having `this` be borrowed multiple times
143180
for waiter in std::mem::take(&mut init_once.waiters) {
144-
// End of the wait happens-before woken-up thread.
145-
if let Some(data_race) = &this.machine.data_race {
146-
data_race.validate_lock_acquire(
147-
&this.machine.threads.sync.init_onces[id].data_race,
148-
waiter.thread,
149-
);
150-
}
151-
152-
this.unblock_thread(waiter.thread);
153-
154-
// Call callback, with the woken-up thread as `current`.
155-
this.set_active_thread(waiter.thread);
156-
waiter.callback.call(this)?;
157-
this.set_active_thread(current_thread);
181+
this.init_once_wake_waiter(id, waiter)?;
158182
}
159183

160184
Ok(())
@@ -172,33 +196,33 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
172196
);
173197

174198
// Each complete happens-before the end of the wait
175-
// FIXME: should this really induce synchronization? If we think of it as a lock, then yes,
176-
// but the docs don't talk about such details.
177199
if let Some(data_race) = &this.machine.data_race {
178200
data_race.validate_lock_release(&mut init_once.data_race, current_thread);
179201
}
180202

181203
// Wake up one waiting thread, so they can go ahead and try to init this.
182204
if let Some(waiter) = init_once.waiters.pop_front() {
183-
// End of the wait happens-before woken-up thread.
184-
if let Some(data_race) = &this.machine.data_race {
185-
data_race.validate_lock_acquire(
186-
&this.machine.threads.sync.init_onces[id].data_race,
187-
waiter.thread,
188-
);
189-
}
190-
191-
this.unblock_thread(waiter.thread);
192-
193-
// Call callback, with the woken-up thread as `current`.
194-
this.set_active_thread(waiter.thread);
195-
waiter.callback.call(this)?;
196-
this.set_active_thread(current_thread);
205+
this.init_once_wake_waiter(id, waiter)?;
197206
} else {
198207
// Nobody there to take this, so go back to 'uninit'
199208
init_once.status = InitOnceStatus::Uninitialized;
200209
}
201210

202211
Ok(())
203212
}
213+
214+
/// Synchronize with the previous completion of an InitOnce.
215+
/// Must only be called after checking that it is complete.
216+
#[inline]
217+
fn init_once_observe_completed(&mut self, id: InitOnceId) {
218+
let this = self.eval_context_mut();
219+
220+
assert_eq!(
221+
this.init_once_status(id),
222+
InitOnceStatus::Complete,
223+
"observing the completion of incomplete init once"
224+
);
225+
226+
this.init_once_observe_attempt(id);
227+
}
204228
}

src/shims/windows/sync.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
177177
Box::new(Callback { init_once_id: id, pending_place }),
178178
)
179179
}
180-
InitOnceStatus::Complete =>
181-
this.write_scalar(this.eval_windows("c", "FALSE")?, &pending_place)?,
180+
InitOnceStatus::Complete => {
181+
this.init_once_observe_completed(id);
182+
this.write_scalar(this.eval_windows("c", "FALSE")?, &pending_place)?;
183+
}
182184
}
183185

184186
// This always succeeds (even if the thread is blocked, we will succeed if we ever unblock).

tests/pass/concurrency/windows_init_once.rs

+38
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,46 @@ fn retry_on_fail() {
131131
waiter2.join().unwrap();
132132
}
133133

134+
fn no_data_race_after_complete() {
135+
let mut init_once = null_mut();
136+
let mut pending = 0;
137+
138+
unsafe {
139+
assert_eq!(InitOnceBeginInitialize(&mut init_once, 0, &mut pending, null_mut()), TRUE);
140+
assert_eq!(pending, TRUE);
141+
}
142+
143+
let init_once_ptr = SendPtr(&mut init_once);
144+
145+
let mut place = 0;
146+
let place_ptr = SendPtr(&mut place);
147+
148+
let reader = thread::spawn(move || unsafe {
149+
let mut pending = 0;
150+
151+
// this doesn't block because reader only executes after `InitOnceComplete` is called
152+
assert_eq!(InitOnceBeginInitialize(init_once_ptr.0, 0, &mut pending, null_mut()), TRUE);
153+
assert_eq!(pending, FALSE);
154+
// this should not data race
155+
place_ptr.0.read()
156+
});
157+
158+
unsafe {
159+
// this should not data race
160+
place_ptr.0.write(1);
161+
}
162+
163+
unsafe {
164+
assert_eq!(InitOnceComplete(init_once_ptr.0, 0, null_mut()), TRUE);
165+
}
166+
167+
// run reader (without preemption, it has not taken a step yet)
168+
assert_eq!(reader.join().unwrap(), 1);
169+
}
170+
134171
fn main() {
135172
single_thread();
136173
block_until_complete();
137174
retry_on_fail();
175+
no_data_race_after_complete();
138176
}

0 commit comments

Comments
 (0)