Add Time.now and Time.sleep_until

Providers now provide `sleep_until`. `sleep` is implemented using that.
This makes things a little more precise.
This commit is contained in:
Thomas Leonard 2021-06-17 13:38:08 +01:00
parent 49dc7c62ca
commit 1b853cca44
7 changed files with 71 additions and 13 deletions

View File

@ -22,6 +22,7 @@ unreleased repository.
* [Networking](#networking) * [Networking](#networking)
* [Design note: object capabilities](#design-note-object-capabilities) * [Design note: object capabilities](#design-note-object-capabilities)
* [Filesystem access](#filesystem-access) * [Filesystem access](#filesystem-access)
* [Time](#time)
* [Multicore support](#multicore-support) * [Multicore support](#multicore-support)
* [Design note: thread-safety](#design-note-thread-safety) * [Design note: thread-safety](#design-note-thread-safety)
* [Design note: determinism](#design-note-determinism) * [Design note: determinism](#design-note-determinism)
@ -507,6 +508,24 @@ A program that operates on the current directory will probably want to use `cwd`
whereas a program that accepts a path from the user will probably want to use `fs`, whereas a program that accepts a path from the user will probably want to use `fs`,
perhaps with `open_dir` to constrain all access to be within that directory. perhaps with `open_dir` to constrain all access to be within that directory.
## Time
The standard environment provides a clock with the usual POSIX time:
```ocaml
# Eio_main.run @@ fun env ->
let clock = Eio.Stdenv.clock env in
traceln "The time is now %f" (Eio.Time.now clock);
Eio.Time.sleep clock 1.0;
traceln "The time is now %f" (Eio.Time.now clock)
The time is now 1623940778.270336
The time is now 1623940779.270336
- : unit = ()
```
You might like to replace this clock with a mock for tests.
In fact, this README does just that - see [doc/prelude.ml](doc/prelude.ml) for the fake clock used in the example above!
## Multicore support ## Multicore support
Fibres are scheduled cooperatively within a single domain, but you can also create new domains that run in parallel. Fibres are scheduled cooperatively within a single domain, but you can also create new domains that run in parallel.

View File

@ -3,8 +3,34 @@
module Eio_main = struct module Eio_main = struct
open Eio.Std open Eio.Std
let now = ref 1623940778.27033591
let fake_clock real_clock = object (_ : #Eio.Time.clock)
method now = !now
method sleep_until ?sw time =
(* The fake times are all in the past, so we just ask to wait until the
fake time is due and it will happen immediately. If we wait for
multiple times, they'll get woken in the right order. At the moment,
the scheduler only checks for expired timers when the run-queue is
empty, so this is a convenient way to wait for the system to be idle.
Will need revising if we make the scheduler fair at some point. *)
Eio.Time.sleep_until ?sw real_clock time;
now := max !now time
end
(* https://github.com/ocaml/ocaml/issues/10324 *)
let dontcrash = Sys.opaque_identity
let run fn = let run fn =
Eio_main.run @@ fun env -> Eio_main.run @@ fun env ->
try fn env try
fn @@ object
method net = dontcrash env#net
method stdin = dontcrash env#stdin
method stdout = dontcrash env#stdout
method cwd = dontcrash env#cwd
method domain_mgr = dontcrash env#domain_mgr
method clock = fake_clock env#clock
end
with Failure msg -> traceln "Error: %s" msg with Failure msg -> traceln "Error: %s" msg
end end

View File

@ -169,10 +169,15 @@ end
module Time = struct module Time = struct
class virtual clock = object class virtual clock = object
method virtual sleep : ?sw:Switch.t -> float -> unit method virtual now : float
method virtual sleep_until : ?sw:Switch.t -> float -> unit
end end
let sleep ?sw (t : #clock) d = t#sleep ?sw d let now (t : #clock) = t#now
let sleep_until ?sw (t : #clock) time = t#sleep_until ?sw time
let sleep ?sw t d = sleep_until ?sw t (now t +. d)
end end
module Dir = struct module Dir = struct

View File

@ -363,9 +363,17 @@ end
module Time : sig module Time : sig
class virtual clock : object class virtual clock : object
method virtual sleep : ?sw:Switch.t -> float -> unit method virtual now : float
method virtual sleep_until : ?sw:Switch.t -> float -> unit
end end
val now : #clock -> float
(** [now t] is the current time according to [t]. *)
val sleep_until : ?sw:Switch.t -> #clock -> float -> unit
(** [sleep_until t time] waits until the given time is reached.
@param sw The sleep is aborted if the switch is turned off. *)
val sleep : ?sw:Switch.t -> #clock -> float -> unit val sleep : ?sw:Switch.t -> #clock -> float -> unit
(** [sleep t d] waits for [d] seconds. (** [sleep t d] waits for [d] seconds.
@param sw The sleep is aborted if the switch is turned off. *) @param sw The sleep is aborted if the switch is turned off. *)

View File

@ -404,9 +404,9 @@ let free_buf st buf =
| None -> Uring.Region.free buf | None -> Uring.Region.free buf
| Some k -> enqueue_thread st k buf | Some k -> enqueue_thread st k buf
effect Sleep : Switch.t option * float -> unit effect Sleep_until : Switch.t option * float -> unit
let sleep ?sw d = let sleep_until ?sw d =
perform (Sleep (sw, d)) perform (Sleep_until (sw, d))
effect ERead : (Switch.t option * Optint.Int63.t option * FD.t * Uring.Region.chunk * amount) -> int effect ERead : (Switch.t option * Optint.Int63.t option * FD.t * Uring.Region.chunk * amount) -> int
@ -722,7 +722,8 @@ module Objects = struct
let clock = object let clock = object
inherit Eio.Time.clock inherit Eio.Time.clock
method sleep ?sw d = sleep ?sw d method now = Unix.gettimeofday ()
method sleep_until = sleep_until
end end
class dir fd = object class dir fd = object
@ -856,9 +857,8 @@ let run ?(queue_depth=64) ?(block_size=4096) main =
let k = { Suspended.k; tid } in let k = { Suspended.k; tid } in
enqueue_accept ~sw st k fd client_addr; enqueue_accept ~sw st k fd client_addr;
schedule st schedule st
| effect (Sleep (sw, d)) k -> | effect (Sleep_until (sw, time)) k ->
let k = { Suspended.k; tid } in let k = { Suspended.k; tid } in
let time = Unix.gettimeofday () +. d in
let cancel_hook = ref Switch.null_hook in let cancel_hook = ref Switch.null_hook in
begin match sw with begin match sw with
| None -> | None ->

View File

@ -44,8 +44,8 @@ end
(** {1 Time functions} *) (** {1 Time functions} *)
val sleep : ?sw:Switch.t -> float -> unit val sleep_until : ?sw:Switch.t -> float -> unit
(** [sleep s] blocks until (at least) [s] seconds have passed. (** [sleep_until time] blocks until the current time is [time].
@param sw Cancel the sleep if [sw] is turned off. *) @param sw Cancel the sleep if [sw] is turned off. *)
(** {1 Memory allocation functions} *) (** {1 Memory allocation functions} *)

View File

@ -24,7 +24,7 @@ let () =
let buf = alloc () in let buf = alloc () in
let _ = read_exactly fd buf 5 in let _ = read_exactly fd buf 5 in
Logs.debug (fun l -> l "sleeping at %f" (Unix.gettimeofday ())); Logs.debug (fun l -> l "sleeping at %f" (Unix.gettimeofday ()));
sleep 1.0; sleep_until (Unix.gettimeofday () +. 1.0);
print_endline (Uring.Region.to_string ~len:5 buf); print_endline (Uring.Region.to_string ~len:5 buf);
let _ = read_exactly fd ~file_offset:(Int63.of_int 3) buf 3 in let _ = read_exactly fd ~file_offset:(Int63.of_int 3) buf 3 in
print_endline (Uring.Region.to_string ~len:3 buf); print_endline (Uring.Region.to_string ~len:3 buf);