Skip to content

Commit b3b5225

Browse files
committed
Signal triggers in LIFO order at computation completion
1 parent 44c3742 commit b3b5225

File tree

2 files changed

+58
-8
lines changed

2 files changed

+58
-8
lines changed

lib/picos/bootstrap.ml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,17 @@ module Computation = struct
4646
| Returned _ | Continue _ -> None
4747

4848
open struct
49-
(** [gc] reverses the list of triggers while dropping signaled triggers.
50-
This should be fine (it doesn't make the behavior non-deterministic, for
51-
example), but it might make sense to take extra steps to preserve the
52-
original ordering. *)
53-
let rec gc length triggers = function
54-
| [] -> Continue { balance = length; triggers }
49+
(** [gc] is called when balance becomes negative by both [try_attach] and
50+
[detach]. This ensures that the [O(n)] lazy removal done by [gc] cannot
51+
cause starvation, because the only reason that CAS fails after [gc] is
52+
that someone else completed the [gc]. *)
53+
let rec gc balance triggers = function
54+
| [] ->
55+
let triggers = if balance <= 1 then triggers else List.rev triggers in
56+
Continue { balance; triggers }
5557
| r :: rs ->
56-
if Trigger.is_signaled r then gc length triggers rs
57-
else gc (length + 1) (r :: triggers) rs
58+
if Trigger.is_signaled r then gc balance triggers rs
59+
else gc (balance + 1) (r :: triggers) rs
5860
end
5961

6062
let rec try_attach t trigger backoff =

test/test_picos.ml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,49 @@ let test_cancel_after () =
110110
(Exn_bt.get_callstack 0 Not_found);
111111
Computation.await computation
112112

113+
let test_computation_completion_signals_triggers_in_lifo_order () =
114+
let state = Random.State.make_self_init () in
115+
let num_non_trivial = ref 0 in
116+
for _ = 1 to 10 do
117+
let computation = Computation.create () in
118+
let signals = ref [] in
119+
let triggers = ref [] in
120+
let counter = ref 0 in
121+
let attach_one () =
122+
let trigger = Trigger.create () in
123+
triggers := trigger :: !triggers;
124+
let i = !counter in
125+
counter := i + 1;
126+
assert (Computation.try_attach computation trigger);
127+
assert (
128+
Trigger.on_signal trigger () () (fun _ _ _ -> signals := i :: !signals))
129+
in
130+
let detach_one () =
131+
let n = List.length !triggers in
132+
if 0 < n then begin
133+
let bits = Random.State.bits state in
134+
let i = bits mod n in
135+
let trigger = List.nth !triggers i in
136+
triggers := List.filter (( != ) trigger) !triggers;
137+
Computation.detach computation trigger
138+
end
139+
in
140+
for _ = 1 to 10 do
141+
for _ = 1 to 10 do
142+
let bits = Random.State.bits state in
143+
if bits land 3 <= 2 then attach_one () else detach_one ()
144+
done;
145+
for _ = 1 to List.length !triggers / 3 do
146+
detach_one ()
147+
done
148+
done;
149+
if List.length !triggers >= 2 then incr num_non_trivial;
150+
signals := [];
151+
Computation.finish computation;
152+
assert (!signals = List.sort Int.compare !signals)
153+
done;
154+
assert (0 < !num_non_trivial)
155+
113156
let () =
114157
[
115158
("DLS is lazy", [ Alcotest.test_case "" `Quick test_dls_is_lazy ]);
@@ -120,5 +163,10 @@ let () =
120163
( "Thread cancelation",
121164
[ Alcotest.test_case "" `Quick test_thread_cancelation ] );
122165
("Cancel after", [ Alcotest.test_case "" `Quick test_cancel_after ]);
166+
( "Computation signals in LIFO order",
167+
[
168+
Alcotest.test_case "" `Quick
169+
test_computation_completion_signals_triggers_in_lifo_order;
170+
] );
123171
]
124172
|> Alcotest.run "Picos"

0 commit comments

Comments
 (0)