diff --git a/doc/prelude.ml b/doc/prelude.ml index 5b4793336..6eca96f4d 100644 --- a/doc/prelude.ml +++ b/doc/prelude.ml @@ -5,9 +5,13 @@ module Eio_main = struct let now = ref 1623940778.27033591 - let fake_clock real_clock = object (_ : #Eio.Time.clock) - method now = !now - method sleep_until time = + module Fake_clock = struct + type time = float + type t = time Eio.Time.clock_ty r (* The real clock *) + + let make real_clock = (real_clock :> t) + + let sleep_until real_clock 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, @@ -16,8 +20,14 @@ module Eio_main = struct TODO: This is no longer true (since #213). *) Eio.Time.sleep_until real_clock time; now := max !now time + + let now _ = !now end + let fake_clock = + let handler = Eio.Time.Pi.clock (module Fake_clock) in + fun real_clock -> Eio.Resource.T (Fake_clock.make real_clock, handler) + (* To avoid non-deterministic output, we run the examples a single domain. *) let fake_domain_mgr = object (_ : #Eio.Domain_manager.t) method run fn = diff --git a/lib_eio/eio.ml b/lib_eio/eio.ml index f199e02d4..909ca5894 100644 --- a/lib_eio/eio.ml +++ b/lib_eio/eio.ml @@ -28,8 +28,8 @@ module Stdenv = struct let net (t : ) = t#net let process_mgr (t : ) = t#process_mgr let domain_mgr (t : ) = t#domain_mgr - let clock (t : ) = t#clock - let mono_clock (t : ) = t#mono_clock + let clock (t : ) = t#clock + let mono_clock (t : ) = t#mono_clock let secure_random (t: ) = t#secure_random let fs (t : ) = t#fs let cwd (t : ) = t#cwd diff --git a/lib_eio/eio.mli b/lib_eio/eio.mli index 8a8a32d8e..3f26dcb04 100644 --- a/lib_eio/eio.mli +++ b/lib_eio/eio.mli @@ -213,10 +213,10 @@ module Stdenv : sig To use this, see {!Time}. *) - val clock : -> 'a + val clock : -> 'a (** [clock t] is the system clock (used to get the current time and date). *) - val mono_clock : -> 'a + val mono_clock : -> 'a (** [mono_clock t] is a monotonic clock (used for measuring intervals). *) (** {1 Randomness} *) diff --git a/lib_eio/mock/clock.ml b/lib_eio/mock/clock.ml index e65e8c9bd..5e5f6b300 100644 --- a/lib_eio/mock/clock.ml +++ b/lib_eio/mock/clock.ml @@ -1,13 +1,11 @@ open Eio.Std +type 'time ty = [`Mock | 'time Eio.Time.clock_ty] + module type S = sig type time - type t = < - time Eio.Time.clock_base; - advance : unit; - set_time : time -> unit; - > + type t = time ty r val make : unit -> t val advance : t -> unit @@ -22,11 +20,7 @@ module type TIME = sig end module Make(T : TIME) : S with type time := T.t = struct - type t = < - T.t Eio.Time.clock_base; - advance : unit; - set_time : T.t -> unit; - > + type t = T.t ty r module Key = struct type t = < > @@ -44,49 +38,68 @@ module Make(T : TIME) : S with type time := T.t = struct module Q = Psq.Make(Key)(Job) + module Impl = struct + type time = T.t + + type t = { + mutable now : T.t; + mutable q : Q.t; + } + + let make () = + { + now = T.zero; + q = Q.empty; + } + + let now t = t.now + + let sleep_until t time = + if T.compare time t.now <= 0 then Fiber.yield () + else ( + let p, r = Promise.create () in + let k = object end in + t.q <- Q.add k { time; resolver = r } t.q; + try + Promise.await p + with Eio.Cancel.Cancelled _ as ex -> + t.q <- Q.remove k t.q; + raise ex + ) + + let set_time t time = + let rec drain () = + match Q.min t.q with + | Some (_, v) when T.compare v.time time <= 0 -> + Promise.resolve v.resolver (); + t.q <- Option.get (Q.rest t.q); + drain () + | _ -> () + in + drain (); + t.now <- time; + traceln "mock time is now %a" T.pp t.now + + let advance t = + match Q.min t.q with + | None -> invalid_arg "No further events scheduled on mock clock" + | Some (_, v) -> set_time t v.time + + type (_, _, _) Eio.Resource.pi += Raw : ('t, 't -> t, T.t ty) Eio.Resource.pi + let raw (Eio.Resource.T (t, ops)) = Eio.Resource.get ops Raw t + end + + let handler = + Eio.Resource.handler ( + H (Impl.Raw, Fun.id) :: + Eio.Resource.bindings (Eio.Time.Pi.clock (module Impl)); + ) + let make () = - object (self) - inherit [T.t] Eio.Time.clock_base - - val mutable now = T.zero - val mutable q = Q.empty - - method now = now - - method sleep_until time = - if T.compare time now <= 0 then Fiber.yield () - else ( - let p, r = Promise.create () in - let k = object end in - q <- Q.add k { time; resolver = r } q; - try - Promise.await p - with Eio.Cancel.Cancelled _ as ex -> - q <- Q.remove k q; - raise ex - ) - - method set_time time = - let rec drain () = - match Q.min q with - | Some (_, v) when T.compare v.time time <= 0 -> - Promise.resolve v.resolver (); - q <- Option.get (Q.rest q); - drain () - | _ -> () - in - drain (); - now <- time; - traceln "mock time is now %a" T.pp now - - method advance = - match Q.min q with - | None -> invalid_arg "No further events scheduled on mock clock" - | Some (_, v) -> self#set_time v.time - end - - let set_time (t:t) time = t#set_time time - let advance (t:t) = t#advance + Eio.Resource.T (Impl.make (), handler) + + let set_time t v = Impl.set_time (Impl.raw t) v + let advance t = Impl.advance (Impl.raw t) end module Old_time = struct diff --git a/lib_eio/mock/clock.mli b/lib_eio/mock/clock.mli index 3d4bd449f..f8b2f9888 100644 --- a/lib_eio/mock/clock.mli +++ b/lib_eio/mock/clock.mli @@ -1,11 +1,11 @@ +open Eio.Std + +type 'time ty = [`Mock | 'time Eio.Time.clock_ty] + module type S = sig type time - type t = < - time Eio.Time.clock_base; - advance : unit; - set_time : time -> unit; - > + type t = time ty r val make : unit -> t (** [make ()] is a new clock. diff --git a/lib_eio/time.ml b/lib_eio/time.ml index 4e4feadf9..bb2ca79ee 100644 --- a/lib_eio/time.ml +++ b/lib_eio/time.ml @@ -1,24 +1,42 @@ +open Std + exception Timeout -class virtual ['a] clock_base = object - method virtual now : 'a - method virtual sleep_until : 'a -> unit -end +type 'a clock_ty = [`Clock of 'a] +type 'a clock_base = 'a r constraint 'a = [> _ clock_ty] + +module Pi = struct + module type CLOCK = sig + type t + type time + val now : t -> time + val sleep_until : t -> time -> unit + end -class virtual clock = object - inherit [float] clock_base + type (_, _, _) Resource.pi += + | Clock : ('t, (module CLOCK with type t = 't and type time = 'time), [> 'time clock_ty]) Resource.pi + + let clock (type t time) (module X : CLOCK with type t = t and type time = time) = + Resource.handler [ H (Clock, (module X)) ] end -let now (t : _ #clock_base) = t#now +type 'a clock = ([> float clock_ty] as 'a) r + +let now (type time) (t : [> time clock_ty] r) = + let Resource.T (t, ops) = t in + let module X = (val (Resource.get ops Pi.Clock)) in + X.now t -let sleep_until (t : _ #clock_base) time = t#sleep_until time +let sleep_until (type time) (t : [> time clock_ty] r) time = + let Resource.T (t, ops) = t in + let module X = (val (Resource.get ops Pi.Clock)) in + X.sleep_until t time let sleep t d = sleep_until t (now t +. d) module Mono = struct - class virtual t = object - inherit [Mtime.t] clock_base - end + type ty = Mtime.t clock_ty + type 'a t = ([> ty] as 'a) r let now = now let sleep_until = sleep_until @@ -39,7 +57,7 @@ module Mono = struct else Mtime.Span.of_uint64_ns (Int64.of_float ns) ) else Mtime.Span.zero (* Also happens for NaN and negative infinity *) - let sleep (t : #t) s = + let sleep t s = sleep_span t (span_of_s s) end @@ -48,11 +66,11 @@ let with_timeout_exn t d = Fiber.first (fun () -> sleep t d; raise Timeout) module Timeout = struct type t = - | Timeout of Mono.t * Mtime.Span.t + | Timeout of Mono.ty r * Mtime.Span.t | Unlimited let none = Unlimited - let v clock time = Timeout ((clock :> Mono.t), time) + let v clock time = Timeout ((clock :> Mono.ty r), time) let seconds clock time = v clock (Mono.span_of_s time) diff --git a/lib_eio/time.mli b/lib_eio/time.mli index 0d2739e16..8e5aa8106 100644 --- a/lib_eio/time.mli +++ b/lib_eio/time.mli @@ -1,19 +1,17 @@ -class virtual ['a] clock_base : object - method virtual now : 'a - method virtual sleep_until : 'a -> unit -end +open Std -class virtual clock : object - inherit [float] clock_base -end +type 'a clock_ty = [`Clock of 'a] +type 'a clock_base = 'a r constraint 'a = [> _ clock_ty] + +type 'a clock = ([> float clock_ty] as 'a) r -val now : #clock -> float +val now : _ clock -> float (** [now t] is the current time since 00:00:00 GMT, Jan. 1, 1970 - in seconds - according to [t]. *) -val sleep_until : #clock -> float -> unit +val sleep_until : _ clock -> float -> unit (** [sleep_until t time] waits until the given time is reached. *) -val sleep : #clock -> float -> unit +val sleep : _ clock -> float -> unit (** [sleep t d] waits for [d] seconds. *) (** Monotonic clocks. *) @@ -24,20 +22,19 @@ module Mono : sig A monotonic clock may or may not include time while the computer is suspended. *) - class virtual t : object - inherit [Mtime.t] clock_base - end + type ty = Mtime.t clock_ty + type 'a t = ([> ty] as 'a) r - val now : #t -> Mtime.t + val now : _ t -> Mtime.t (** [now t] is the current time according to [t]. *) - val sleep_until : #t -> Mtime.t -> unit + val sleep_until : _ t -> Mtime.t -> unit (** [sleep_until t time] waits until [time] before returning. *) - val sleep : #t -> float -> unit + val sleep : _ t -> float -> unit (** [sleep t d] waits for [d] seconds. *) - val sleep_span : #t -> Mtime.span -> unit + val sleep_span : _ t -> Mtime.span -> unit (** [sleep_span t d] waits for duration [d]. *) end @@ -45,10 +42,10 @@ end exception Timeout -val with_timeout : #clock -> float -> (unit -> ('a, 'e) result) -> ('a, [> `Timeout] as 'e) result +val with_timeout : _ clock -> float -> (unit -> ('a, 'e) result) -> ('a, [> `Timeout] as 'e) result (** [with_timeout clock d fn] runs [fn ()] but cancels it after [d] seconds. *) -val with_timeout_exn : #clock -> float -> (unit -> 'a) -> 'a +val with_timeout_exn : _ clock -> float -> (unit -> 'a) -> 'a (** [with_timeout_exn clock d fn] runs [fn ()] but cancels it after [d] seconds, raising exception {!exception-Timeout}. *) @@ -56,11 +53,11 @@ val with_timeout_exn : #clock -> float -> (unit -> 'a) -> 'a module Timeout : sig type t - val v : #Mono.t -> Mtime.Span.t -> t + val v : _ Mono.t -> Mtime.Span.t -> t (** [v clock duration] is a timeout of [duration], as measured by [clock]. Internally, this is just the tuple [(clock, duration)]. *) - val seconds : #Mono.t -> float -> t + val seconds : _ Mono.t -> float -> t (** [seconds clock duration] is a timeout of [duration] seconds, as measured by [clock]. *) val none : t @@ -77,3 +74,21 @@ module Timeout : sig (** [pp] formats a timeout as a duration (e.g. "5s"). This is intended for use in error messages and logging and is rounded. *) end + +module Pi : sig + module type CLOCK = sig + type t + type time + + val now : t -> time + val sleep_until : t -> time -> unit + end + + type (_, _, _) Resource.pi += + Clock : ('t, (module CLOCK with type t = 't and type time = 'time), + [> 'time clock_ty ]) Resource.pi + + val clock : + (module CLOCK with type t = 't and type time = 'time) -> + ('t, [> 'time clock_ty]) Resource.handler +end diff --git a/lib_eio/unix/eio_unix.ml b/lib_eio/unix/eio_unix.ml index f0eca3ddb..ca0d90518 100644 --- a/lib_eio/unix/eio_unix.ml +++ b/lib_eio/unix/eio_unix.ml @@ -40,8 +40,8 @@ module Stdenv = struct net : [`Unix | `Generic] Eio.Net.ty r; domain_mgr : Eio.Domain_manager.t; process_mgr : Process.mgr; - clock : Eio.Time.clock; - mono_clock : Eio.Time.Mono.t; + clock : float Eio.Time.clock_ty r; + mono_clock : Eio.Time.Mono.ty r; fs : Eio.Fs.dir_ty Eio.Path.t; cwd : Eio.Fs.dir_ty Eio.Path.t; secure_random : Eio.Flow.source_ty r; diff --git a/lib_eio/unix/eio_unix.mli b/lib_eio/unix/eio_unix.mli index 30fe135ad..6874a3d79 100644 --- a/lib_eio/unix/eio_unix.mli +++ b/lib_eio/unix/eio_unix.mli @@ -71,8 +71,8 @@ module Stdenv : sig net : [`Unix | `Generic] Eio.Net.ty r; domain_mgr : Eio.Domain_manager.t; process_mgr : Process.mgr; - clock : Eio.Time.clock; - mono_clock : Eio.Time.Mono.t; + clock : float Eio.Time.clock_ty r; + mono_clock : Eio.Time.Mono.ty r; fs : Eio.Fs.dir_ty Eio.Path.t; cwd : Eio.Fs.dir_ty Eio.Path.t; secure_random : Eio.Flow.source_ty r; @@ -89,7 +89,7 @@ module Private : sig type _ Effect.t += | Await_readable : Unix.file_descr -> unit Effect.t (** See {!await_readable} *) | Await_writable : Unix.file_descr -> unit Effect.t (** See {!await_writable} *) - | Get_monotonic_clock : Eio.Time.Mono.t Effect.t + | Get_monotonic_clock : Eio.Time.Mono.ty r Effect.t | Pipe : Eio.Switch.t -> (source_ty r * sink_ty r) Effect.t (** See {!pipe} *) module Rcfd = Rcfd diff --git a/lib_eio/unix/private.ml b/lib_eio/unix/private.ml index ada01bc88..ab14090a7 100644 --- a/lib_eio/unix/private.ml +++ b/lib_eio/unix/private.ml @@ -6,7 +6,7 @@ open Types type _ Effect.t += | Await_readable : Unix.file_descr -> unit Effect.t | Await_writable : Unix.file_descr -> unit Effect.t - | Get_monotonic_clock : Eio.Time.Mono.t Effect.t + | Get_monotonic_clock : Eio.Time.Mono.ty r Effect.t | Pipe : Switch.t -> (source_ty r * sink_ty r) Effect.t let await_readable fd = Effect.perform (Await_readable fd) diff --git a/lib_eio_linux/eio_linux.ml b/lib_eio_linux/eio_linux.ml index c369ca7cc..17fd7be5d 100644 --- a/lib_eio_linux/eio_linux.ml +++ b/lib_eio_linux/eio_linux.ml @@ -401,26 +401,35 @@ let domain_mgr ~run_event_loop = object unwrap_backtrace (Domain.join (Option.get !domain)) end -let mono_clock = object - inherit Eio.Time.Mono.t - - method now = Mtime_clock.now () +module Mono_clock = struct + type t = unit + type time = Mtime.t - method sleep_until = Low_level.sleep_until + let now () = Mtime_clock.now () + let sleep_until () time = Low_level.sleep_until time end -let clock = object - inherit Eio.Time.clock +let mono_clock : Mtime.t Eio.Time.clock_ty r = + let handler = Eio.Time.Pi.clock (module Mono_clock) in + Eio.Resource.T ((), handler) - method now = Unix.gettimeofday () +module Clock = struct + type t = unit + type time = float - method sleep_until time = + let now () = Unix.gettimeofday () + + let sleep_until () time = (* todo: use the realtime clock directly instead of converting to monotonic time. That is needed to handle adjustments to the system clock correctly. *) let d = time -. Unix.gettimeofday () in Eio.Time.Mono.sleep mono_clock d end +let clock : float Eio.Time.clock_ty r = + let handler = Eio.Time.Pi.clock (module Clock) in + Eio.Resource.T ((), handler) + module rec Dir : sig include Eio.Fs.Pi.DIR diff --git a/lib_eio_posix/domain_mgr.ml b/lib_eio_posix/domain_mgr.ml index 3b8c86ee7..aebc34a3f 100644 --- a/lib_eio_posix/domain_mgr.ml +++ b/lib_eio_posix/domain_mgr.ml @@ -41,7 +41,7 @@ let run_event_loop fn x = let extra_effects : _ effect_handler = { effc = fun (type a) (e : a Effect.t) : ((a, Sched.exit) continuation -> Sched.exit) option -> match e with - | Eio_unix.Private.Get_monotonic_clock -> Some (fun k -> continue k (Time.mono_clock : Eio.Time.Mono.t)) + | Eio_unix.Private.Get_monotonic_clock -> Some (fun k -> continue k Time.mono_clock) | Eio_unix.Net.Import_socket_stream (sw, close_unix, unix_fd) -> Some (fun k -> let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in Unix.set_nonblock unix_fd; diff --git a/lib_eio_posix/time.ml b/lib_eio_posix/time.ml index b11aaffa3..9e07c3956 100644 --- a/lib_eio_posix/time.ml +++ b/lib_eio_posix/time.ml @@ -1,19 +1,30 @@ -let mono_clock = object - inherit Eio.Time.Mono.t +open Eio.Std - method now = Mtime_clock.now () +module Mono_clock = struct + type t = unit + type time = Mtime.t - method sleep_until = Low_level.sleep_until + let now () = Mtime_clock.now () + let sleep_until () time = Low_level.sleep_until time end -let clock = object - inherit Eio.Time.clock +let mono_clock : Mtime.t Eio.Time.clock_ty r = + let handler = Eio.Time.Pi.clock (module Mono_clock) in + Eio.Resource.T ((), handler) - method now = Unix.gettimeofday () +module Clock = struct + type t = unit + type time = float - method sleep_until time = + let now () = Unix.gettimeofday () + + let sleep_until () time = (* todo: use the realtime clock directly instead of converting to monotonic time. That is needed to handle adjustments to the system clock correctly. *) let d = time -. Unix.gettimeofday () in Eio.Time.Mono.sleep mono_clock d end + +let clock : float Eio.Time.clock_ty r = + let handler = Eio.Time.Pi.clock (module Clock) in + Eio.Resource.T ((), handler) diff --git a/lib_eio_windows/time.ml b/lib_eio_windows/time.ml index b11aaffa3..9e07c3956 100755 --- a/lib_eio_windows/time.ml +++ b/lib_eio_windows/time.ml @@ -1,19 +1,30 @@ -let mono_clock = object - inherit Eio.Time.Mono.t +open Eio.Std - method now = Mtime_clock.now () +module Mono_clock = struct + type t = unit + type time = Mtime.t - method sleep_until = Low_level.sleep_until + let now () = Mtime_clock.now () + let sleep_until () time = Low_level.sleep_until time end -let clock = object - inherit Eio.Time.clock +let mono_clock : Mtime.t Eio.Time.clock_ty r = + let handler = Eio.Time.Pi.clock (module Mono_clock) in + Eio.Resource.T ((), handler) - method now = Unix.gettimeofday () +module Clock = struct + type t = unit + type time = float - method sleep_until time = + let now () = Unix.gettimeofday () + + let sleep_until () time = (* todo: use the realtime clock directly instead of converting to monotonic time. That is needed to handle adjustments to the system clock correctly. *) let d = time -. Unix.gettimeofday () in Eio.Time.Mono.sleep mono_clock d end + +let clock : float Eio.Time.clock_ty r = + let handler = Eio.Time.Pi.clock (module Clock) in + Eio.Resource.T ((), handler) diff --git a/tests/time.md b/tests/time.md index 0986d7525..e9b8230eb 100644 --- a/tests/time.md +++ b/tests/time.md @@ -8,7 +8,7 @@ ```ocaml open Eio.Std -let run (fn : clock:Eio.Time.clock -> unit) = +let run (fn : clock:float Eio.Time.clock_ty r -> unit) = Eio_main.run @@ fun env -> let clock = Eio.Stdenv.clock env in fn ~clock