601 lines
15 KiB
OCaml
601 lines
15 KiB
OCaml
|
|
(** Implementation of the protocol used by dune rpc. Independent of IO and any
|
||
|
|
specific rpc requests. The protocol described here is stable and is relied
|
||
|
|
on by 3rd party clients.
|
||
|
|
|
||
|
|
The implementation is loosely modelled on jsonrpc. It defines the following
|
||
|
|
concepts:
|
||
|
|
|
||
|
|
Session - An active rpc session
|
||
|
|
|
||
|
|
Request - A unique id with a call sent by a client. A server must respond to
|
||
|
|
every request
|
||
|
|
|
||
|
|
Notification - A call send by a client. A server must not respond to a
|
||
|
|
notification
|
||
|
|
|
||
|
|
It contains hooks that make it possible to use with any custom scheduler
|
||
|
|
that uses fibers
|
||
|
|
|
||
|
|
The API in this version is versioned. When using this library, we expect
|
||
|
|
that the module corresponding to a particular version is used exclusively.
|
||
|
|
|
||
|
|
While we guarantee stability of the API, we reserve the right to:
|
||
|
|
|
||
|
|
- Add optional arguments to functions
|
||
|
|
- Add new fields to records
|
||
|
|
- New variant constructors that will not cause runtime errors in existing
|
||
|
|
user programs.
|
||
|
|
|
||
|
|
This means that you must refrain from re-exporting any values, constructing
|
||
|
|
any records, using any module types as functor arguments, or make non
|
||
|
|
exhaustive matches an error to guarantee compatibility. *)
|
||
|
|
|
||
|
|
[@@@alert unstable "The API of this library is not stable and may change without notice."]
|
||
|
|
[@@@alert "-unstable"]
|
||
|
|
|
||
|
|
module Id : sig
|
||
|
|
(** Id's for requests, responses, sessions.
|
||
|
|
|
||
|
|
Id's are permitted to be arbitrary s-expressions to allow users pick
|
||
|
|
descriptive tokens to ease debugging. *)
|
||
|
|
|
||
|
|
type t
|
||
|
|
|
||
|
|
val make : Csexp.t -> t
|
||
|
|
end
|
||
|
|
|
||
|
|
module Response : sig
|
||
|
|
module Error : sig
|
||
|
|
type kind =
|
||
|
|
| Invalid_request
|
||
|
|
| Code_error
|
||
|
|
| Connection_dead
|
||
|
|
|
||
|
|
type t
|
||
|
|
|
||
|
|
val payload : t -> Csexp.t option
|
||
|
|
val message : t -> string
|
||
|
|
val kind : t -> kind
|
||
|
|
|
||
|
|
exception E of t
|
||
|
|
end
|
||
|
|
|
||
|
|
type t = (Csexp.t, Error.t) result
|
||
|
|
end
|
||
|
|
|
||
|
|
module Initialize : sig
|
||
|
|
type t
|
||
|
|
|
||
|
|
val create : id:Id.t -> t
|
||
|
|
end
|
||
|
|
|
||
|
|
module Loc : sig
|
||
|
|
type t
|
||
|
|
|
||
|
|
val start : t -> Lexing.position
|
||
|
|
val stop : t -> Lexing.position
|
||
|
|
end
|
||
|
|
|
||
|
|
module Path : sig
|
||
|
|
type t
|
||
|
|
|
||
|
|
val dune_root : t
|
||
|
|
val absolute : string -> t
|
||
|
|
val relative : t -> string -> t
|
||
|
|
val to_string_absolute : t -> string
|
||
|
|
end
|
||
|
|
|
||
|
|
module Ansi_color : sig
|
||
|
|
module RGB8 : sig
|
||
|
|
(** 8-bit RGB color *)
|
||
|
|
type t
|
||
|
|
|
||
|
|
(** [to_int t] returns the 8-bit color as an integer in the range [0, 255]. *)
|
||
|
|
val to_int : t -> int
|
||
|
|
end
|
||
|
|
|
||
|
|
module RGB24 : sig
|
||
|
|
(** 24-bit RGB color (true color) *)
|
||
|
|
type t
|
||
|
|
|
||
|
|
(** [red t] returns the red component of the 24-bit color [t]. *)
|
||
|
|
val red : t -> int
|
||
|
|
|
||
|
|
(** [green t] returns the green component of the 24-bit color [t]. *)
|
||
|
|
val green : t -> int
|
||
|
|
|
||
|
|
(** [blue t] returns the blue component of the 24-bit color [t]. *)
|
||
|
|
val blue : t -> int
|
||
|
|
|
||
|
|
(** [to_int t] returns the 24-bit color as an integer in the range [0, 0xFFFFFF].
|
||
|
|
Each color components consists of 8 bits. *)
|
||
|
|
val to_int : t -> int
|
||
|
|
end
|
||
|
|
|
||
|
|
module Style : sig
|
||
|
|
(** Ansi Terminal Styles *)
|
||
|
|
type t =
|
||
|
|
[ `Fg_default
|
||
|
|
| `Fg_black
|
||
|
|
| `Fg_red
|
||
|
|
| `Fg_green
|
||
|
|
| `Fg_yellow
|
||
|
|
| `Fg_blue
|
||
|
|
| `Fg_magenta
|
||
|
|
| `Fg_cyan
|
||
|
|
| `Fg_white
|
||
|
|
| `Fg_bright_black
|
||
|
|
| `Fg_bright_red
|
||
|
|
| `Fg_bright_green
|
||
|
|
| `Fg_bright_yellow
|
||
|
|
| `Fg_bright_blue
|
||
|
|
| `Fg_bright_magenta
|
||
|
|
| `Fg_bright_cyan
|
||
|
|
| `Fg_bright_white
|
||
|
|
| `Fg_8_bit_color of RGB8.t
|
||
|
|
| `Fg_24_bit_color of RGB24.t
|
||
|
|
| `Bg_default
|
||
|
|
| `Bg_black
|
||
|
|
| `Bg_red
|
||
|
|
| `Bg_green
|
||
|
|
| `Bg_yellow
|
||
|
|
| `Bg_blue
|
||
|
|
| `Bg_magenta
|
||
|
|
| `Bg_cyan
|
||
|
|
| `Bg_white
|
||
|
|
| `Bg_bright_black
|
||
|
|
| `Bg_bright_red
|
||
|
|
| `Bg_bright_green
|
||
|
|
| `Bg_bright_yellow
|
||
|
|
| `Bg_bright_blue
|
||
|
|
| `Bg_bright_magenta
|
||
|
|
| `Bg_bright_cyan
|
||
|
|
| `Bg_bright_white
|
||
|
|
| `Bg_8_bit_color of RGB8.t
|
||
|
|
| `Bg_24_bit_color of RGB24.t
|
||
|
|
| `Bold
|
||
|
|
| `Dim
|
||
|
|
| `Italic
|
||
|
|
| `Underline
|
||
|
|
]
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
module User_message : sig
|
||
|
|
(** User Message Styles *)
|
||
|
|
module Style : sig
|
||
|
|
type t =
|
||
|
|
| Loc
|
||
|
|
| Error
|
||
|
|
| Warning
|
||
|
|
| Kwd
|
||
|
|
| Id
|
||
|
|
| Prompt
|
||
|
|
| Hint
|
||
|
|
| Details
|
||
|
|
| Ok
|
||
|
|
| Debug
|
||
|
|
| Success
|
||
|
|
| Ansi_styles of Ansi_color.Style.t list
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
module Target : sig
|
||
|
|
type t =
|
||
|
|
| Path of string
|
||
|
|
| Alias of string
|
||
|
|
| Library of string
|
||
|
|
| Executables of string list
|
||
|
|
| Preprocess of string list
|
||
|
|
| Loc of Loc.t
|
||
|
|
end
|
||
|
|
|
||
|
|
module Diagnostic : sig
|
||
|
|
type severity =
|
||
|
|
| Error
|
||
|
|
| Warning
|
||
|
|
|
||
|
|
module Promotion : sig
|
||
|
|
type t
|
||
|
|
|
||
|
|
val in_build : t -> string
|
||
|
|
val in_source : t -> string
|
||
|
|
end
|
||
|
|
|
||
|
|
module Id : sig
|
||
|
|
type t
|
||
|
|
|
||
|
|
val compare : t -> t -> Ordering.t
|
||
|
|
val hash : t -> int
|
||
|
|
val create : int -> t
|
||
|
|
end
|
||
|
|
|
||
|
|
module Related : sig
|
||
|
|
type t
|
||
|
|
|
||
|
|
val loc : t -> Loc.t
|
||
|
|
val message : t -> unit Pp.t
|
||
|
|
val message_with_style : t -> User_message.Style.t Pp.t
|
||
|
|
end
|
||
|
|
|
||
|
|
type t
|
||
|
|
|
||
|
|
val related : t -> Related.t list
|
||
|
|
val loc : t -> Loc.t option
|
||
|
|
val id : t -> Id.t
|
||
|
|
val message : t -> unit Pp.t
|
||
|
|
val message_with_style : t -> User_message.Style.t Pp.t
|
||
|
|
val severity : t -> severity option
|
||
|
|
val promotion : t -> Promotion.t list
|
||
|
|
|
||
|
|
(* The list of targets is ordered such that the first element is the
|
||
|
|
immediate ("innermost") target being built when the error was
|
||
|
|
encountered, which was required by the next element, and so on. *)
|
||
|
|
val targets : t -> Target.t list
|
||
|
|
|
||
|
|
(* The directory from which the action producing the error was run. This is
|
||
|
|
often, but not always, the directory of the first target in [targets].
|
||
|
|
This path of this directory is absolute.
|
||
|
|
|
||
|
|
If this is [None], then the error does not have an associated error (for
|
||
|
|
example, if your opam installation is too old). *)
|
||
|
|
val directory : t -> string option
|
||
|
|
|
||
|
|
module Event : sig
|
||
|
|
type nonrec t =
|
||
|
|
| Add of t
|
||
|
|
| Remove of t
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
module Progress : sig
|
||
|
|
type t =
|
||
|
|
| Waiting
|
||
|
|
| In_progress of
|
||
|
|
{ complete : int
|
||
|
|
; remaining : int
|
||
|
|
; failed : int
|
||
|
|
}
|
||
|
|
| Failed
|
||
|
|
| Interrupted
|
||
|
|
| Success
|
||
|
|
end
|
||
|
|
|
||
|
|
module Job : sig
|
||
|
|
module Id : sig
|
||
|
|
type t
|
||
|
|
|
||
|
|
val compare : t -> t -> Ordering.t
|
||
|
|
val hash : t -> int
|
||
|
|
end
|
||
|
|
|
||
|
|
type t
|
||
|
|
|
||
|
|
val id : t -> Id.t
|
||
|
|
val description : t -> unit Pp.t
|
||
|
|
val started_at : t -> float
|
||
|
|
|
||
|
|
module Event : sig
|
||
|
|
type nonrec t =
|
||
|
|
| Start of t
|
||
|
|
| Stop of Id.t
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
module Sub : sig
|
||
|
|
type 'a t
|
||
|
|
|
||
|
|
val progress : Progress.t t
|
||
|
|
val diagnostic : Diagnostic.Event.t list t
|
||
|
|
end
|
||
|
|
|
||
|
|
module Message : sig
|
||
|
|
type t
|
||
|
|
|
||
|
|
val payload : t -> Csexp.t option
|
||
|
|
val message : t -> string
|
||
|
|
end
|
||
|
|
|
||
|
|
(** A [Version_error] is returned on the client-side when a request or
|
||
|
|
notification is determined to be invalid due to version negotiation (no
|
||
|
|
known method or no common version). *)
|
||
|
|
module Version_error : sig
|
||
|
|
type t
|
||
|
|
|
||
|
|
val payload : t -> Csexp.t option
|
||
|
|
val message : t -> string
|
||
|
|
|
||
|
|
exception E of t
|
||
|
|
end
|
||
|
|
|
||
|
|
module Notification : sig
|
||
|
|
type 'a t
|
||
|
|
|
||
|
|
(** Request dune to shutdown. The current build job will be cancelled. *)
|
||
|
|
val shutdown : unit t
|
||
|
|
end
|
||
|
|
|
||
|
|
module Request : sig
|
||
|
|
type ('a, 'b) t
|
||
|
|
|
||
|
|
val ping : (unit, unit) t
|
||
|
|
val diagnostics : (unit, Diagnostic.t list) t
|
||
|
|
|
||
|
|
(** format a [dune], [dune-project], or a [dune-workspace] file. The full
|
||
|
|
path to the file is necessary so that dune knows the formatting options
|
||
|
|
for the project this file is in *)
|
||
|
|
val format_dune_file : (Path.t * [ `Contents of string ], string) t
|
||
|
|
|
||
|
|
(** Promote a file. *)
|
||
|
|
val promote : (Path.t, unit) t
|
||
|
|
|
||
|
|
(** Returns the location of the build directory for the current build. *)
|
||
|
|
val build_dir : (unit, Path.t) t
|
||
|
|
end
|
||
|
|
|
||
|
|
module Client : sig
|
||
|
|
module type S = sig
|
||
|
|
(** Rpc client *)
|
||
|
|
|
||
|
|
type t
|
||
|
|
type 'a fiber
|
||
|
|
type chan
|
||
|
|
|
||
|
|
module Handler : sig
|
||
|
|
type t
|
||
|
|
|
||
|
|
val create
|
||
|
|
: ?log:(Message.t -> unit fiber)
|
||
|
|
-> ?abort:(Message.t -> unit fiber)
|
||
|
|
(** If [abort] is called, the server has terminated the
|
||
|
|
connection due to a protocol error. This should never be
|
||
|
|
called unless there's a server side bug. *)
|
||
|
|
-> unit
|
||
|
|
-> t
|
||
|
|
end
|
||
|
|
|
||
|
|
(** Individual RPC procedures are versioned beyond the larger API version.
|
||
|
|
At session startup, the server and client exchange version information
|
||
|
|
for each method ("negotiation"), setting on a common version for each
|
||
|
|
(if possible) to produce a "version menu".
|
||
|
|
|
||
|
|
To initiate a method, then, that method must be looked up in the
|
||
|
|
version menu to determine the correct protocol for this session. This
|
||
|
|
module stages this pattern to share the lookup for all calls to the
|
||
|
|
same procedure.
|
||
|
|
|
||
|
|
For lower-level design details, see [doc/dev/rpc-versioning.md] in the
|
||
|
|
main dune repository. *)
|
||
|
|
module Versioned : sig
|
||
|
|
type 'a notification
|
||
|
|
type ('a, 'b) request
|
||
|
|
|
||
|
|
(** [prepare_request client r] checks the request [r] against the
|
||
|
|
negotiated version menu, giving a versioned request as a result.
|
||
|
|
|
||
|
|
This function does not initiate any communication with the server.
|
||
|
|
However, as this function must check the version menu, it cannot
|
||
|
|
complete until after version negotiation, and so returns a [fiber]. *)
|
||
|
|
val prepare_request
|
||
|
|
: t
|
||
|
|
-> ('a, 'b) Request.t
|
||
|
|
-> (('a, 'b) request, Version_error.t) result fiber
|
||
|
|
|
||
|
|
(** See [prepare_request]. *)
|
||
|
|
val prepare_notification
|
||
|
|
: t
|
||
|
|
-> 'a Notification.t
|
||
|
|
-> ('a notification, Version_error.t) result fiber
|
||
|
|
end
|
||
|
|
|
||
|
|
(** [request ?id client decl req] send a request [req] specified by [decl]
|
||
|
|
to [client]. If [id] is [None], it will be automatically generated. *)
|
||
|
|
val request
|
||
|
|
: ?id:Id.t
|
||
|
|
-> t
|
||
|
|
-> ('a, 'b) Versioned.request
|
||
|
|
-> 'a
|
||
|
|
-> ('b, Response.Error.t) result fiber
|
||
|
|
|
||
|
|
val notification : t -> 'a Versioned.notification -> 'a -> unit fiber
|
||
|
|
|
||
|
|
(** [disconnected client] produces a fiber that only becomes determined
|
||
|
|
when the session is ended from the server side (such as if the build
|
||
|
|
server is killed entirely). *)
|
||
|
|
val disconnected : t -> unit fiber
|
||
|
|
|
||
|
|
module Stream : sig
|
||
|
|
(** Control for a polling loop *)
|
||
|
|
|
||
|
|
type 'a t
|
||
|
|
|
||
|
|
(** [cancel t] notify the server that we are stopping our polling loop.
|
||
|
|
It is an error to call [next] after [cancel] *)
|
||
|
|
val cancel : _ t -> unit fiber
|
||
|
|
|
||
|
|
(** [next t] poll for the next value. It is an error to call [next]
|
||
|
|
again until the previous [next] terminated. If [next] returns
|
||
|
|
[None], subsequent calls to [next] is forbidden. *)
|
||
|
|
val next : 'a t -> 'a option fiber
|
||
|
|
end
|
||
|
|
|
||
|
|
(** [poll client sub] Initialize a polling loop for [sub] *)
|
||
|
|
val poll : ?id:Id.t -> t -> 'a Sub.t -> ('a Stream.t, Version_error.t) result fiber
|
||
|
|
|
||
|
|
module Batch : sig
|
||
|
|
type client := t
|
||
|
|
type t
|
||
|
|
|
||
|
|
val create : client -> t
|
||
|
|
|
||
|
|
val request
|
||
|
|
: ?id:Id.t
|
||
|
|
-> t
|
||
|
|
-> ('a, 'b) Versioned.request
|
||
|
|
-> 'a
|
||
|
|
-> ('b, Response.Error.t) result fiber
|
||
|
|
|
||
|
|
val notification : t -> 'a Versioned.notification -> 'a -> unit
|
||
|
|
val submit : t -> unit fiber
|
||
|
|
end
|
||
|
|
|
||
|
|
(** [connect ?on_handler session init ~f] connect to [session], initialize
|
||
|
|
with [init] and call [f] once the client is initialized. [handler] is
|
||
|
|
called for some notifications sent to [session] *)
|
||
|
|
val connect
|
||
|
|
: ?handler:Handler.t
|
||
|
|
-> chan
|
||
|
|
-> Initialize.t
|
||
|
|
-> f:(t -> 'a fiber)
|
||
|
|
-> 'a fiber
|
||
|
|
end
|
||
|
|
|
||
|
|
(** Functor to create a client implementation *)
|
||
|
|
module Make
|
||
|
|
(Fiber : sig
|
||
|
|
type 'a t
|
||
|
|
|
||
|
|
val return : 'a -> 'a t
|
||
|
|
val fork_and_join_unit : (unit -> unit t) -> (unit -> 'a t) -> 'a t
|
||
|
|
val parallel_iter : (unit -> 'a option t) -> f:('a -> unit t) -> unit t
|
||
|
|
val finalize : (unit -> 'a t) -> finally:(unit -> unit t) -> 'a t
|
||
|
|
|
||
|
|
module O : sig
|
||
|
|
val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
|
||
|
|
val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
|
||
|
|
end
|
||
|
|
|
||
|
|
module Ivar : sig
|
||
|
|
type 'a fiber := 'a t
|
||
|
|
type 'a t
|
||
|
|
|
||
|
|
val create : unit -> 'a t
|
||
|
|
val read : 'a t -> 'a fiber
|
||
|
|
val fill : 'a t -> 'a -> unit fiber
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
(Chan : sig
|
||
|
|
type t
|
||
|
|
|
||
|
|
(* [write t x] writes the s-expression when [x] is [Some _], and closes
|
||
|
|
the session if [x = None] *)
|
||
|
|
val write : t -> Csexp.t list option -> unit Fiber.t
|
||
|
|
|
||
|
|
(* [read t] attempts to read from [t]. If an s-expression is read, it is
|
||
|
|
returned as [Some sexp], otherwise [None] is returned and the session
|
||
|
|
is closed. *)
|
||
|
|
val read : t -> Csexp.t option Fiber.t
|
||
|
|
end) : S with type 'a fiber := 'a Fiber.t and type chan := Chan.t
|
||
|
|
end
|
||
|
|
|
||
|
|
module Where : sig
|
||
|
|
(** represents the address where a dune rpc instance might be listening *)
|
||
|
|
|
||
|
|
type t =
|
||
|
|
[ `Unix of string
|
||
|
|
| `Ip of [ `Host of string ] * [ `Port of int ]
|
||
|
|
]
|
||
|
|
|
||
|
|
type error = Invalid_where of string
|
||
|
|
|
||
|
|
exception E of error
|
||
|
|
|
||
|
|
module type S = sig
|
||
|
|
type 'a fiber
|
||
|
|
|
||
|
|
val get
|
||
|
|
: env:(string -> string option)
|
||
|
|
-> build_dir:string
|
||
|
|
-> (t option, exn) result fiber
|
||
|
|
|
||
|
|
val default : ?win32:bool -> build_dir:string -> unit -> t
|
||
|
|
end
|
||
|
|
|
||
|
|
(** obtain the address from the build directory and environment *)
|
||
|
|
module Make
|
||
|
|
(Fiber : sig
|
||
|
|
type 'a t
|
||
|
|
|
||
|
|
val return : 'a -> 'a t
|
||
|
|
|
||
|
|
module O : sig
|
||
|
|
val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
|
||
|
|
val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
(_ : sig
|
||
|
|
val read_file : string -> (string, exn) result Fiber.t
|
||
|
|
|
||
|
|
val analyze_path
|
||
|
|
: string
|
||
|
|
-> ([ `Unix_socket | `Normal_file | `Other ], exn) result Fiber.t
|
||
|
|
end) : S with type 'a fiber := 'a Fiber.t
|
||
|
|
end
|
||
|
|
|
||
|
|
module Registry : sig
|
||
|
|
(** The registry is where all running instances of dune rpc are stored.
|
||
|
|
|
||
|
|
It's used by clients to determine which dune rpc instance corresponds to
|
||
|
|
the workspace they're trying to edit. *)
|
||
|
|
|
||
|
|
module Dune : sig
|
||
|
|
(** a registered instance of dune. supposedly running and listening to rpc
|
||
|
|
connections *)
|
||
|
|
|
||
|
|
type t
|
||
|
|
|
||
|
|
val to_dyn : t -> Dyn.t
|
||
|
|
val compare : t -> t -> Ordering.t
|
||
|
|
val pid : t -> int
|
||
|
|
val where : t -> Where.t
|
||
|
|
val root : t -> string
|
||
|
|
end
|
||
|
|
|
||
|
|
module Config : sig
|
||
|
|
(** The registry directory is located using xdg *)
|
||
|
|
|
||
|
|
type t
|
||
|
|
|
||
|
|
val create : Xdg.t -> t
|
||
|
|
val watch_dir : t -> string
|
||
|
|
end
|
||
|
|
|
||
|
|
type t
|
||
|
|
|
||
|
|
val create : Config.t -> t
|
||
|
|
|
||
|
|
(** currently detected running instances *)
|
||
|
|
val current : t -> Dune.t list
|
||
|
|
|
||
|
|
module Refresh : sig
|
||
|
|
(** the result of polling the registry *)
|
||
|
|
|
||
|
|
type t
|
||
|
|
|
||
|
|
val added : t -> Dune.t list
|
||
|
|
val removed : t -> Dune.t list
|
||
|
|
val errored : t -> (string * exn) list
|
||
|
|
end
|
||
|
|
|
||
|
|
(** we can poll the registry efficiently using the following functor *)
|
||
|
|
module Poll
|
||
|
|
(Fiber : sig
|
||
|
|
type 'a t
|
||
|
|
|
||
|
|
val return : 'a -> 'a t
|
||
|
|
val parallel_map : 'a list -> f:('a -> 'b t) -> 'b list t
|
||
|
|
|
||
|
|
module O : sig
|
||
|
|
val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
|
||
|
|
val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
(_ : sig
|
||
|
|
val scandir : string -> (string list, exn) result Fiber.t
|
||
|
|
val stat : string -> ([ `Mtime of float ], exn) result Fiber.t
|
||
|
|
val read_file : string -> (string, exn) result Fiber.t
|
||
|
|
end) : sig
|
||
|
|
val poll : t -> (Refresh.t, exn) result Fiber.t
|
||
|
|
end
|
||
|
|
end
|