Skip to content

Commit 341c908

Browse files
authored
Merge pull request #1 from richkcho/ricch/fix_timeout
Fix bug in windows wait, add tests, update CI to use cargo nextest
2 parents b7dfc53 + 69db708 commit 341c908

File tree

5 files changed

+102
-35
lines changed

5 files changed

+102
-35
lines changed

.config/nextest.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[profile.default]
2+
slow-timeout = { period = "10s", terminate-after = 2, grace-period = "10s" }

.github/workflows/rust.yml

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,27 @@ on:
33
push: { branches: "main" }
44
pull_request: { branches: "*" }
55
jobs:
6-
check-fmt:
7-
name: Check formatting
8-
runs-on: ubuntu-latest
6+
build-and-test:
7+
strategy:
8+
matrix:
9+
os: [ubuntu-latest, ubuntu-22.04-arm, windows-latest, windows-11-arm, macos-latest]
10+
runs-on: ${{ matrix.os }}
911
steps:
10-
- uses: actions/checkout@v1
11-
- run: cargo fmt -- --check
12-
linux:
13-
name: Build and test on Linux
14-
runs-on: ubuntu-latest
15-
steps:
16-
- uses: actions/checkout@v1
17-
- name: Build
18-
run: cargo build --verbose
19-
- name: Run tests
20-
run: cargo test --verbose
21-
windows:
22-
name: Build and test on Windows
23-
runs-on: windows-latest
24-
steps:
25-
- uses: actions/checkout@v1
26-
- name: Build
27-
run: cargo build --verbose
28-
- name: Run tests
29-
run: cargo test --verbose
30-
macos:
31-
name: Build and test on macOS
32-
runs-on: macos-latest
33-
steps:
34-
- uses: actions/checkout@v1
35-
- name: Build
36-
run: cargo build --verbose
37-
- name: Run tests
38-
run: cargo test --verbose
12+
- uses: actions/checkout@v4
13+
- name: Install toolchain components
14+
run: rustup component add rustfmt clippy
15+
- name: Install cargo-binstall
16+
uses: taiki-e/install-action@v2
17+
with:
18+
tool: cargo-binstall
19+
- name: Install cargo-nextest
20+
run: cargo binstall cargo-nextest --secure --no-confirm
21+
- name: Check formatting
22+
run: cargo fmt --all -- --check
23+
- name: Run clippy
24+
run: cargo clippy --all-targets -- -D warnings
25+
- name: Build
26+
run: cargo build --verbose
27+
- name: Run tests
28+
run: cargo nextest run --verbose
29+

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ categories = ["concurrency", "os", "no-std"]
1212
libc = { version = "0.2", default-features = false }
1313

1414
[target.'cfg(windows)'.dependencies]
15-
windows-sys = { version = "0.59.0", default-features = false, features = ["Win32_System_Threading", "Win32_Foundation"] }
15+
windows-sys = { version = "0.61.0", default-features = false, features = ["Win32_System_Threading", "Win32_Foundation"] }
1616

1717
[target.'cfg(target_arch = "wasm32")'.dependencies]
1818
js-sys = { version = "0.3.67", default-features = false }

src/windows.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ impl AtomicWaitImpl for AtomicU32 {
1818
&value as *const _ as *const _,
1919
size_of::<Self>(),
2020
timeout
21-
.map(|x| x.as_millis().max(u64::MAX as u128) as u32)
21+
.map(|x| {
22+
// Clamp to a finite u32 millisecond timeout. INFINITE (0xFFFFFFFF)
23+
// means no timeout, so avoid ever passing that when a timeout is set.
24+
let ms = x.as_millis();
25+
let capped = ms.min(u32::MAX as u128 - 1);
26+
capped as u32
27+
})
2228
.unwrap_or(INFINITE),
2329
);
2430
}
@@ -43,7 +49,7 @@ impl AtomicWaitImpl for AtomicU64 {
4349
&value as *const _ as *const _,
4450
size_of::<Self>(),
4551
timeout
46-
.map(|x| x.as_millis().max(u64::MAX as u128) as u32)
52+
.map(|x| x.as_millis().min(u32::MAX as u128 - 1) as u32)
4753
.unwrap_or(INFINITE),
4854
);
4955
}

tests/test.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,71 @@ fn wait_wake() {
3737
assert!((90..400).contains(&t.elapsed().as_millis()));
3838
});
3939
}
40+
41+
#[test]
42+
fn wait_timeout() {
43+
let a = AtomicU32::new(0);
44+
a.wait_timeout(0, Duration::from_millis(1));
45+
}
46+
47+
#[test]
48+
fn stress_many_waiters_notify_all() {
49+
use std::sync::Arc;
50+
let a = Arc::new(AtomicU32::new(0));
51+
let woke = Arc::new(AtomicU32::new(0));
52+
53+
let threads = 64;
54+
std::thread::scope(|s| {
55+
for _ in 0..threads {
56+
let a = a.clone();
57+
let woke = woke.clone();
58+
s.spawn(move || {
59+
while a.load(Relaxed) == 0 {
60+
a.wait(0);
61+
}
62+
woke.fetch_add(1, Relaxed);
63+
});
64+
}
65+
66+
// Give threads time to start waiting
67+
sleep(Duration::from_millis(50));
68+
a.store(1, Relaxed);
69+
a.notify_all();
70+
});
71+
72+
assert_eq!(woke.load(Relaxed), threads);
73+
}
74+
75+
#[test]
76+
fn stress_ping_pong_many_iters() {
77+
use std::sync::Arc;
78+
let state = Arc::new(AtomicU32::new(0));
79+
let iters = 5_000u32;
80+
81+
std::thread::scope(|s| {
82+
let state_c = state.clone();
83+
s.spawn(move || {
84+
// Consumer: wait for 1, reset to 0, and notify producer.
85+
for _ in 0..iters {
86+
while state_c.load(Relaxed) != 1 {
87+
// Wait while the state is 0; use a short timeout to be resilient to spurious wakes.
88+
state_c.wait_timeout(0, Duration::from_millis(10));
89+
}
90+
state_c.store(0, Relaxed);
91+
state_c.notify_one();
92+
}
93+
});
94+
95+
// Producer: set to 1, notify consumer, then wait until it resets to 0.
96+
for _ in 0..iters {
97+
state.store(1, Relaxed);
98+
state.notify_one();
99+
while state.load(Relaxed) != 0 {
100+
state.wait_timeout(1, Duration::from_millis(10));
101+
}
102+
}
103+
});
104+
105+
// Final state should be 0 after a complete ping-pong.
106+
assert_eq!(state.load(Relaxed), 0);
107+
}

0 commit comments

Comments
 (0)