Events provide variants of run_at
and run_after
with the ability to abort or
reschedule an event that hasn't yet happened. Once an event happens or is aborted,
Async doesn't use any space for tracking it.
Let t = run_at time f z
. At time
, this runs f z
and transitions status t
to `Happened h
, where h
is result of f z
.
More precisely, at time
, provided abort t a
has not previously been called,
this will call f z
, with the guarantee that status t = `Scheduled_at time
. If
f z
returns h
and did not call abort t a
, then status t
becomes `Happened
h
. If f z
calls abort t a
, then the result of f
is ignored, and status t
is `Aborted a
.
If f z
raises, then status t
does not transition and remains `Scheduled_at
time
, and the exception is sent to the monitor in effect when run_at
was
called.
abort t
changes status t
to `Aborted
and returns `Ok
, unless t
previously happened or was previously aborted.
abort_exn t a
returns unit if abort t a = `Ok
, and otherwise raises.
abort_if_possible t a = ignore (abort t a)
.
reschedule_at t
and reschedule_after t
change the time that t
will fire, if
possible, and if not, give a reason why. `Too_late_to_reschedule
means that the
Async job to fire t
has been enqueued, but has not yet run.
Like run_at
, if the requested time is in the past, the event will be scheduled
to run immediately. If reschedule_at t time = `Ok
, then subsequently
scheduled_at t = time
.
at time
is run_at time ignore ()
.
after time
is run_after time ignore ()
.
You should generally prefer to use the run_*
functions, which allow one to
*synchronously* update state via a user-supplied function when the event
transitions to `Happened
. That is, there is an important difference between:
let t = run_at time f ()
and:
let t = at time in
fired t
>>> function
| `Happened () -> f ()
| `Aborted () -> ()
With run_at
, if status t = `Happened
, one knows that f
has run. With at
and fired
, one does not know whether f
has yet run; it may still be scheduled
to run. Thus, with at
and fired
, it is easy to introduce a race. For
example, consider these two code snippets:
let t = Event.after (sec 2.) in
upon (Event.fired t) (function
| `Aborted () -> ()
| `Happened () -> printf "Timer fired");
upon deferred_event (fun () ->
match Event.abort t () with
| `Ok -> printf "Event occurred"
| `Previously_aborted () -> assert false
| `Previously_happened () -> printf "Event occurred after timer fired");
let t = Event.run_after (sec 2.) printf "Timer fired" in
upon deferred_event (fun () ->
match Event.abort t () with
| `Ok -> printf "Event occurred"
| `Previously_aborted () -> assert false
| `Previously_happened () -> printf "Event occurred after timer fired");
In both snippets, if Event.abort
returns `Ok
, "Timer fired" is never printed.
However, the first snippet might print "Event occurred after timer fired" and then
"Timer fired". This confused ordering cannot happen with Event.run_after
.