In the documentation's deep-handler example, there's this paragraph:
A computation f can itself perform effects. When f performs Yield, its task is paused and placed in the ready-task queue, then the scheduler runs the next queued task. When the effect is Fork f, it pauses the current task and immediately runs the new task f by tail-calling spawn f. Note that picking the new task to run first is an arbitrary decision. Another possible choice would put f's task into the ready queue and immediately continue k instead.
Perhaps I'm overlooking something simple, but consider this scheduler example:
type _ Effect.t += Fork : (unit -> unit) -> unit t
| Yield : unit t
(* Round-robin scheduling with concurrency *)
let run (entry : unit -> unit) : unit =
let pending : (int * (int, unit) continuation) option ref =
ref None (* exchanger awaiting a partner *)
in
let ready = Queue.create () in (* ready-task queue *)
let schedule cont arg =
let resume () = continue cont arg in
Queue.push
resume ready
in
let next () =
if Queue.is_empty ready then () (* finished *)
else begin
let resume = Queue.pop ready in
resume ()
end
in
let rec start (work : unit -> unit) : unit =
match work () with
| () -> next ()
| exception err ->
print_endline (Printexc.to_string err);
next ()
| effect Yield, cont -> schedule cont (); next ()
(* How can work be queued at this point? *)
| effect (Fork work), cont -> schedule cont (); start work
| effect (Xchg value), cont ->
begin match !pending with
| Some (other, saved) -> pending := None; schedule saved value; continue cont other
| None -> pending := Some (value, cont); next ()
end
in
start entry
What would put work onto ready?