Skip to content

Commit 6a54a19

Browse files
author
Simon Cruanes
committed
dscheck for ws_deque
1 parent 423a011 commit 6a54a19

3 files changed

Lines changed: 139 additions & 0 deletions

File tree

test/dscheck/atomic.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(* Shadows [Stdlib.Atomic] for [ws_deque_.ml] (copied unmodified from
2+
[src/private/] by the dune rule in this directory), so it gets compiled
3+
against DScheck's traced atomics instead of the real ones. *)
4+
include Dscheck.TracedAtomic

test/dscheck/dune

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
(rule
2+
(enabled_if
3+
(>= %{ocaml_version} 5.0))
4+
(action
5+
(copy ../../src/private/ws_deque_.ml ws_deque_.ml)))
6+
7+
(test
8+
(name ws_deque_dscheck)
9+
(package moonpool)
10+
(enabled_if
11+
(>= %{ocaml_version} 5.0))
12+
(libraries dscheck)
13+
(modules atomic ws_deque_ ws_deque_dscheck))

test/dscheck/ws_deque_dscheck.ml

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
(* DScheck interleaving tests for [Ws_deque_], the work-stealing deque used
2+
by every pool. [ws_deque_.ml] here is an unmodified copy of
3+
[src/private/ws_deque_.ml] (see dune rule); [atomic.ml] shadows
4+
[Stdlib.Atomic] so it runs against DScheck's traced atomics.
5+
6+
NOTE: DScheck only tracks races on [Atomic.t] cells ("domains communicate
7+
through atomic variables only"). It cannot see races on the deque's plain
8+
backing array, so it would not have caught the cross-thread [steal]
9+
clearing bug found via the [t_ws_deque.ml] stress test. What it *does*
10+
check: that the [top]/[bottom]/CAS index protocol never hands out the
11+
same logical item twice, or drops one, across every reachable
12+
interleaving of a small push/pop/steal scenario. *)
13+
14+
let dummy = -1
15+
16+
let drain_remaining (q : int Ws_deque_.t) : int list =
17+
let acc = ref [] in
18+
let continue = ref true in
19+
while !continue do
20+
match Ws_deque_.pop q with
21+
| Some x -> acc := x :: !acc
22+
| None -> continue := false
23+
done;
24+
!acc
25+
26+
(* one owner (push + pop), one stealer *)
27+
let owner_stealer () =
28+
Atomic.trace (fun () ->
29+
let q = Ws_deque_.create ~dummy () in
30+
let total_items = 4 in
31+
32+
let popped = ref 0 in
33+
Atomic.spawn (fun () ->
34+
for i = 1 to total_items do
35+
ignore (Ws_deque_.push q i : bool)
36+
done;
37+
for _ = 1 to total_items / 2 do
38+
match Ws_deque_.pop q with
39+
| Some _ -> incr popped
40+
| None -> ()
41+
done);
42+
43+
let stolen = ref 0 in
44+
Atomic.spawn (fun () ->
45+
for _ = 1 to total_items / 2 do
46+
match Ws_deque_.steal q with
47+
| Some _ -> incr stolen
48+
| None -> ()
49+
done);
50+
51+
Atomic.final (fun () ->
52+
Atomic.check (fun () ->
53+
let remaining = List.length (drain_remaining q) in
54+
remaining + !popped + !stolen = total_items)))
55+
56+
(* one owner (push + pop), two stealers *)
57+
let owner_two_stealers () =
58+
Atomic.trace (fun () ->
59+
let q = Ws_deque_.create ~dummy () in
60+
let total_items = 6 in
61+
62+
let popped = ref 0 in
63+
Atomic.spawn (fun () ->
64+
for i = 1 to total_items do
65+
ignore (Ws_deque_.push q i : bool)
66+
done;
67+
for _ = 1 to total_items / 3 do
68+
match Ws_deque_.pop q with
69+
| Some _ -> incr popped
70+
| None -> ()
71+
done);
72+
73+
let stolen1 = ref 0 in
74+
Atomic.spawn (fun () ->
75+
for _ = 1 to total_items / 3 do
76+
match Ws_deque_.steal q with
77+
| Some _ -> incr stolen1
78+
| None -> ()
79+
done);
80+
81+
let stolen2 = ref 0 in
82+
Atomic.spawn (fun () ->
83+
for _ = 1 to total_items / 3 do
84+
match Ws_deque_.steal q with
85+
| Some _ -> incr stolen2
86+
| None -> ()
87+
done);
88+
89+
Atomic.final (fun () ->
90+
Atomic.check (fun () ->
91+
let remaining = List.length (drain_remaining q) in
92+
remaining + !popped + !stolen1 + !stolen2 = total_items)))
93+
94+
(* queue pre-filled before any domain starts (mirrors a pool handing off a
95+
full local queue to be stolen from), then only stealers touch it *)
96+
let prefilled_stealers () =
97+
Atomic.trace (fun () ->
98+
let q = Ws_deque_.create ~dummy () in
99+
let total_items = 3 in
100+
for i = 1 to total_items do
101+
ignore (Ws_deque_.push q i : bool)
102+
done;
103+
104+
let stolen = Array.make total_items 0 in
105+
for k = 0 to total_items - 1 do
106+
Atomic.spawn (fun () ->
107+
match Ws_deque_.steal q with
108+
| Some _ -> stolen.(k) <- 1
109+
| None -> ())
110+
done;
111+
112+
Atomic.final (fun () ->
113+
Atomic.check (fun () ->
114+
let n_stolen = Array.fold_left ( + ) 0 stolen in
115+
let remaining = List.length (drain_remaining q) in
116+
n_stolen + remaining = total_items)))
117+
118+
let () =
119+
owner_stealer ();
120+
owner_two_stealers ();
121+
prefilled_stealers ();
122+
print_endline "ws_deque_dscheck: all interleavings checked ok"

0 commit comments

Comments
 (0)