Skip to content

Commit 53f2165

Browse files
committed
Auto merge of #59676 - alexcrichton:osx-deadlock, r=sfackler
std: Avoid usage of `Once` in `Instant` This commit removes usage of `Once` from the internal implementation of time utilities on OSX and Windows. It turns out that we accidentally hit a deadlock today (#59020) via events that look like: * A thread invokes `park_timeout` * Internally, only on OSX, `park_timeout` calls `Instant::elapsed` * Inside of `Instant::elapsed` on OSX we enter a `Once` to initialize global timer data * Inside of `Once`, it attempts to `park` This means on the same stack frame, when there's contention, we're calling `park` from inside `park_timeout`, causing a deadlock! The solution implemented in this commit was to remove usage of `Once` and instead just do a small dance with atomics. There's no real need we need to guarantee that the global information is only learned once, only that it's only *stored* once. This implementation may have multiple threads invoke `mach_timebase_info`, but only one will store the global information which will amortize the cost for all other threads. A similar fix has been applied to windows to be uniform across our implementations, but looking at the code on Windows no deadlock was possible. This is purely just a consistency update for Windows and in theory a slightly leaner implementation. Closes #59020
2 parents 52980d0 + cb57484 commit 53f2165

File tree

3 files changed

+65
-13
lines changed

3 files changed

+65
-13
lines changed

src/libstd/sys/unix/time.rs

+20-7
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ impl Hash for Timespec {
114114
#[cfg(any(target_os = "macos", target_os = "ios"))]
115115
mod inner {
116116
use crate::fmt;
117-
use crate::sync::Once;
117+
use crate::mem;
118+
use crate::sync::atomic::{AtomicUsize, Ordering::SeqCst};
118119
use crate::sys::cvt;
119120
use crate::sys_common::mul_div_u64;
120121
use crate::time::Duration;
@@ -229,18 +230,30 @@ mod inner {
229230
Some(mul_div_u64(nanos, info.denom as u64, info.numer as u64))
230231
}
231232

232-
fn info() -> &'static libc::mach_timebase_info {
233+
fn info() -> libc::mach_timebase_info {
233234
static mut INFO: libc::mach_timebase_info = libc::mach_timebase_info {
234235
numer: 0,
235236
denom: 0,
236237
};
237-
static ONCE: Once = Once::new();
238+
static STATE: AtomicUsize = AtomicUsize::new(0);
238239

239240
unsafe {
240-
ONCE.call_once(|| {
241-
libc::mach_timebase_info(&mut INFO);
242-
});
243-
&INFO
241+
// If a previous thread has filled in this global state, use that.
242+
if STATE.load(SeqCst) == 2 {
243+
return INFO;
244+
}
245+
246+
// ... otherwise learn for ourselves ...
247+
let mut info = mem::zeroed();
248+
libc::mach_timebase_info(&mut info);
249+
250+
// ... and attempt to be the one thread that stores it globally for
251+
// all other threads
252+
if STATE.compare_exchange(0, 1, SeqCst, SeqCst).is_ok() {
253+
INFO = info;
254+
STATE.store(2, SeqCst);
255+
}
256+
return info;
244257
}
245258
}
246259
}

src/libstd/sys/windows/time.rs

+18-6
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ fn intervals2dur(intervals: u64) -> Duration {
173173

174174
mod perf_counter {
175175
use super::{NANOS_PER_SEC};
176-
use crate::sync::Once;
176+
use crate::sync::atomic::{AtomicUsize, Ordering::SeqCst};
177177
use crate::sys_common::mul_div_u64;
178178
use crate::sys::c;
179179
use crate::sys::cvt;
@@ -210,13 +210,25 @@ mod perf_counter {
210210

211211
fn frequency() -> c::LARGE_INTEGER {
212212
static mut FREQUENCY: c::LARGE_INTEGER = 0;
213-
static ONCE: Once = Once::new();
213+
static STATE: AtomicUsize = AtomicUsize::new(0);
214214

215215
unsafe {
216-
ONCE.call_once(|| {
217-
cvt(c::QueryPerformanceFrequency(&mut FREQUENCY)).unwrap();
218-
});
219-
FREQUENCY
216+
// If a previous thread has filled in this global state, use that.
217+
if STATE.load(SeqCst) == 2 {
218+
return FREQUENCY;
219+
}
220+
221+
// ... otherwise learn for ourselves ...
222+
let mut frequency = 0;
223+
cvt(c::QueryPerformanceFrequency(&mut frequency)).unwrap();
224+
225+
// ... and attempt to be the one thread that stores it globally for
226+
// all other threads
227+
if STATE.compare_exchange(0, 1, SeqCst, SeqCst).is_ok() {
228+
FREQUENCY = frequency;
229+
STATE.store(2, SeqCst);
230+
}
231+
return frequency;
220232
}
221233
}
222234

src/test/run-pass/issue-59020.rs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// edition:2018
2+
// run-pass
3+
// ignore-emscripten no threads support
4+
5+
use std::thread;
6+
use std::time::Duration;
7+
8+
fn main() {
9+
let t1 = thread::spawn(|| {
10+
let sleep = Duration::new(0,100_000);
11+
for _ in 0..100 {
12+
println!("Parking1");
13+
thread::park_timeout(sleep);
14+
}
15+
});
16+
17+
let t2 = thread::spawn(|| {
18+
let sleep = Duration::new(0,100_000);
19+
for _ in 0..100 {
20+
println!("Parking2");
21+
thread::park_timeout(sleep);
22+
}
23+
});
24+
25+
t1.join().expect("Couldn't join thread 1");
26+
t2.join().expect("Couldn't join thread 2");
27+
}

0 commit comments

Comments
 (0)