diff --git a/lwt/Lwt/index.html b/lwt/Lwt/index.html index 0da4c396..75fd7215 100644 --- a/lwt/Lwt/index.html +++ b/lwt/Lwt/index.html @@ -74,7 +74,7 @@ Lwt_io.printl "Only 2 more seconds passed"))) end -(* ocamlfind opt -linkpkg -thread -package lwt.unix code.ml && ./a.out *)

And that's it! Concurrency in Lwt is simply a matter of whether you start an operation in the callback of another one or not. As a convenience, Lwt provides a few helpers for common concurrency patterns.

Execution model

It's important to understand that promises are a pure-OCaml data type. They don't do any fancy scheduling or I/O. They are just lists of callbacks (if pending), or containers for one value (if resolved).

The interesting function is Lwt_main.run. It's a wrapper around select(2), epoll(7), kqueue(2), or whatever asynchronous I/O API your system provides. On browsers, the work of Lwt_main.run is done by the surrounding JavaScript engine, so you don't call Lwt_main.run from inside your program. But the execution model is still the same, and the description below applies!

To avoid writing out “underlying asynchronous I/O API,” we'll assume, in this section, that the API is select(2). That's just for the sake of abbreviation. It doesn't actually matter, for most purposes, what the underlying I/O API is.

Let's use the program from the tutorial that reads two lines as an example. Here it is, again, in its desugared form:

let () =
+(* ocamlfind opt -linkpkg -thread -package lwt.unix code.ml && ./a.out *)

And that's it! Concurrency in Lwt is simply a matter of whether you start an operation in the callback of another one or not. As a convenience, Lwt provides a few helpers for common concurrency patterns.

Execution model

It's important to understand that promises are a pure-OCaml data type. They don't do any fancy scheduling or I/O. They are just lists of callbacks (if pending), or containers for one value (if resolved).

The interesting function is Lwt_main.run. It's a wrapper around select(2), epoll(7), kqueue(2), or whatever asynchronous I/O API your system provides. On browsers, the work of Lwt_main.run is done by the surrounding JavaScript engine, so you don't call Lwt_main.run from inside your program. But the execution model is still the same, and the description below applies!

To avoid writing out “underlying asynchronous I/O API,” we'll assume, in this section, that the API is select(2). That's just for the sake of abbreviation. It doesn't actually matter, for most purposes, what the underlying I/O API is.

Let's use the program from the tutorial that reads two lines as an example. Here it is, again, in its desugared form:

let () =
   let p : unit Lwt.t =
     let line_1_promise : string Lwt.t = Lwt_io.(read_line stdin) in
     Lwt.bind line_1_promise (fun (line_1 : string) ->
diff --git a/lwt/Lwt_engine/Ev_backend/index.html b/lwt/Lwt_engine/Ev_backend/index.html
index 62e6e358..00f3866e 100644
--- a/lwt/Lwt_engine/Ev_backend/index.html
+++ b/lwt/Lwt_engine/Ev_backend/index.html
@@ -1,2 +1,2 @@
 
-Ev_backend (lwt.Lwt_engine.Ev_backend)

Module Lwt_engine.Ev_backend

type t
val default : t
val select : t
val poll : t
val epoll : t
val kqueue : t
val devpoll : t
val port : t
val pp : Stdlib.Format.formatter -> t -> unit
+Ev_backend (lwt.Lwt_engine.Ev_backend)

Module Lwt_engine.Ev_backend

type t
val default : t
val select : t
val poll : t
val epoll : t
val kqueue : t
val devpoll : t
val port : t
val equal : t -> t -> bool
val pp : Stdlib.Format.formatter -> t -> unit
diff --git a/lwt/Lwt_engine/Versioned/class-libev_1/index.html b/lwt/Lwt_engine/Versioned/class-libev_1/index.html index 4c3a891a..367ddb31 100644 --- a/lwt/Lwt_engine/Versioned/class-libev_1/index.html +++ b/lwt/Lwt_engine/Versioned/class-libev_1/index.html @@ -1,2 +1,2 @@ -libev_1 (lwt.Lwt_engine.Versioned.libev_1)

Class Versioned.libev_1

Old version of Lwt_engine.libev. The current Lwt_engine.libev allows selecting the libev back end.

  • since 2.7.0
inherit t
val loop : ev_loop
method loop : ev_loop
+libev_1 (lwt.Lwt_engine.Versioned.libev_1)

Class Versioned.libev_1

Old version of Lwt_engine.libev. The current Lwt_engine.libev allows selecting the libev back end.

  • since 2.7.0
inherit t
val loop : ev_loop
method backend : Ev_backend.t
method loop : ev_loop
diff --git a/lwt/Lwt_engine/Versioned/class-libev_2/index.html b/lwt/Lwt_engine/Versioned/class-libev_2/index.html index 09b4189f..ef59fa69 100644 --- a/lwt/Lwt_engine/Versioned/class-libev_2/index.html +++ b/lwt/Lwt_engine/Versioned/class-libev_2/index.html @@ -1,2 +1,2 @@ -libev_2 (lwt.Lwt_engine.Versioned.libev_2)

Class Versioned.libev_2

Since Lwt 3.0.0, this is just an alias for Lwt_engine.libev.

  • since 2.7.0
inherit t
val loop : ev_loop
method loop : ev_loop
+libev_2 (lwt.Lwt_engine.Versioned.libev_2)

Class Versioned.libev_2

Since Lwt 3.0.0, this is just an alias for Lwt_engine.libev.

  • since 2.7.0
inherit t
val loop : ev_loop
method backend : Ev_backend.t
method loop : ev_loop
diff --git a/lwt/Lwt_engine/class-libev/index.html b/lwt/Lwt_engine/class-libev/index.html index 11b9ce1b..42c68b27 100644 --- a/lwt/Lwt_engine/class-libev/index.html +++ b/lwt/Lwt_engine/class-libev/index.html @@ -1,2 +1,2 @@ -libev (lwt.Lwt_engine.libev)

Class Lwt_engine.libev

Engine based on libev. If not compiled with libev support, the creation of the class will raise Lwt_sys.Not_available.

inherit t
val loop : ev_loop

The libev loop used for this engine.

method loop : ev_loop

Returns loop.

+libev (lwt.Lwt_engine.libev)

Class Lwt_engine.libev

Engine based on libev. If not compiled with libev support, the creation of the class will raise Lwt_sys.Not_available.

inherit t
method backend : Ev_backend.t

The backend picked by libev.

val loop : ev_loop

The libev loop used for this engine.

method loop : ev_loop

Returns loop.

diff --git a/lwt/Lwt_fmt/index.html b/lwt/Lwt_fmt/index.html index f69e7add..75bd1b02 100644 --- a/lwt/Lwt_fmt/index.html +++ b/lwt/Lwt_fmt/index.html @@ -1,5 +1,5 @@ -Lwt_fmt (lwt.Lwt_fmt)

Module Lwt_fmt

Format API for Lwt-powered IOs

  • since 4.1.0

This module bridges the gap between Stdlib.Format and Lwt. Although it is not required, it is recommended to use this module with the Fmt library.

Compared to regular formatting function, the main difference is that printing statements will now return promises instead of blocking.

val printf : ('a, Stdlib.Format.formatter, unit, unit Lwt.t) format4 -> 'a

Returns a promise that prints on the standard output. Similar to Stdlib.Format.printf.

val eprintf : ('a, Stdlib.Format.formatter, unit, unit Lwt.t) format4 -> 'a

Returns a promise that prints on the standard error. Similar to Stdlib.Format.eprintf.

Formatters

type formatter

Lwt enabled formatters

type order =
  1. | String of string * int * int
    (*

    String (s, off, len) indicate the output of s at offset off and length len.

    *)
  2. | Flush
    (*

    Flush operation

    *)
val make_stream : unit -> order Lwt_stream.t * formatter

make_stream () returns a formatter and a stream of all the writing order given on that stream.

val of_channel : Lwt_io.output_channel -> formatter

of_channel oc creates a formatter that writes to the channel oc.

val stdout : formatter

Formatter printing on Lwt_io.stdout.

val stderr : formatter

Formatter printing on Lwt_io.stdout.

val make_formatter : +Lwt_fmt (lwt.Lwt_fmt)

Module Lwt_fmt

Format API for Lwt-powered IOs

  • since 4.1.0

This module bridges the gap between Stdlib.Format and Lwt. Although it is not required, it is recommended to use this module with the Fmt library.

Compared to regular formatting function, the main difference is that printing statements will now return promises instead of blocking.

val printf : ('a, Stdlib.Format.formatter, unit, unit Lwt.t) format4 -> 'a

Returns a promise that prints on the standard output. Similar to Stdlib.Format.printf.

val eprintf : ('a, Stdlib.Format.formatter, unit, unit Lwt.t) format4 -> 'a

Returns a promise that prints on the standard error. Similar to Stdlib.Format.eprintf.

Formatters

type formatter

Lwt enabled formatters

type order =
  1. | String of string * int * int
    (*

    String (s, off, len) indicate the output of s at offset off and length len.

    *)
  2. | Flush
    (*

    Flush operation

    *)
val make_stream : unit -> order Lwt_stream.t * formatter

make_stream () returns a formatter and a stream of all the writing order given on that stream.

val of_channel : Lwt_io.output_channel -> formatter

of_channel oc creates a formatter that writes to the channel oc.

val stdout : formatter

Formatter printing on Lwt_io.stdout.

val stderr : formatter

Formatter printing on Lwt_io.stdout.

val make_formatter : commit:(unit -> unit Lwt.t) -> fmt:Stdlib.Format.formatter -> unit -> diff --git a/lwt/Lwt_pqueue/index.html b/lwt/Lwt_pqueue/index.html index 27fa40d1..567b0f68 100644 --- a/lwt/Lwt_pqueue/index.html +++ b/lwt/Lwt_pqueue/index.html @@ -1,2 +1,2 @@ -Lwt_pqueue (lwt.Lwt_pqueue)

Module Lwt_pqueue

Functional priority queues (deprecated).

A priority queue maintains, in the abstract sense, a set of elements in order, and supports fast lookup and removal of the first (“minimum”) element. This is used in Lwt for organizing threads that are waiting for timeouts.

The priority queues in this module preserve “duplicates”: elements that compare equal in their order.

  • deprecated

    This module is an internal implementation detail of Lwt, and may be removed from the API at some point in the future. For alternatives, see, for example: Heaps by Jean-Cristophe Filliatre, containers, Batteries, or psq.

module type OrderedType = sig ... end

Signature pairing an element type with an ordering function.

module type S = sig ... end

Signature of priority queues.

module Make (Ord : OrderedType) : S with type elt = Ord.t

Generates priority queue types from ordered types.

+Lwt_pqueue (lwt.Lwt_pqueue)

Module Lwt_pqueue

Functional priority queues (deprecated).

A priority queue maintains, in the abstract sense, a set of elements in order, and supports fast lookup and removal of the first (“minimum”) element. This is used in Lwt for organizing threads that are waiting for timeouts.

The priority queues in this module preserve “duplicates”: elements that compare equal in their order.

  • deprecated

    This module is an internal implementation detail of Lwt, and may be removed from the API at some point in the future. For alternatives, see, for example: Heaps by Jean-Cristophe Filliatre, containers, Batteries, or psq.

module type OrderedType = sig ... end

Signature pairing an element type with an ordering function.

module type S = sig ... end

Signature of priority queues.

module Make (Ord : OrderedType) : S with type elt = Ord.t

Generates priority queue types from ordered types.

diff --git a/lwt/Lwt_stream/class-type-bounded_push/index.html b/lwt/Lwt_stream/class-type-bounded_push/index.html index ff9be9fe..780c3924 100644 --- a/lwt/Lwt_stream/class-type-bounded_push/index.html +++ b/lwt/Lwt_stream/class-type-bounded_push/index.html @@ -1,2 +1,2 @@ -bounded_push (lwt.Lwt_stream.bounded_push)

Class type Lwt_stream.bounded_push

Type of sources for bounded push-streams.

method size : int

Size of the stream.

method resize : int -> unit

Change the size of the stream queue. Note that the new size can smaller than the current stream queue size.

It raises Stdlib.Invalid_argument if size < 0.

method push : 'a -> unit Lwt.t

Pushes a new element to the stream. If the stream is full then it will block until one element is consumed. If another thread is already blocked on push, it raises Lwt_stream.Full.

method close : unit

Closes the stream. Any thread currently blocked on Lwt_stream.bounded_push.push fails with Lwt_stream.Closed.

method count : int

Number of elements in the stream queue.

method blocked : bool

Is a thread is blocked on Lwt_stream.bounded_push.push ?

method closed : bool

Is the stream closed ?

method set_reference : 'a. 'a -> unit

Set the reference to an external source.

+bounded_push (lwt.Lwt_stream.bounded_push)

Class type Lwt_stream.bounded_push

Type of sources for bounded push-streams.

method size : int

Size of the stream.

method resize : int -> unit

Change the size of the stream queue. Note that the new size can smaller than the current stream queue size.

It raises Stdlib.Invalid_argument if size < 0.

method push : 'a -> unit Lwt.t

Pushes a new element to the stream. If the stream is full then it will block until one element is consumed. If another thread is already blocked on push, it raises Lwt_stream.Full.

method close : unit

Closes the stream. Any thread currently blocked on a call to the push method fails with Lwt_stream.Closed.

method count : int

Number of elements in the stream queue.

method blocked : bool

Is a thread is blocked on a call to the push method?

method closed : bool

Is the stream closed?

method set_reference : 'a. 'a -> unit

Set the reference to an external source.

diff --git a/lwt/Lwt_stream/index.html b/lwt/Lwt_stream/index.html index 47eb687c..12e266c1 100644 --- a/lwt/Lwt_stream/index.html +++ b/lwt/Lwt_stream/index.html @@ -1,5 +1,5 @@ -Lwt_stream (lwt.Lwt_stream)

Module Lwt_stream

Data streams

type 'a t

A stream holding values of type 'a.

Naming convention: in this module, all functions applying a function to each element of a stream are suffixed by:

  • _s when the function returns a thread and calls are serialised
  • _p when the function returns a thread and calls are parallelised

Construction

val from : (unit -> 'a option Lwt.t) -> 'a t

from f creates a stream from the given input function. f is called each time more input is needed, and the stream ends when f returns None.

If f, or the thread produced by f, raises an exception, that exception is forwarded to the consumer of the stream (for example, a caller of get). Note that this does not end the stream. A subsequent attempt to read from the stream will cause another call to f, which may succeed with a value.

val from_direct : (unit -> 'a option) -> 'a t

from_direct f does the same as from but with a function that does not return a thread. It is preferred that this function be used rather than wrapping f into a function which returns a thread.

The behavior when f raises an exception is the same as for from, except that f does not produce a thread.

exception Closed

Exception raised by the push function of a push-stream when pushing an element after the end of stream (= None) has been pushed.

val create : unit -> 'a t * ('a option -> unit)

create () returns a new stream and a push function.

To notify the stream's consumer of errors, either use a separate communication channel, or use a Stdlib.result stream. There is no way to push an exception into a push-stream.

val create_with_reference : unit -> 'a t * ('a option -> unit) * ('b -> unit)

create_with_reference () returns a new stream and a push function. The last function allows a reference to be set to an external source. This prevents the external source from being garbage collected.

For example, to convert a reactive event to a stream:

let stream, push, set_ref = Lwt_stream.create_with_reference () in
+Lwt_stream (lwt.Lwt_stream)

Module Lwt_stream

Data streams

type 'a t

A stream holding values of type 'a.

Naming convention: in this module, all functions applying a function to each element of a stream are suffixed by:

  • _s when the function returns a thread and calls are serialised
  • _p when the function returns a thread and calls are parallelised

Construction

val from : (unit -> 'a option Lwt.t) -> 'a t

from f creates a stream from the given input function. f is called each time more input is needed, and the stream ends when f returns None.

If f, or the thread produced by f, raises an exception, that exception is forwarded to the consumer of the stream (for example, a caller of get). Note that this does not end the stream. A subsequent attempt to read from the stream will cause another call to f, which may succeed with a value.

val from_direct : (unit -> 'a option) -> 'a t

from_direct f does the same as from but with a function that does not return a thread. It is preferred that this function be used rather than wrapping f into a function which returns a thread.

The behavior when f raises an exception is the same as for from, except that f does not produce a thread.

exception Closed

Exception raised by the push function of a push-stream when pushing an element after the end of stream (= None) has been pushed.

val create : unit -> 'a t * ('a option -> unit)

create () returns a new stream and a push function.

To notify the stream's consumer of errors, either use a separate communication channel, or use a Stdlib.result stream. There is no way to push an exception into a push-stream.

val create_with_reference : unit -> 'a t * ('a option -> unit) * ('b -> unit)

create_with_reference () returns a new stream and a push function. The last function allows a reference to be set to an external source. This prevents the external source from being garbage collected.

For example, to convert a reactive event to a stream:

let stream, push, set_ref = Lwt_stream.create_with_reference () in
 set_ref (map_event push event)
exception Full

Exception raised by the push function of a bounded push-stream when the stream queue is full and a thread is already waiting to push an element.

class type 'a bounded_push = object ... end

Type of sources for bounded push-streams.

val create_bounded : int -> 'a t * 'a bounded_push

create_bounded size returns a new stream and a bounded push source. The stream can hold a maximum of size elements. When this limit is reached, pushing a new element will block until one is consumed.

Note that you cannot clone or parse (with parse) a bounded stream. These functions will raise Invalid_argument if you try to do so.

It raises Invalid_argument if size < 0.

val return : 'a -> 'a t

return a creates a stream containing the value a and being immediately closed stream (in the sense of is_closed).

  • since 5.5.0
val return_lwt : 'a Lwt.t -> 'a t

return_lwt l creates a stream returning the value that l resolves to. The value is pushed into the stream immediately after the promise becomes resolved and the stream is then immediately closed (in the sense of is_closed).

If, instead, l becomes rejected, then the stream is closed without any elements in it. Attempting to fetch elements from it will raise Empty.

  • since 5.5.0
val of_seq : 'a Stdlib.Seq.t -> 'a t

of_seq s creates a stream returning all elements of s. The elements are evaluated from s and pushed onto the stream as the stream is consumed.

  • since 4.2.0
val of_lwt_seq : 'a Lwt_seq.t -> 'a t

of_lwt_seq s creates a stream returning all elements of s. The elements are evaluated from s and pushed onto the stream as the stream is consumed.

  • since 5.5.0
val of_list : 'a list -> 'a t

of_list l creates a stream returning all elements of l. The elements are pushed into the stream immediately, resulting in a closed stream (in the sense of is_closed).

val of_array : 'a array -> 'a t

of_array a creates a stream returning all elements of a. The elements are pushed into the stream immediately, resulting in a closed stream (in the sense of is_closed).

val of_string : string -> char t

of_string str creates a stream returning all characters of str. The characters are pushed into the stream immediately, resulting in a closed stream (in the sense of is_closed).

val clone : 'a t -> 'a t

clone st clone the given stream. Operations on each stream will not affect the other.

For example:

# let st1 = Lwt_stream.of_list [1; 2; 3];;
 val st1 : int Lwt_stream.t = <abstr>
 # let st2 = Lwt_stream.clone st1;;
@@ -7,7 +7,7 @@ val st2 : int Lwt_stream.t = <abstr>
 # lwt x = Lwt_stream.next st1;;
 val x : int = 1
 # lwt y = Lwt_stream.next st2;;
-val y : int = 1

It raises Invalid_argument if st is a bounded push-stream.

Destruction

val to_list : 'a t -> 'a list Lwt.t

Returns the list of elements of the given stream

val to_string : char t -> string Lwt.t

Returns the word composed of all characters of the given stream

Data retrieval

exception Empty

Exception raised when trying to retrieve data from an empty stream.

val peek : 'a t -> 'a option Lwt.t

peek st returns the first element of the stream, if any, without removing it.

val npeek : int -> 'a t -> 'a list Lwt.t

npeek n st returns at most the first n elements of st, without removing them.

val get : 'a t -> 'a option Lwt.t

get st removes and returns the first element of the stream, if any.

val nget : int -> 'a t -> 'a list Lwt.t

nget n st removes and returns at most the first n elements of st.

val get_while : ('a -> bool) -> 'a t -> 'a list Lwt.t
val get_while_s : ('a -> bool Lwt.t) -> 'a t -> 'a list Lwt.t

get_while f st returns the longest prefix of st where all elements satisfy f.

val next : 'a t -> 'a Lwt.t

next st removes and returns the next element of the stream or fails with Empty, if the stream is empty.

val last_new : 'a t -> 'a Lwt.t

last_new st returns the last element that can be obtained without sleeping, or wait for one if none is available.

It fails with Empty if the stream has no more elements.

val junk : 'a t -> unit Lwt.t

junk st removes the first element of st.

val njunk : int -> 'a t -> unit Lwt.t

njunk n st removes at most the first n elements of the stream.

val junk_while : ('a -> bool) -> 'a t -> unit Lwt.t
val junk_while_s : ('a -> bool Lwt.t) -> 'a t -> unit Lwt.t

junk_while f st removes all elements at the beginning of the streams which satisfy f.

val junk_old : 'a t -> unit Lwt.t

junk_old st removes all elements that are ready to be read without yielding from st.

val get_available : 'a t -> 'a list

get_available st returns all available elements of l without blocking.

val get_available_up_to : int -> 'a t -> 'a list

get_available_up_to n st returns up to n elements of l without blocking.

val is_empty : 'a t -> bool Lwt.t

is_empty st returns whether the given stream is empty.

val is_closed : 'a t -> bool

is_closed st returns whether the given stream has been closed. A closed stream is not necessarily empty. It may still contain unread elements. If is_closed s = true, then all subsequent reads until the end of the stream are guaranteed not to block.

  • since 2.6.0
val closed : 'a t -> unit Lwt.t

closed st returns a thread that will sleep until the stream has been closed.

  • since 2.6.0

Stream transversal

Note: all the following functions are destructive.

For example:

# let st1 = Lwt_stream.of_list [1; 2; 3];;
+val y : int = 1

It raises Invalid_argument if st is a bounded push-stream.

Destruction

val to_list : 'a t -> 'a list Lwt.t

Returns the list of elements of the given stream

val to_string : char t -> string Lwt.t

Returns the word composed of all characters of the given stream

Data retrieval

exception Empty

Exception raised when trying to retrieve data from an empty stream.

val peek : 'a t -> 'a option Lwt.t

peek st returns the first element of the stream, if any, without removing it.

val npeek : int -> 'a t -> 'a list Lwt.t

npeek n st returns at most the first n elements of st, without removing them.

val get : 'a t -> 'a option Lwt.t

get st removes and returns the first element of the stream, if any.

val nget : int -> 'a t -> 'a list Lwt.t

nget n st removes and returns at most the first n elements of st.

val get_while : ('a -> bool) -> 'a t -> 'a list Lwt.t
val get_while_s : ('a -> bool Lwt.t) -> 'a t -> 'a list Lwt.t

get_while f st returns the longest prefix of st where all elements satisfy f.

val next : 'a t -> 'a Lwt.t

next st removes and returns the next element of the stream or fails with Empty, if the stream is empty.

val last_new : 'a t -> 'a Lwt.t

last_new st returns the last element that can be obtained without sleeping, or wait for one if none is available.

It fails with Empty if the stream has no more elements.

val junk : 'a t -> unit Lwt.t

junk st removes the first element of st.

val njunk : int -> 'a t -> unit Lwt.t

njunk n st removes at most the first n elements of the stream.

val junk_while : ('a -> bool) -> 'a t -> unit Lwt.t
val junk_while_s : ('a -> bool Lwt.t) -> 'a t -> unit Lwt.t

junk_while f st removes all elements at the beginning of the streams which satisfy f.

val junk_available : 'a t -> unit

junk_available st removes all elements that are ready to be read without yielding from st.

val get_available : 'a t -> 'a list

get_available st returns all available elements of l without blocking.

val get_available_up_to : int -> 'a t -> 'a list

get_available_up_to n st returns up to n elements of l without blocking.

val is_empty : 'a t -> bool Lwt.t

is_empty st returns whether the given stream is empty.

val is_closed : 'a t -> bool

is_closed st returns whether the given stream has been closed. A closed stream is not necessarily empty. It may still contain unread elements. If is_closed s = true, then all subsequent reads until the end of the stream are guaranteed not to block.

  • since 2.6.0
val closed : 'a t -> unit Lwt.t

closed st returns a thread that will sleep until the stream has been closed.

  • since 2.6.0

Deprecated

val junk_old : 'a t -> unit Lwt.t
  • deprecated

    junk_old st is Lwt.return (junk_available st).

Stream transversal

Note: all the following functions are destructive.

For example:

# let st1 = Lwt_stream.of_list [1; 2; 3];;
 val st1 : int Lwt_stream.t = <abstr>
 # let st2 = Lwt_stream.map string_of_int st1;;
 val st2 : string Lwt_stream.t = <abstr>
diff --git a/lwt/Lwt_unix/index.html b/lwt/Lwt_unix/index.html
index aa8e1e6d..e8788077 100644
--- a/lwt/Lwt_unix/index.html
+++ b/lwt/Lwt_unix/index.html
@@ -15,7 +15,7 @@
   file_offset:int ->
   int ->
   int ->
-  int Lwt.t

See pwrite.

module IO_vectors : sig ... end

Sequences of buffer slices for writev.

val readv : file_descr -> IO_vectors.t -> int Lwt.t

readv fd vs reads bytes from fd into the buffer slices vs. If the operation completes successfully, the resulting promise resolves to the number of bytes read.

Data is always read directly into Bigarray slices. If the Unix file descriptor underlying fd is in non-blocking mode, data is also read directly into bytes slices. Otherwise, data for bytes slices is first read into temporary buffers, then copied.

Note that the returned Lwt promise is pending until failure or a successful read, even if the underlying file descriptor is in non-blocking mode. See of_unix_file_descr for a discussion of non-blocking I/O and Lwt.

If IO_vectors.system_limit is Some n and the count of slices in vs exceeds n, then Lwt_unix.readv reads only into the first n slices of vs.

Not implemented on Windows. It should be possible to implement, upon request, for Windows sockets only.

See readv(3p).

  • since 2.7.0
val writev : file_descr -> IO_vectors.t -> int Lwt.t

writev fd vs writes the bytes in the buffer slices vs to the file descriptor fd. If the operation completes successfully, the resulting promise resolves to the number of bytes written.

If the Unix file descriptor underlying fd is in non-blocking mode, writev does not make a copy the bytes before writing. Otherwise, it copies bytes slices, but not Bigarray slices.

Note that the returned Lwt promise is pending until failure or a successful write, even if the underlying descriptor is in non-blocking mode. See of_unix_file_descr for a discussion of non-blocking I/O and Lwt.

If IO_vectors.system_limit is Some n and the count of slices in vs exceeds n, then Lwt_unix.writev passes only the first n slices in vs to the underlying writev system call.

Not implemented on Windows. It should be possible to implement, upon request, for Windows sockets only.

The behavior of writev when vs has zero slices depends on the system, and may change in future versions of Lwt. On Linux, writev will succeed and write zero bytes. On BSD (including macOS), writev will fail with Unix.Unix_error (Unix.EINVAL, "writev", ...).

See writev(3p).

  • since 2.7.0
val readable : file_descr -> bool

Returns whether the given file descriptor is currently readable.

val writable : file_descr -> bool

Returns whether the given file descriptor is currently writable.

val wait_read : file_descr -> unit Lwt.t

Waits (without blocking other promises) until there is something to read from the file descriptor.

Note that you don't need to use this function if you are using Lwt I/O functions for reading, since they provide non-blocking waiting automatically.

The intended use case for this function is interfacing with existing libraries that are known to be blocking.

val wait_write : file_descr -> unit Lwt.t

Waits (without blocking other promises) until it is possible to write on the file descriptor.

Note that you don't need to use this function if you are using Lwt I/O functions for writing, since they provide non-blocking waiting automatically.

The intended use case for this function is interfacing with existing libraries that are known to be blocking.

Seeking and truncating

type seek_command = Unix.seek_command =
  1. | SEEK_SET
  2. | SEEK_CUR
  3. | SEEK_END
val lseek : file_descr -> int -> seek_command -> int Lwt.t

Wrapper for Unix.lseek

val truncate : string -> int -> unit Lwt.t

Wrapper for Unix.truncate

val ftruncate : file_descr -> int -> unit Lwt.t

Wrapper for Unix.ftruncate

Syncing

val fsync : file_descr -> unit Lwt.t

Synchronise all data and metadata of the file descriptor with the disk. On Windows it uses FlushFileBuffers.

val fdatasync : file_descr -> unit Lwt.t

Synchronise all data (but not metadata) of the file descriptor with the disk.

Note that fdatasync is not available on Windows and OS X.

File status

type file_kind = Unix.file_kind =
  1. | S_REG
  2. | S_DIR
  3. | S_CHR
  4. | S_BLK
  5. | S_LNK
  6. | S_FIFO
  7. | S_SOCK
type stats = Unix.stats = {
  1. st_dev : int;
  2. st_ino : int;
  3. st_kind : file_kind;
  4. st_perm : file_perm;
  5. st_uid : int;
  6. st_gid : int;
  7. st_rdev : int;
  8. st_size : int;
  9. st_atime : float;
  10. st_mtime : float;
  11. st_ctime : float;
}
val stat : string -> stats Lwt.t

Wrapper for Unix.stat

val lstat : string -> stats Lwt.t

Wrapper for Unix.lstat

val fstat : file_descr -> stats Lwt.t

Wrapper for Unix.fstat

val file_exists : string -> bool Lwt.t

file_exists name tests if a file named name exists.

Note that file_exists behaves similarly to Sys.file_exists:

  • “file” is interpreted as “directory entry” in this context
  • file_exists name will return false in circumstances that would make stat raise a Unix.Unix_error exception.
val utimes : string -> float -> float -> unit Lwt.t

utimes path atime mtime updates the access and modification times of the file at path. The access time is set to atime and the modification time to mtime. To set both to the current time, call utimes path 0. 0..

This function corresponds to Unix.utimes. See also utimes(3p).

  • since 2.6.0
val isatty : file_descr -> bool Lwt.t

Wrapper for Unix.isatty

File operations on large files

module LargeFile : sig ... end

Operations on file names

Wrapper for Unix.unlink

val rename : string -> string -> unit Lwt.t

Wrapper for Unix.rename

Wrapper for Unix.link

File permissions and ownership

val chmod : string -> file_perm -> unit Lwt.t

Wrapper for Unix.chmod

val fchmod : file_descr -> file_perm -> unit Lwt.t

Wrapper for Unix.fchmod

val chown : string -> int -> int -> unit Lwt.t

Wrapper for Unix.chown

val fchown : file_descr -> int -> int -> unit Lwt.t

Wrapper for Unix.fchown

type access_permission = Unix.access_permission =
  1. | R_OK
  2. | W_OK
  3. | X_OK
  4. | F_OK
val access : string -> access_permission list -> unit Lwt.t

Wrapper for Unix.access

Operations on file descriptors

val dup : ?cloexec:bool -> file_descr -> file_descr

Wrapper for Unix.dup

val dup2 : ?cloexec:bool -> file_descr -> file_descr -> unit

Wrapper for Unix.dup2

val set_close_on_exec : file_descr -> unit
val clear_close_on_exec : file_descr -> unit

Directories

val mkdir : string -> file_perm -> unit Lwt.t

Wrapper for Unix.mkdir

val rmdir : string -> unit Lwt.t

Wrapper for Unix.rmdir

val chdir : string -> unit Lwt.t

Wrapper for Unix.chdir

val getcwd : unit -> string Lwt.t

Wrapper for Unix.getcwd

  • since 3.1.0
val chroot : string -> unit Lwt.t

Wrapper for Unix.chroot

type dir_handle = Unix.dir_handle
val opendir : string -> dir_handle Lwt.t

Opens a directory for listing. Directories opened with this function must be explicitly closed with closedir. This is a cooperative analog of Unix.opendir.

val readdir : dir_handle -> string Lwt.t

Reads the next directory entry from the given directory. Special entries such as . and .. are included. If all entries have been read, raises End_of_file. This is a cooperative analog of Unix.readdir.

val readdir_n : dir_handle -> int -> string array Lwt.t

readdir_n handle count reads at most count entries from the given directory. It is more efficient than calling readdir count times. If the length of the returned array is smaller than count, this means that the end of the directory has been reached.

val rewinddir : dir_handle -> unit Lwt.t

Resets the given directory handle, so that directory listing can be restarted. Cooperative analog of Unix.rewinddir.

val closedir : dir_handle -> unit Lwt.t

Closes a directory handle. Cooperative analog of Unix.closedir.

val files_of_directory : string -> string Lwt_stream.t

files_of_directory dir returns the stream of all files of dir.

Pipes and redirections

val pipe : ?cloexec:bool -> unit -> file_descr * file_descr

pipe () creates pipe using Unix.pipe and returns two lwt file descriptors created from unix file_descriptor

val pipe_in : ?cloexec:bool -> unit -> file_descr * Unix.file_descr

pipe_in () is the same as pipe but maps only the unix file descriptor for reading into a lwt one. The second is not put into non-blocking mode. You usually want to use this before forking to receive data from the child process.

val pipe_out : ?cloexec:bool -> unit -> Unix.file_descr * file_descr

pipe_out () is the inverse of pipe_in. You usually want to use this before forking to send data to the child process

val mkfifo : string -> file_perm -> unit Lwt.t

Wrapper for Unix.mkfifo

Wrapper for Unix.symlink

Wrapper for Unix.readlink

Locking

type lock_command = Unix.lock_command =
  1. | F_ULOCK
  2. | F_LOCK
  3. | F_TLOCK
  4. | F_TEST
  5. | F_RLOCK
  6. | F_TRLOCK
val lockf : file_descr -> lock_command -> int -> unit Lwt.t

Wrapper for Unix.lockf

User id, group id

type passwd_entry = Unix.passwd_entry = {
  1. pw_name : string;
  2. pw_passwd : string;
  3. pw_uid : int;
  4. pw_gid : int;
  5. pw_gecos : string;
  6. pw_dir : string;
  7. pw_shell : string;
}
type group_entry = Unix.group_entry = {
  1. gr_name : string;
  2. gr_passwd : string;
  3. gr_gid : int;
  4. gr_mem : string array;
}
val getlogin : unit -> string Lwt.t

Wrapper for Unix.getlogin

val getpwnam : string -> passwd_entry Lwt.t

Wrapper for Unix.getpwnam

val getgrnam : string -> group_entry Lwt.t

Wrapper for Unix.getgrnam

val getpwuid : int -> passwd_entry Lwt.t

Wrapper for Unix.getpwuid

val getgrgid : int -> group_entry Lwt.t

Wrapper for Unix.getgrgid

Signals

type signal_handler_id

Id of a signal handler, used to cancel it

val on_signal : int -> (int -> unit) -> signal_handler_id

on_signal signum f calls f each time the signal with numnber signum is received by the process. It returns a signal handler identifier that can be used to stop monitoring signum.

val on_signal_full : + int Lwt.t

See pwrite.

module IO_vectors : sig ... end

Sequences of buffer slices for writev.

val readv : file_descr -> IO_vectors.t -> int Lwt.t

readv fd vs reads bytes from fd into the buffer slices vs. If the operation completes successfully, the resulting promise resolves to the number of bytes read.

Data is always read directly into Bigarray slices. If the Unix file descriptor underlying fd is in non-blocking mode, data is also read directly into bytes slices. Otherwise, data for bytes slices is first read into temporary buffers, then copied.

Note that the returned Lwt promise is pending until failure or a successful read, even if the underlying file descriptor is in non-blocking mode. See of_unix_file_descr for a discussion of non-blocking I/O and Lwt.

If IO_vectors.system_limit is Some n and the count of slices in vs exceeds n, then Lwt_unix.readv reads only into the first n slices of vs.

Not implemented on Windows. It should be possible to implement, upon request, for Windows sockets only.

See readv(3p).

  • since 2.7.0
val writev : file_descr -> IO_vectors.t -> int Lwt.t

writev fd vs writes the bytes in the buffer slices vs to the file descriptor fd. If the operation completes successfully, the resulting promise resolves to the number of bytes written.

If the Unix file descriptor underlying fd is in non-blocking mode, writev does not make a copy the bytes before writing. Otherwise, it copies bytes slices, but not Bigarray slices.

Note that the returned Lwt promise is pending until failure or a successful write, even if the underlying descriptor is in non-blocking mode. See of_unix_file_descr for a discussion of non-blocking I/O and Lwt.

If IO_vectors.system_limit is Some n and the count of slices in vs exceeds n, then Lwt_unix.writev passes only the first n slices in vs to the underlying writev system call.

Not implemented on Windows. It should be possible to implement, upon request, for Windows sockets only.

The behavior of writev when vs has zero slices depends on the system, and may change in future versions of Lwt. On Linux, writev will succeed and write zero bytes. On BSD (including macOS), writev will fail with Unix.Unix_error (Unix.EINVAL, "writev", ...).

See writev(3p).

  • since 2.7.0
val readable : file_descr -> bool

Returns whether the given file descriptor is currently readable.

val writable : file_descr -> bool

Returns whether the given file descriptor is currently writable.

val wait_read : file_descr -> unit Lwt.t

Waits (without blocking other promises) until there is something to read from the file descriptor.

Note that you don't need to use this function if you are using Lwt I/O functions for reading, since they provide non-blocking waiting automatically.

The intended use case for this function is interfacing with existing libraries that are known to be blocking.

val wait_write : file_descr -> unit Lwt.t

Waits (without blocking other promises) until it is possible to write on the file descriptor.

Note that you don't need to use this function if you are using Lwt I/O functions for writing, since they provide non-blocking waiting automatically.

The intended use case for this function is interfacing with existing libraries that are known to be blocking.

Seeking and truncating

type seek_command = Unix.seek_command =
  1. | SEEK_SET
  2. | SEEK_CUR
  3. | SEEK_END
val lseek : file_descr -> int -> seek_command -> int Lwt.t

Wrapper for Unix.lseek

val truncate : string -> int -> unit Lwt.t

Wrapper for Unix.truncate

val ftruncate : file_descr -> int -> unit Lwt.t

Wrapper for Unix.ftruncate

Syncing

val fsync : file_descr -> unit Lwt.t

Synchronise all data and metadata of the file descriptor with the disk. On Windows it uses FlushFileBuffers.

val fdatasync : file_descr -> unit Lwt.t

Synchronise all data (but not metadata) of the file descriptor with the disk.

Note that fdatasync is not available on Windows and OS X.

File status

type file_kind = Unix.file_kind =
  1. | S_REG
  2. | S_DIR
  3. | S_CHR
  4. | S_BLK
  5. | S_LNK
  6. | S_FIFO
  7. | S_SOCK
type stats = Unix.stats = {
  1. st_dev : int;
  2. st_ino : int;
  3. st_kind : file_kind;
  4. st_perm : file_perm;
  5. st_uid : int;
  6. st_gid : int;
  7. st_rdev : int;
  8. st_size : int;
  9. st_atime : float;
  10. st_mtime : float;
  11. st_ctime : float;
}
val stat : string -> stats Lwt.t

Wrapper for Unix.stat

val lstat : string -> stats Lwt.t

Wrapper for Unix.lstat

val fstat : file_descr -> stats Lwt.t

Wrapper for Unix.fstat

val file_exists : string -> bool Lwt.t

file_exists name tests if a file named name exists.

Note that file_exists behaves similarly to Sys.file_exists:

  • “file” is interpreted as “directory entry” in this context
  • file_exists name will return false in circumstances that would make stat raise a Unix.Unix_error exception.
val utimes : string -> float -> float -> unit Lwt.t

utimes path atime mtime updates the access and modification times of the file at path. The access time is set to atime and the modification time to mtime. To set both to the current time, call utimes path 0. 0..

This function corresponds to Unix.utimes. See also utimes(3p).

  • since 2.6.0
val isatty : file_descr -> bool Lwt.t

Wrapper for Unix.isatty

File operations on large files

module LargeFile : sig ... end

Operations on file names

Wrapper for Unix.unlink

val rename : string -> string -> unit Lwt.t

Wrapper for Unix.rename

Wrapper for Unix.link

File permissions and ownership

val chmod : string -> file_perm -> unit Lwt.t

Wrapper for Unix.chmod

val fchmod : file_descr -> file_perm -> unit Lwt.t

Wrapper for Unix.fchmod

val chown : string -> int -> int -> unit Lwt.t

Wrapper for Unix.chown

val fchown : file_descr -> int -> int -> unit Lwt.t

Wrapper for Unix.fchown

type access_permission = Unix.access_permission =
  1. | R_OK
  2. | W_OK
  3. | X_OK
  4. | F_OK
val access : string -> access_permission list -> unit Lwt.t

Wrapper for Unix.access

Operations on file descriptors

val dup : ?cloexec:bool -> file_descr -> file_descr

Wrapper for Unix.dup

val dup2 : ?cloexec:bool -> file_descr -> file_descr -> unit

Wrapper for Unix.dup2

val set_close_on_exec : file_descr -> unit
val clear_close_on_exec : file_descr -> unit

Directories

val mkdir : string -> file_perm -> unit Lwt.t

Wrapper for Unix.mkdir

val rmdir : string -> unit Lwt.t

Wrapper for Unix.rmdir

val chdir : string -> unit Lwt.t

Wrapper for Unix.chdir

val getcwd : unit -> string Lwt.t

Wrapper for Unix.getcwd

  • since 3.1.0
val chroot : string -> unit Lwt.t

Wrapper for Unix.chroot

type dir_handle = Unix.dir_handle
val opendir : string -> dir_handle Lwt.t

Opens a directory for listing. Directories opened with this function must be explicitly closed with closedir. This is a cooperative analog of Unix.opendir.

val readdir : dir_handle -> string Lwt.t

Reads the next directory entry from the given directory. Special entries such as . and .. are included. If all entries have been read, raises End_of_file. This is a cooperative analog of Unix.readdir.

val readdir_n : dir_handle -> int -> string array Lwt.t

readdir_n handle count reads at most count entries from the given directory. It is more efficient than calling readdir count times. If the length of the returned array is smaller than count, this means that the end of the directory has been reached.

val rewinddir : dir_handle -> unit Lwt.t

Resets the given directory handle, so that directory listing can be restarted. Cooperative analog of Unix.rewinddir.

val closedir : dir_handle -> unit Lwt.t

Closes a directory handle. Cooperative analog of Unix.closedir.

val files_of_directory : string -> string Lwt_stream.t

files_of_directory dir returns the stream of all files of dir.

Pipes and redirections

val pipe : ?cloexec:bool -> unit -> file_descr * file_descr

pipe () creates pipe using Unix.pipe and returns two lwt file descriptors created from unix file_descriptor

val pipe_in : ?cloexec:bool -> unit -> file_descr * Unix.file_descr

pipe_in () is the same as pipe but maps only the unix file descriptor for reading into a lwt one. The second is not put into non-blocking mode. You usually want to use this before forking to receive data from the child process.

val pipe_out : ?cloexec:bool -> unit -> Unix.file_descr * file_descr

pipe_out () is the inverse of pipe_in. You usually want to use this before forking to send data to the child process

val mkfifo : string -> file_perm -> unit Lwt.t

Wrapper for Unix.mkfifo

Wrapper for Unix.symlink

Wrapper for Unix.readlink

Locking

type lock_command = Unix.lock_command =
  1. | F_ULOCK
  2. | F_LOCK
  3. | F_TLOCK
  4. | F_TEST
  5. | F_RLOCK
  6. | F_TRLOCK
val lockf : file_descr -> lock_command -> int -> unit Lwt.t

Wrapper for Unix.lockf

User id, group id

type passwd_entry = Unix.passwd_entry = {
  1. pw_name : string;
  2. pw_passwd : string;
  3. pw_uid : int;
  4. pw_gid : int;
  5. pw_gecos : string;
  6. pw_dir : string;
  7. pw_shell : string;
}
type group_entry = Unix.group_entry = {
  1. gr_name : string;
  2. gr_passwd : string;
  3. gr_gid : int;
  4. gr_mem : string array;
}
val getlogin : unit -> string Lwt.t

Wrapper for Unix.getlogin

val getpwnam : string -> passwd_entry Lwt.t

Wrapper for Unix.getpwnam

val getgrnam : string -> group_entry Lwt.t

Wrapper for Unix.getgrnam

val getpwuid : int -> passwd_entry Lwt.t

Wrapper for Unix.getpwuid

val getgrgid : int -> group_entry Lwt.t

Wrapper for Unix.getgrgid

Signals

type signal_handler_id

Id of a signal handler, used to cancel it

val on_signal : int -> (int -> unit) -> signal_handler_id

on_signal signum f calls f each time the signal with numnber signum is received by the process. It returns a signal handler identifier that can be used to stop monitoring signum.

val on_signal_full : int -> (signal_handler_id -> int -> unit) -> signal_handler_id

on_signal_full f is the same as on_signal f except that f also receive the signal handler identifier as argument so it can disable it.

val disable_signal_handler : signal_handler_id -> unit

Stops receiving this signal

val signal_count : unit -> int

Returns the number of registered signal handler.

val reinstall_signal_handler : int -> unit

reinstall_signal_handler signum if any signal handler is registered for this signal with on_signal, it reinstall the signal handler (with Sys.set_signal). This is useful in case another part of the program install another signal handler.

val handle_signal : int -> unit

handle_signal signum acts as if Lwt had received the signum signal. This allows another IO library to install the handler, perform its own handling, but still notify Lwt. It is particularly useful for SIGCHLD, where several IO libraries may be spawning sub-processes.

This function is thread-safe.

Sockets

type inet_addr = Unix.inet_addr
type socket_domain = Unix.socket_domain =
  1. | PF_UNIX
  2. | PF_INET
  3. | PF_INET6
type socket_type = Unix.socket_type =
  1. | SOCK_STREAM
  2. | SOCK_DGRAM
  3. | SOCK_RAW
  4. | SOCK_SEQPACKET
type sockaddr = Unix.sockaddr =
  1. | ADDR_UNIX of string
  2. | ADDR_INET of inet_addr * int
val socket : ?cloexec:bool -> socket_domain -> socket_type -> int -> file_descr

socket domain type proto is the same as Unix.socket but maps the result into a lwt file descriptor

val socketpair : @@ -23,11 +23,11 @@ socket_domain -> socket_type -> int -> - file_descr * file_descr

Wrapper for Unix.socketpair

val bind : file_descr -> sockaddr -> unit Lwt.t

Binds an address to the given socket. This is the cooperative analog of Unix.bind. See also bind(3p).

  • since 3.0.0
val listen : file_descr -> int -> unit

Wrapper for Unix.listen

val accept : ?cloexec:bool -> file_descr -> (file_descr * sockaddr) Lwt.t

Wrapper for Unix.accept

val accept_n : + file_descr * file_descr

Wrapper for Unix.socketpair

val bind : file_descr -> sockaddr -> unit Lwt.t

Binds an address to the given socket. This is the cooperative analog of Unix.bind. See also bind(3p).

  • since 3.0.0
val listen : file_descr -> int -> unit

Wrapper for Unix.listen

val accept : ?cloexec:bool -> file_descr -> (file_descr * sockaddr) Lwt.t

Wrapper for Unix.accept

val accept_n : ?cloexec:bool -> file_descr -> int -> - ((file_descr * sockaddr) list * exn option) Lwt.t

accept_n fd count accepts up to count connections at one time.

  • if no connection is available right now, it returns a pending promise
  • if more than 1 and less than count are available, it returns all of them
  • if more than count are available, it returns the next count of them
  • if an error happens, it returns the connections that have been successfully accepted so far and the error

accept_n has the advantage of improving performance. If you want a more detailed description, you can have a look at:

Acceptable strategies for improving web server performance

val connect : file_descr -> sockaddr -> unit Lwt.t

Wrapper for Unix.connect

type shutdown_command = Unix.shutdown_command =
  1. | SHUTDOWN_RECEIVE
  2. | SHUTDOWN_SEND
  3. | SHUTDOWN_ALL
val shutdown : file_descr -> shutdown_command -> unit

Wrapper for Unix.shutdown

val getsockname : file_descr -> sockaddr

Wrapper for Unix.getsockname

val getpeername : file_descr -> sockaddr

Wrapper for Unix.getpeername

type msg_flag = Unix.msg_flag =
  1. | MSG_OOB
  2. | MSG_DONTROUTE
  3. | MSG_PEEK
val recv : file_descr -> bytes -> int -> int -> msg_flag list -> int Lwt.t

Wrapper for Unix.recv.

On Windows, recv writes data into a temporary buffer, then copies it into the given one.

val recvfrom : + ((file_descr * sockaddr) list * exn option) Lwt.t

accept_n fd count accepts up to count connections at one time.

  • if no connection is available right now, it returns a pending promise
  • if more than 1 and less than count are available, it returns all of them
  • if more than count are available, it returns the next count of them
  • if an error happens, it returns the connections that have been successfully accepted so far and the error

accept_n has the advantage of improving performance. If you want a more detailed description, you can have a look at:

Acceptable strategies for improving web server performance

val connect : file_descr -> sockaddr -> unit Lwt.t

Wrapper for Unix.connect

type shutdown_command = Unix.shutdown_command =
  1. | SHUTDOWN_RECEIVE
  2. | SHUTDOWN_SEND
  3. | SHUTDOWN_ALL
val shutdown : file_descr -> shutdown_command -> unit

Wrapper for Unix.shutdown

val getsockname : file_descr -> sockaddr

Wrapper for Unix.getsockname

val getpeername : file_descr -> sockaddr

Wrapper for Unix.getpeername

type msg_flag = Unix.msg_flag =
  1. | MSG_OOB
  2. | MSG_DONTROUTE
  3. | MSG_PEEK
val recv : file_descr -> bytes -> int -> int -> msg_flag list -> int Lwt.t

Wrapper for Unix.recv.

On Windows, recv writes data into a temporary buffer, then copies it into the given one.

val recvfrom : file_descr -> bytes -> int -> diff --git a/lwt/_doc-dir/CHANGES b/lwt/_doc-dir/CHANGES index 02f2e473..76a6cdb8 100644 --- a/lwt/_doc-dir/CHANGES +++ b/lwt/_doc-dir/CHANGES @@ -1,3 +1,43 @@ +===== 5.9.0 ===== + +====== Additions ====== + + * Lwt_stream.junk_available is a Lwt-free alternative to Lwt_stream.junk_old. (Alain Mebsout, #1036) + + * Lwt_engine.libev#backend indicates which backend was picked by libev. (mefyl, bnguyenvanyen, #985) + +====== Documentation ====== + + * Many fixes. (Arvid Jakobsson, #1038) + +====== Other ====== + + * Misc repository maintenance. (Sora Morimoto, Shon Feder, #1037, #1035) + +===== 5.8.0 ===== + +====== Improvements ====== + + * Make Lwt_seq.of_list lazier, make Lwt_set.to_list tail-rec. (Samer Abdallah, #1019) + + * Convert more Lwt.fail into raise to increase availibility of backtraces. (#1008) + +====== Documentation ====== + + * Small fixes. (Nils André, #1001, #1006) + + * Convert manual to mld. (#951, Antonin Décimo) + +====== Other ====== + + * Many improbements to the CI. (Sora Morimoto, Idir Lankri, #986, #1009, #1011, #1013, #1014, #1016, #1020, #1021, #1023, #1024, #1025) + + * Improbements to the packaging. (Sora Morimoto, #1010, #1015) + + * Make C code pass the stricter checks of GCC 14. (Jerry James, #1004) + + * Fix many many C warnings and other fixes. (Antonin Décimo, #1007, #1022) + ===== 5.7.0 ===== ====== Breaking API changes ====== diff --git a/lwt/_doc-dir/README.md b/lwt/_doc-dir/README.md index 1c94c14d..12ef39f4 100644 --- a/lwt/_doc-dir/README.md +++ b/lwt/_doc-dir/README.md @@ -2,12 +2,10 @@ [![version][version]][releases] [![GitHub Actions status][github-actions-img]][github-actions] -[version]: https://img.shields.io/github/release/ocsigen/lwt +[version]: https://img.shields.io/github/v/release/ocsigen/lwt [releases]: https://github.com/ocsigen/lwt/releases [github-actions]: https://github.com/ocsigen/lwt/actions [github-actions-img]: https://github.com/ocsigen/lwt/actions/workflows/workflow.yml/badge.svg?branch=master -[appveyor]: https://ci.appveyor.com/project/aantron/lwt/branch/master -[appveyor-img]: https://img.shields.io/appveyor/ci/aantron/lwt/master.svg?label=appveyor Lwt is a concurrent programming library for OCaml. It provides a single data type: the *promise*, which is a value that will become determined in the future. @@ -117,10 +115,10 @@ rewriting the manual). In the meantime: or just "threads." This will be fixed in the new manual. `'a Lwt.t` is a promise, and has nothing to do with system or preemptive threads.* -[manual]: http://ocsigen.org/lwt/ +[manual]: https://ocsigen.org/lwt/ [rwo-lwt]: https://github.com/dkim/rwo-lwt#readme [mirage-tutorial]: https://mirage.io/docs/tutorial-lwt -[counter-server]: http://www.baturin.org/code/lwt-counter-server/ +[counter-server]: https://baturin.org/code/lwt-counter-server/
@@ -130,18 +128,13 @@ promise, and has nothing to do with system or preemptive threads.* Open an [issue][issues], visit [Discord][discord] chat, ask on [discuss.ocaml.org][discourse], or on [Stack Overflow][so]. -Release announcements are made in [/r/ocaml][reddit], and on -[discuss.ocaml.org][discourse]. Watching the repo for "Releases only" is also an -option. +Release announcements are made on [discuss.ocaml.org][discourse]. Watching the +repo for "Releases only" is also an option. -[irc]: http://webchat.freenode.net/?channels=#ocaml -[so]: http://stackoverflow.com/questions/ask?tags=ocaml,lwt,ocaml-lwt -[announcements]: https://github.com/ocsigen/lwt/issues/309 -[reddit]: https://www.reddit.com/r/ocaml/ -[caml-list]: https://sympa.inria.fr/sympa/arc/caml-list +[so]: https://stackoverflow.com/questions/ask?tags=ocaml,lwt,ocaml-lwt [discourse]: https://discuss.ocaml.org/tag/lwt [issues]: https://github.com/ocsigen/lwt/issues/new -[discord]: https://discordapp.com/invite/cCYQbqN +[discord]: https://discord.com/invite/cCYQbqN
@@ -159,7 +152,6 @@ option. - Any feedback is welcome, including how to make contributing easier! [issues-and-prs]: https://github.com/ocsigen/lwt/issues?utf8=%E2%9C%93&q=is%3Aopen -[all-issues]: https://github.com/ocsigen/lwt/issues [contributing-md]: https://github.com/ocsigen/lwt/blob/master/docs/CONTRIBUTING.md#readme @@ -180,8 +172,10 @@ JSON parsing and output serialization combinators - [logs](https://github.com/dbuenzli/logs) — logging -- [lwt-parallel](https://github.com/ivg/parallel) — +- [lwt-parallel](https://github.com/ivg/lwt-parallel) — distributed computing - [mwt](https://github.com/hcarty/mwt) — preemptive (system) thread pools - [opium](https://github.com/rgrinberg/opium) — web framework +- [lwt_domain](https://github.com/ocsigen/lwt_domain) — domain parallelism when + using Lwt with OCaml 5 diff --git a/lwt/_doc-dir/odoc-pages/index.mld b/lwt/_doc-dir/odoc-pages/index.mld index 238f380f..f82f4abc 100644 --- a/lwt/_doc-dir/odoc-pages/index.mld +++ b/lwt/_doc-dir/odoc-pages/index.mld @@ -79,12 +79,12 @@ In Lwt, {1 Additional Docs} -- {{:http://ocsigen.org/lwt/} Online manual}. +- {{!page-manual} Manual} ({{:https://ocsigen.org/lwt/} Online manual}). - {{:https://github.com/dkim/rwo-lwt#readme} Concurrent Programming with Lwt} is a nice source of Lwt examples. They are translations of code from Real World OCaml, but are just as useful if you are not reading the book. -- {{:https://mirage.io/wiki/tutorial-lwt} Mirage Lwt tutorial}. -- {{:http://www.baturin.org/code/lwt-counter-server/} Example server} written +- {{:https://mirage.io/docs/tutorial-lwt} Mirage Lwt tutorial}. +- {{:https://baturin.org/code/lwt-counter-server/} Example server} written with Lwt. diff --git a/lwt/_doc-dir/odoc-pages/manual.mld b/lwt/_doc-dir/odoc-pages/manual.mld new file mode 100644 index 00000000..df72da24 --- /dev/null +++ b/lwt/_doc-dir/odoc-pages/manual.mld @@ -0,0 +1,974 @@ +{0 Lwt manual } + +{1 Introduction } + + When writing a program, a common developer's task is to handle I/O + operations. Indeed, most software interacts with several different + resources, such as: + +{ul + {- the kernel, by doing system calls,} + {- the user, by reading the keyboard, the mouse, or any input device,} + {- a graphical server, to build graphical user interface,} + {- other computers, by using the network,} + {- …and so on.}} + + When this list contains only one item, it is pretty easy to + handle. However as this list grows it becomes harder and harder to + make everything work together. Several choices have been proposed + to solve this problem: + +{ul + {- using a main loop, and integrating all components we are + interacting with into this main loop,} + {- using preemptive system threads.}} + + Both solutions have their advantages and their drawbacks. For the + first one, it may work, but it becomes very complicated to write + a piece of asynchronous sequential code. The typical example is + graphical user interfaces freezing and not redrawing themselves + because they are waiting for some blocking part of the code to + complete. + + If you already wrote code using preemptive threads, you should know + that doing it right with threads is a difficult job. Moreover, system + threads consume non-negligible resources, and so you can only launch + a limited number of threads at the same time. Thus, this is not a + general solution. + + [Lwt] offers a third alternative. It provides promises, which are + very fast: a promise is just a reference that will be filled asynchronously, + and calling a function that returns a promise does not require a new stack, + new process, or anything else. It is just a normal, fast, function call. + Promises compose nicely, allowing us to write highly asynchronous programs. + + In the first part, we will explain the concepts of [Lwt], then we will + describe the main modules [Lwt] consists of. + +{2 Finding examples } + + Additional sources of examples: + +{ul + {- {{: https://github.com/dkim/rwo-lwt#readme }Concurrent Programming with Lwt}} + {- {{: https://mirage.io/docs/tutorial-lwt }Mirage Lwt Tutorial}} + {- {{: https://baturin.org/code/lwt-counter-server/ }Simple Server with Lwt}}} + +{1 The Lwt core library } + + In this section we describe the basics of [Lwt]. It is advised to + start [utop] and try the given code examples. + +{2 Lwt concepts } + + Let's take a classic function of the [Stdlib] module: + +{[ +# Stdlib.input_char;; +- : in_channel -> char = +]} + + This function will wait for a character to come on the given input + channel, and then return it. The problem with this function is that it is + blocking: while it is being executed, the whole program will be + blocked, and other events will not be handled until it returns. + + Now, let's look at the lwt equivalent: + +{[ +# Lwt_io.read_char;; +- : Lwt_io.input_channel -> char Lwt.t = +]} + + As you can see, it does not return just a character, but something of + type [char Lwt.t]. The type ['a Lwt.t] is the type + of promises that can be fulfilled later with a value of type ['a]. + [Lwt_io.read_char] will try to read a character from the + given input channel and {e immediately} return a promise, without + blocking, whether a character is available or not. If a character is + not available, the promise will just not be fulfilled {e yet}. + + Now, let's see what we can do with a [Lwt] promise. The following + code creates a pipe, creates a promise that is fulfilled with the result of + reading the input side: + +{[ +# let ic, oc = Lwt_io.pipe ();; +val ic : Lwt_io.input_channel = +val oc : Lwt_io.output_channel = +# let p = Lwt_io.read_char ic;; +val p : char Lwt.t = +]} + + We can now look at the state of our newly created promise: + +{[ +# Lwt.state p;; +- : char Lwt.state = Lwt.Sleep +]} + + A promise may be in one of the following states: + +{ul + {- [Return x], which means that the promise has been fulfilled + with the value [x]. This usually implies that the asynchronous + operation, that you started by calling the function that returned the + promise, has completed successfully.} + {- [Fail exn], which means that the promise has been rejected + with the exception [exn]. This usually means that the asynchronous + operation associated with the promise has failed.} + {- [Sleep], which means that the promise is has not yet been + fulfilled or rejected, so it is {e pending}.}} + + The above promise [p] is pending because there is nothing yet + to read from the pipe. Let's write something: + +{[ +# Lwt_io.write_char oc 'a';; +- : unit Lwt.t = +# Lwt.state p;; +- : char Lwt.state = Lwt.Return 'a' +]} + + So, after we write something, the reading promise has been fulfilled + with the value ['a']. + +{2 Primitives for promise creation } + + There are several primitives for creating [Lwt] promises. These + functions are located in the module [Lwt]. + + Here are the main primitives: + +{ul + {- [Lwt.return : 'a -> 'a Lwt.t] + creates a promise which is already fulfilled with the given value} + {- [Lwt.fail : exn -> 'a Lwt.t] + creates a promise which is already rejected with the given exception} + {- [Lwt.wait : unit -> 'a Lwt.t * 'a Lwt.u] + creates a pending promise, and returns it, paired with a resolver (of + type ['a Lwt.u]), which must be used to resolve (fulfill or reject) + the promise.}} + + To resolve a pending promise, use one of the following + functions: + +{ul + {- [Lwt.wakeup : 'a Lwt.u -> 'a -> unit] + fulfills the promise with a value.} + {- [Lwt.wakeup_exn : 'a Lwt.u -> exn -> unit] + rejects the promise with an exception.}} + + Note that it is an error to try to resolve the same promise twice. [Lwt] + will raise [Invalid_argument] if you try to do so. + + With this information, try to guess the result of each of the + following expressions: + +{[ +# Lwt.state (Lwt.return 42);; +# Lwt.state (Lwt.fail Exit);; +# let p, r = Lwt.wait ();; +# Lwt.state p;; +# Lwt.wakeup r 42;; +# Lwt.state p;; +# let p, r = Lwt.wait ();; +# Lwt.state p;; +# Lwt.wakeup_exn r Exit;; +# Lwt.state p;; +]} + +{3 Primitives for promise composition } + + The most important operation you need to know is [bind]: + +{[ +val bind : 'a Lwt.t -> ('a -> 'b Lwt.t) -> 'b Lwt.t +]} + + [bind p f] creates a promise which waits for [p] to become + become fulfilled, then passes the resulting value to [f]. If [p] is a + pending promise, then [bind p f] will be a pending promise too, + until [p] is resolved. If [p] is rejected, then the resulting + promise will be rejected with the same exception. For example, consider the + following expression: + +{[ +Lwt.bind + (Lwt_io.read_line Lwt_io.stdin) + (fun str -> Lwt_io.printlf "You typed %S" str) +]} + + This code will first wait for the user to enter a line of text, then + print a message on the standard output. + + Similarly to [bind], there is a function to handle the case + when [p] is rejected: + +{[ +val catch : (unit -> 'a Lwt.t) -> (exn -> 'a Lwt.t) -> 'a Lwt.t +]} + + [catch f g] will call [f ()], then wait for it to become + resolved, and if it was rejected with an exception [exn], call + [g exn] to handle it. Note that both exceptions raised with + [Pervasives.raise] and [Lwt.fail] are caught by + [catch]. + +{3 Cancelable promises } + + In some case, we may want to cancel a promise. For example, because it + has not resolved after a timeout. This can be done with cancelable + promises. To create a cancelable promise, you must use the + [Lwt.task] function: + +{[ +val task : unit -> 'a Lwt.t * 'a Lwt.u +]} + + It has the same semantics as [Lwt.wait], except that the + pending promise can be canceled with [Lwt.cancel]: + +{[ +val cancel : 'a Lwt.t -> unit +]} + + The promise will then be rejected with the exception + [Lwt.Canceled]. To execute a function when the promise is + canceled, you must use [Lwt.on_cancel]: + +{[ +val on_cancel : 'a Lwt.t -> (unit -> unit) -> unit +]} + + Note that canceling a promise does not automatically cancel the + asynchronous operation that is going to resolve it. It does, however, + prevent any further chained operations from running. The asynchronous + operation associated with a promise can only be canceled if its implementation + has taken care to set an [on_cancel] callback on the promise that + it returned to you. In practice, most operations (such as system calls) + can't be canceled once they are started anyway, so promise cancellation is + useful mainly for interrupting future operations once you know that a chain of + asynchronous operations will not be needed. + + It is also possible to cancel a promise which has not been + created directly by you with [Lwt.task]. In this case, the deepest + cancelable promise that the given promise depends on will be canceled. + + For example, consider the following code: + +{[ +# let p, r = Lwt.task ();; +val p : '_a Lwt.t = +val r : '_a Lwt.u = +# let p' = Lwt.bind p (fun x -> Lwt.return (x + 1));; +val p' : int Lwt.t = +]} + + Here, cancelling [p'] will in fact cancel [p], rejecting + it with [Lwt.Canceled]. [Lwt.bind] will then propagate the + exception forward to [p']: + +{[ +# Lwt.cancel p';; +- : unit = () +# Lwt.state p;; +- : int Lwt.state = Lwt.Fail Lwt.Canceled +# Lwt.state p';; +- : int Lwt.state = Lwt.Fail Lwt.Canceled +]} + + It is possible to prevent a promise from being canceled + by using the function [Lwt.protected]: + +{[ +val protected : 'a Lwt.t -> 'a Lwt.t +]} + + Canceling [(protected p)] will have no effect on [p]. + +{3 Primitives for concurrent composition } + + We now show how to compose several promises concurrently. The + main functions for this are in the [Lwt] module: [join], + [choose] and [pick]. + + The first one, [join] takes a list of promises and returns a promise + that is waiting for all of them to resolve: + +{[ +val join : unit Lwt.t list -> unit Lwt.t +]} + + Moreover, if at least one promise is rejected, [join l] will be rejected + with the same exception as the first one, after all the promises are resolved. + + Conversely, [choose] waits for at least {e one} promise to become + resolved, then resolves with the same value or exception: + +{[ +val choose : 'a Lwt.t list -> 'a Lwt.t +]} + + For example: + +{[ +# let p1, r1 = Lwt.wait ();; +val p1 : '_a Lwt.t = +val r1 : '_a Lwt.u = +# let p2, r2 = Lwt.wait ();; +val p2 : '_a Lwt.t = +val r2 : '_a Lwt.u = +# let p3 = Lwt.choose [p1; p2];; +val p3 : '_a Lwt.t = +# Lwt.state p3;; +- : '_a Lwt.state = Lwt.Sleep +# Lwt.wakeup r2 42;; +- : unit = () +# Lwt.state p3;; +- : int Lwt.state = Lwt.Return 42 +]} + + The last one, [pick], is the same as [choose], except that it tries to cancel + all other promises when one resolves. Promises created via [Lwt.wait()] are not cancellable + and are thus not cancelled. + +{3 Rules } + + A callback, like the [f] that you might pass to [Lwt.bind], is + an ordinary OCaml function. [Lwt] just handles ordering calls to these + functions. + + [Lwt] uses some preemptive threading internally, but all of your code + runs in the main thread, except when you explicitly opt into additional + threads with [Lwt_preemptive]. + + This simplifies reasoning about critical sections: all the code in one + callback cannot be interrupted by any of the code in another callback. + However, it also carries the danger that if a single callback takes a very + long time, it will not give [Lwt] a chance to run your other callbacks. + In particular: + +{ul + {- do not write functions that may take time to complete, without splitting + them up using [Lwt.pause] or performing some [Lwt] I/O,} + {- do not do I/O that may block, otherwise the whole program will + hang inside that callback. You must instead use the asynchronous I/O + operations provided by [Lwt].}} + +{2 The syntax extension } + + [Lwt] offers a PPX syntax extension which increases code readability and + makes coding using [Lwt] easier. The syntax extension is documented + in {!Ppx_lwt}. + + To use the PPX syntax extension, add the [lwt_ppx] package when + compiling: + +{[ +$ ocamlfind ocamlc -package lwt_ppx -linkpkg -o foo foo.ml +]} + + Or, in [utop]: + +{[ +# #require "lwt_ppx";; +]} + + [lwt_ppx] is distributed in a separate opam package of that same name. + + For a brief overview of the syntax, see the Correspondence table below. + +{3 Correspondence table } + +{table + {tr + {th Without Lwt} + {th With Lwt}} + {tr + {td {[let pattern_1 = expr_1 +and pattern_2 = expr2 +… +and pattern_n = expr_n in +expr]}} + {td {[let%lwt pattern_1 = expr_1 +and pattern_2 = expr2 +… +and pattern_n = expr_n in +expr]}}} + {tr + {td {[try expr with +| pattern_1 = expr_1 +| pattern_2 = expr2 +… +| pattern_n = expr_n]}} + {td {[try%lwt expr with +| pattern_1 = expr_1 +| pattern_2 = expr2 +… +| pattern_n = expr_n]}}} + {tr + {td {[match expr with +| pattern_1 = expr_1 +| pattern_2 = expr2 +… +| pattern_n = expr_n]}} + {td {[match%lwt expr with +| pattern_1 = expr_1 +| pattern_2 = expr2 +… +| pattern_n = expr_n]}}} + {tr + {td {[for ident = expr_init to expr_final do + expr +done]}} + {td {[for%lwt ident = expr_init to expr_final do + expr +done]}}} + {tr + {td {[while expr do expr done]}} + {td {[while%lwt expr do expr done]}}} + {tr + {td {[if expr then expr else expr]}} + {td {[if%lwt expr then expr else expr]}}} + {tr + {td {[assert expr]}} + {td {[assert%lwt expr]}}} + {tr + {td {[raise exn]}} + {td {[[%lwt raise exn]]}}}} + +{2 Backtrace support } + + If an exception is raised inside a callback called by Lwt, the backtrace + provided by OCaml will not be very useful. It will end inside the Lwt + scheduler instead of continuing into the code that started the operations that + led to the callback call. To avoid this, and get good backtraces from Lwt, use + the syntax extension. The [let%lwt] construct will properly propagate + backtraces. + + As always, to get backtraces from an OCaml program, you need to either declare + the environment variable [OCAMLRUNPARAM=b] or call + [Printexc.record_backtrace true] at the start of your program, and be + sure to compile it with [-g]. Most modern build systems add [-g] by + default. + +{2 [let*] syntax } + + To use Lwt with the [let*] syntax introduced in OCaml 4.08, you can open + the [Syntax] module: + +{[ +open Syntax +]} + + Then, you can write + +{[ +let* () = Lwt_io.printl "Hello," in +let* () = Lwt_io.printl "world!" in +Lwt.return () +]} + +{2 Other modules of the core library } + + The core library contains several modules that only depend on + [Lwt]. The following naming convention is used in [Lwt]: when a + function takes as argument a function, returning a promise, that is going + to be executed sequentially, it is suffixed with “[_s]”. And + when it is going to be executed concurrently, it is suffixed with + “[_p]”. For example, in the [Lwt_list] module we have: + +{[ +val map_s : ('a -> 'b Lwt.t) -> 'a list -> 'b list Lwt.t +val map_p : ('a -> 'b Lwt.t) -> 'a list -> 'b list Lwt.t +]} + +{3 Mutexes } + + [Lwt_mutex] provides mutexes for [Lwt]. Its use is almost the + same as the [Mutex] module of the thread library shipped with + OCaml. In general, programs using [Lwt] do not need a lot of + mutexes, because callbacks run without preempting each other. They are + only useful for synchronising or sequencing complex operations spread over + multiple callback calls. + +{3 Lists } + + The [Lwt_list] module defines iteration and scanning functions + over lists, similar to the ones of the [List] module, but using + functions that return a promise. For example: + +{[ +val iter_s : ('a -> unit Lwt.t) -> 'a list -> unit Lwt.t +val iter_p : ('a -> unit Lwt.t) -> 'a list -> unit Lwt.t +]} + + In [iter_s f l], [iter_s] will call f on each elements + of [l], waiting for resolution between each element. On the + contrary, in [iter_p f l], [iter_p] will call f on all + elements of [l], only then wait for all the promises to resolve. + +{3 Data streams } + + [Lwt] streams are used in a lot of places in [Lwt] and its + submodules. They offer a high-level interface to manipulate data flows. + + A stream is an object which returns elements sequentially and + lazily. Lazily means that the source of the stream is touched only for new + elements when needed. This module contains a lot of stream + transformation, iteration, and scanning functions. + + The common way of creating a stream is by using + [Lwt_stream.from] or by using [Lwt_stream.create]: + +{[ +val from : (unit -> 'a option Lwt.t) -> 'a Lwt_stream.t +val create : unit -> 'a Lwt_stream.t * ('a option -> unit) +]} + + As for streams of the standard library, [from] takes as + argument a function which is used to create new elements. + + [create] returns a function used to push new elements + into the stream and the stream which will receive them. + + For example: + +{[ +# let stream, push = Lwt_stream.create ();; +val stream : '_a Lwt_stream.t = +val push : '_a option -> unit = +# push (Some 1);; +- : unit = () +# push (Some 2);; +- : unit = () +# push (Some 3);; +- : unit = () +# Lwt.state (Lwt_stream.next stream);; +- : int Lwt.state = Lwt.Return 1 +# Lwt.state (Lwt_stream.next stream);; +- : int Lwt.state = Lwt.Return 2 +# Lwt.state (Lwt_stream.next stream);; +- : int Lwt.state = Lwt.Return 3 +# Lwt.state (Lwt_stream.next stream);; +- : int Lwt.state = Lwt.Sleep +]} + + Note that streams are consumable. Once you take an element from a + stream, it is removed from the stream. So, if you want to iterate two times + over a stream, you may consider “cloning” it, with + [Lwt_stream.clone]. Cloned stream will return the same + elements in the same order. Consuming one will not consume the other. + For example: + +{[ +# let s = Lwt_stream.of_list [1; 2];; +val s : int Lwt_stream.t = +# let s' = Lwt_stream.clone s;; +val s' : int Lwt_stream.t = +# Lwt.state (Lwt_stream.next s);; +- : int Lwt.state = Lwt.Return 1 +# Lwt.state (Lwt_stream.next s);; +- : int Lwt.state = Lwt.Return 2 +# Lwt.state (Lwt_stream.next s');; +- : int Lwt.state = Lwt.Return 1 +# Lwt.state (Lwt_stream.next s');; +- : int Lwt.state = Lwt.Return 2 +]} + +{3 Mailbox variables } + + The [Lwt_mvar] module provides mailbox variables. A mailbox + variable, also called a “mvar”, is a cell which may contain 0 or 1 + element. If it contains no elements, we say that the mvar is empty, + if it contains one, we say that it is full. Adding an element to a + full mvar will block until one is taken. Taking an element from an + empty mvar will block until one is added. + + Mailbox variables are commonly used to pass messages between chains of + callbacks being executed concurrently. + + Note that a mailbox variable can be seen as a pushable stream with a + limited memory. + +{1 Running an Lwt program } + + An [Lwt] computation you have created will give you something of type + [Lwt.t], a promise. However, even though you have the promise, the + computation may not have run yet, and the promise might still be pending. + + For example if your program is just: + +{[ +let _ = Lwt_io.printl "Hello, world!" +]} + + you have no guarantee that the promise for writing ["Hello, world!"] + on the terminal will be resolved before the program exits. In order + to wait for the promise to resolve, you have to call the function + [Lwt_main.run]: + +{[ +val Lwt_main.run : 'a Lwt.t -> 'a +]} + + This function waits for the given promise to resolve and returns + its result. In fact it does more than that; it also runs the + scheduler which is responsible for making asynchronous computations progress + when events are received from the outside world. + + So basically, when you write a [Lwt] program, you must call + [Lwt_main.run] on your top-level, outer-most promise. For instance: + +{[ +let () = Lwt_main.run (Lwt_io.printl "Hello, world!") +]} + + Note that you must not make nested calls to [Lwt_main.run]. It + cannot be used anywhere else to get the result of a promise. + +{1 The [lwt.unix] library } + + The package [lwt.unix] contains all [Unix]-dependent + modules of [Lwt]. Among all its features, it implements Lwt-friendly, + non-blocking versions of functions of the OCaml standard and Unix libraries. + +{2 Unix primitives } + + Module [Lwt_unix] provides non-blocking system calls. For example, + the [Lwt] counterpart of [Unix.read] is: + +{[ +val read : file_descr -> string -> int -> int -> int Lwt.t +]} + + [Lwt_io] provides features similar to buffered channels of + the standard library (of type [in_channel] or + [out_channel]), but with non-blocking semantics. + + [Lwt_gc] allows you to register a finalizer that returns a + promise. At the end of the program, [Lwt] will wait for all these + finalizers to resolve. + +{2 The Lwt scheduler } + + Operations doing I/O have to be resumed when some events are received by + the process, so they can resolve their associated pending promises. + For example, when you read from a file descriptor, you + may have to wait for the file descriptor to become readable if no + data are immediately available on it. + + [Lwt] contains a scheduler which is responsible for managing + multiple operations waiting for events, and restarting them when needed. + This scheduler is implemented by the two modules [Lwt_engine] + and [Lwt_main]. [Lwt_engine] is a low-level module, it + provides a signature for custom I/O multiplexers as well as two built-in + implementations, [libev] and [select]. The signature is given by the + class [Lwt_engine.t]. + + [libev] is used by default on Linux, because it supports any + number of file descriptors, while [select] supports only 1024. [libev] + is also much more efficient. On Windows, [Unix.select] is used because + [libev] does not work properly. The user may change the backend in use at + any time. + + If you see an [Invalid_argument] error on [Unix.select], it + may be because the 1024 file descriptor limit was exceeded. Try + switching to [libev], if possible. + + The engine can also be used directly in order to integrate other + libraries with [Lwt]. For example, [GTK] needs to be notified + when some events are received. If you use [Lwt] with [GTK] + you need to use the [Lwt] scheduler to monitor [GTK] + sources. This is what is done by the [Lwt_glib] library. + + The [Lwt_main] module contains the {e main loop} of + [Lwt]. It is run by calling the function [Lwt_main.run]: + +{[ +val Lwt_main.run : 'a Lwt.t -> 'a +]} + + This function continuously runs the scheduler until the promise passed + as argument is resolved. + + To make sure [Lwt] is compiled with [libev] support, + tell opam that the library is available on the system by installing the + {{: https://opam.ocaml.org/packages/conf-libev/conf-libev.4-11/ }conf-libev} + package. You may get the actual library with your system package manager: + +{ul + {- [brew install libev] on MacOSX,} + {- [apt-get install libev-dev] on Debian/Ubuntu, or} + {- [yum install libev-devel] on CentOS, which requires to set + [export C_INCLUDE_PATH=/usr/include/libev/] and + [export LIBRARY_PATH=/usr/lib64/] before calling + [opam install conf-libev].}} + + +{2 Logging } + + For logging, we recommend the [logs] package from opam, which includes an + Lwt-aware module [Logs_lwt]. + +{1 The Lwt.react library } + + The [Lwt_react] module provides helpers for using the [react] + library with [Lwt]. It extends the [React] module by adding + [Lwt]-specific functions. It can be used as a replacement of + [React]. For example you can add at the beginning of your + program: + +{[ +open Lwt_react +]} + + instead of: + +{[ +open React +]} + + or: + +{[ +module React = Lwt_react +]} + + Among the added functionalities we have [Lwt_react.E.next], which + takes an event and returns a promise which will be pending until the next + occurrence of this event. For example: + +{[ +# open Lwt_react;; +# let event, push = E.create ();; +val event : '_a React.event = +val push : '_a -> unit = +# let p = E.next event;; +val p : '_a Lwt.t = +# Lwt.state p;; +- : '_a Lwt.state = Lwt.Sleep +# push 42;; +- : unit = () +# Lwt.state p;; +- : int Lwt.state = Lwt.Return 42 +]} + + Another interesting feature is the ability to limit events + (resp. signals) from occurring (resp. changing) too often. For example, + suppose you are doing a program which displays something on the screen + each time a signal changes. If at some point the signal changes 1000 + times per second, you probably don't want to render it 1000 times per + second. For that you use [Lwt_react.S.limit]: + +{[ +val limit : (unit -> unit Lwt.t) -> 'a React.signal -> 'a React.signal +]} + + [Lwt_react.S.limit f signal] returns a signal which varies as + [signal] except that two consecutive updates are separated by a + call to [f]. For example if [f] returns a promise which is pending + for 0.1 seconds, then there will be no more than 10 changes per + second: + +{[ +open Lwt_react + +let draw x = + (* Draw the screen *) + … + +let () = + (* The signal we are interested in: *) + let signal = … in + + (* The limited signal: *) + let signal' = S.limit (fun () -> Lwt_unix.sleep 0.1) signal in + + (* Redraw the screen each time the limited signal change: *) + S.notify_p draw signal' +]} + +{1 Other libraries } + +{2 Parallelise computations to other cores } + + If you have some compute-intensive steps within your program, you can execute + them on a separate core. You can get performance benefits from the + parallelisation. In addition, whilst your compute-intensive function is running + on a different core, your normal I/O-bound tasks continue running on the + original core. + + The module {!Lwt_domain} from the [lwt_domain] package provides all the + necessary helpers to achieve this. It is based on the [Domainslib] library + and uses similar concepts (such as tasks and pools). + + First, you need to create a task pool: + +{[ +val setup_pool : ?name:string -> int -> pool +]} + + Then you simple detach the function calls to the created pool: + +{[ +val detach : pool -> ('a -> 'b) -> 'a -> 'b Lwt.t +]} + + The returned promise resolves as soon as the function returns. + +{2 Detaching computation to preemptive threads } + + It may happen that you want to run a function which will take time to + compute or that you want to use a blocking function that cannot be + used in a non-blocking way. For these situations, [Lwt] allows you to + {e detach} the computation to a preemptive thread. + + This is done by the module [Lwt_preemptive] of the + [lwt.unix] package which maintains a pool of system + threads. The main function is: + +{[ +val detach : ('a -> 'b) -> 'a -> 'b Lwt.t +]} + + [detach f x] will execute [f x] in another thread and + return a pending promise, usable from the main thread, which will be fulfilled + with the result of the preemptive thread. + + If you want to trigger some [Lwt] operations from your detached thread, + you have to call back into the main thread using + [Lwt_preemptive.run_in_main]: + +{[ +val run_in_main : (unit -> 'a Lwt.t) -> 'a +]} + + This is roughly the equivalent of [Lwt.main_run], but for detached + threads, rather than for the whole process. Note that you must not call + [Lwt_main.run] in a detached thread. + +{2 SSL support } + + The library [Lwt_ssl] allows use of SSL asynchronously. + +{1 Writing stubs using [Lwt] } + +{2 Thread-safe notifications } + + If you want to notify the main thread from another thread, you can use the [Lwt] + thread safe notification system. First you need to create a notification identifier + (which is just an integer) from the OCaml side using the + [Lwt_unix.make_notification] function, then you can send it from either the + OCaml code with [Lwt_unix.send_notification] function, or from the C code using + the function [lwt_unix_send_notification] (defined in [lwt_unix_.h]). + + Notifications are received and processed asynchronously by the main thread. + +{2 Jobs } + + For operations that cannot be executed asynchronously, [Lwt] + uses a system of jobs that can be executed in a different threads. A + job is composed of three functions: + +{ul + {- A stub function to create the job. It must allocate a new job + structure and fill its [worker] and [result] fields. This + function is executed in the main thread. + The return type for the OCaml external must be of the form ['a job].} + {- A function which executes the job. This one may be executed asynchronously + in another thread. This function must not: + {ul + {- access or allocate OCaml block values (tuples, strings, …),} + {- call OCaml code.}}} + {- A function which reads the result of the job, frees resources and + returns the result as an OCaml value. This function is executed in + the main thread.}} + + With [Lwt < 2.3.3], 4 functions (including 3 stubs) were + required. It is still possible to use this mode but it is + deprecated. + + We show as example the implementation of [Lwt_unix.mkdir]. On the C + side we have: + +{@c[/**/ +/* Structure holding informations for calling [mkdir]. */ +struct job_mkdir { + /* Informations used by lwt. + It must be the first field of the structure. */ + struct lwt_unix_job job; + /* This field store the result of the call. */ + int result; + /* This field store the value of [errno] after the call. */ + int errno_copy; + /* Pointer to a copy of the path parameter. */ + char* path; + /* Copy of the mode parameter. */ + int mode; + /* Buffer for storing the path. */ + char data[]; +}; + +/* The function calling [mkdir]. */ +static void worker_mkdir(struct job_mkdir* job) +{ + /* Perform the blocking call. */ + job->result = mkdir(job->path, job->mode); + /* Save the value of errno. */ + job->errno_copy = errno; +} + +/* The function building the caml result. */ +static value result_mkdir(struct job_mkdir* job) +{ + /* Check for errors. */ + if (job->result < 0) { + /* Save the value of errno so we can use it + once the job has been freed. */ + int error = job->errno_copy; + /* Copy the contents of job->path into a caml string. */ + value string_argument = caml_copy_string(job->path); + /* Free the job structure. */ + lwt_unix_free_job(&job->job); + /* Raise the error. */ + unix_error(error, "mkdir", string_argument); + } + /* Free the job structure. */ + lwt_unix_free_job(&job->job); + /* Return the result. */ + return Val_unit; +} + +/* The stub creating the job structure. */ +CAMLprim value lwt_unix_mkdir_job(value path, value mode) +{ + /* Get the length of the path parameter. */ + mlsize_t len_path = caml_string_length(path) + 1; + /* Allocate a new job. */ + struct job_mkdir* job = + (struct job_mkdir*)lwt_unix_new_plus(struct job_mkdir, len_path); + /* Set the offset of the path parameter inside the job structure. */ + job->path = job->data; + /* Copy the path parameter inside the job structure. */ + memcpy(job->path, String_val(path), len_path); + /* Initialize function fields. */ + job->job.worker = (lwt_unix_job_worker)worker_mkdir; + job->job.result = (lwt_unix_job_result)result_mkdir; + /* Copy the mode parameter. */ + job->mode = Int_val(mode); + /* Wrap the structure into a caml value. */ + return lwt_unix_alloc_job(&job->job); +} +]} + + and on the ocaml side: + +{[ +(* The stub for creating the job. *) +external mkdir_job : string -> int -> unit job = "lwt_unix_mkdir_job" + +(* The ocaml function. *) +let mkdir name perms = Lwt_unix.run_job (mkdir_job name perms) +]} diff --git a/lwt/index.html b/lwt/index.html index 624d4d44..fa7796c7 100644 --- a/lwt/index.html +++ b/lwt/index.html @@ -22,4 +22,4 @@ let () = | Some response -> print_string response | None -> prerr_endline "Request timed out"; exit 1 -(* ocamlfind opt -package lwt.unix -linkpkg example.ml && ./a.out *)

In the program, functions such as Lwt_io.write create promises. The let%lwt ... in construct is used to wait for a promise to become determined; the code after in is scheduled to run in a "callback." Lwt.pick races promises against each other, and behaves as the first one to complete. Lwt_main.run forces the whole promise-computation network to be executed. All the visible OCaml code is run in a single thread, but Lwt internally uses a combination of worker threads and non-blocking file descriptors to resolve in parallel the promises that do I/O.

Tour

Lwt compiles to native code on Linux, macOS, Windows, and other systems. It's also routinely compiled to JavaScript for the front end and Node by js_of_ocaml.

In Lwt,

Installing

  1. Use your system package manager to install a development libev package. It is often called libev-dev or libev-devel.
  2. opam install conf-libev lwt

Additional Docs

API: Library lwt

This is the system-independent, pure-OCaml core of Lwt. To link with it, use (libraries lwt) in your dune file.

API: Library lwt.unix

This is the system call and I/O library. Despite its name, it is implemented on both Unix-like systems and Windows, although not all functions are available on Windows. To link with this library, use (libraries lwt.unix) in your dune file.

Package info

changes-files
license-files
readme-files
+(* ocamlfind opt -package lwt.unix -linkpkg example.ml && ./a.out *)

In the program, functions such as Lwt_io.write create promises. The let%lwt ... in construct is used to wait for a promise to become determined; the code after in is scheduled to run in a "callback." Lwt.pick races promises against each other, and behaves as the first one to complete. Lwt_main.run forces the whole promise-computation network to be executed. All the visible OCaml code is run in a single thread, but Lwt internally uses a combination of worker threads and non-blocking file descriptors to resolve in parallel the promises that do I/O.

Tour

Lwt compiles to native code on Linux, macOS, Windows, and other systems. It's also routinely compiled to JavaScript for the front end and Node by js_of_ocaml.

In Lwt,

Installing

  1. Use your system package manager to install a development libev package. It is often called libev-dev or libev-devel.
  2. opam install conf-libev lwt

Additional Docs

API: Library lwt

This is the system-independent, pure-OCaml core of Lwt. To link with it, use (libraries lwt) in your dune file.

API: Library lwt.unix

This is the system call and I/O library. Despite its name, it is implemented on both Unix-like systems and Windows, although not all functions are available on Windows. To link with this library, use (libraries lwt.unix) in your dune file.

Package info

changes-files
license-files
readme-files
diff --git a/lwt/manual.html b/lwt/manual.html new file mode 100644 index 00000000..6cd19228 --- /dev/null +++ b/lwt/manual.html @@ -0,0 +1,201 @@ + +manual (lwt.manual)

Lwt manual

Introduction

When writing a program, a common developer's task is to handle I/O operations. Indeed, most software interacts with several different resources, such as:

When this list contains only one item, it is pretty easy to handle. However as this list grows it becomes harder and harder to make everything work together. Several choices have been proposed to solve this problem:

Both solutions have their advantages and their drawbacks. For the first one, it may work, but it becomes very complicated to write a piece of asynchronous sequential code. The typical example is graphical user interfaces freezing and not redrawing themselves because they are waiting for some blocking part of the code to complete.

If you already wrote code using preemptive threads, you should know that doing it right with threads is a difficult job. Moreover, system threads consume non-negligible resources, and so you can only launch a limited number of threads at the same time. Thus, this is not a general solution.

Lwt offers a third alternative. It provides promises, which are very fast: a promise is just a reference that will be filled asynchronously, and calling a function that returns a promise does not require a new stack, new process, or anything else. It is just a normal, fast, function call. Promises compose nicely, allowing us to write highly asynchronous programs.

In the first part, we will explain the concepts of Lwt, then we will describe the main modules Lwt consists of.

Finding examples

Additional sources of examples:

The Lwt core library

In this section we describe the basics of Lwt. It is advised to start utop and try the given code examples.

Lwt concepts

Let's take a classic function of the Stdlib module:

# Stdlib.input_char;;
+- : in_channel -> char = <fun>

This function will wait for a character to come on the given input channel, and then return it. The problem with this function is that it is blocking: while it is being executed, the whole program will be blocked, and other events will not be handled until it returns.

Now, let's look at the lwt equivalent:

# Lwt_io.read_char;;
+- : Lwt_io.input_channel -> char Lwt.t = <fun>

As you can see, it does not return just a character, but something of type char Lwt.t. The type 'a Lwt.t is the type of promises that can be fulfilled later with a value of type 'a. Lwt_io.read_char will try to read a character from the given input channel and immediately return a promise, without blocking, whether a character is available or not. If a character is not available, the promise will just not be fulfilled yet.

Now, let's see what we can do with a Lwt promise. The following code creates a pipe, creates a promise that is fulfilled with the result of reading the input side:

# let ic, oc = Lwt_io.pipe ();;
+val ic : Lwt_io.input_channel = <abstr>
+val oc : Lwt_io.output_channel = <abstr>
+# let p = Lwt_io.read_char ic;;
+val p : char Lwt.t = <abstr>

We can now look at the state of our newly created promise:

# Lwt.state p;;
+- : char Lwt.state = Lwt.Sleep

A promise may be in one of the following states:

The above promise p is pending because there is nothing yet to read from the pipe. Let's write something:

# Lwt_io.write_char oc 'a';;
+- : unit Lwt.t = <abstr>
+# Lwt.state p;;
+- : char Lwt.state = Lwt.Return 'a'

So, after we write something, the reading promise has been fulfilled with the value 'a'.

Primitives for promise creation

There are several primitives for creating Lwt promises. These functions are located in the module Lwt.

Here are the main primitives:

To resolve a pending promise, use one of the following functions:

Note that it is an error to try to resolve the same promise twice. Lwt will raise Invalid_argument if you try to do so.

With this information, try to guess the result of each of the following expressions:

# Lwt.state (Lwt.return 42);;
+# Lwt.state (Lwt.fail Exit);;
+# let p, r = Lwt.wait ();;
+# Lwt.state p;;
+# Lwt.wakeup r 42;;
+# Lwt.state p;;
+# let p, r = Lwt.wait ();;
+# Lwt.state p;;
+# Lwt.wakeup_exn r Exit;;
+# Lwt.state p;;

Primitives for promise composition

The most important operation you need to know is bind:

val bind : 'a Lwt.t -> ('a -> 'b Lwt.t) -> 'b Lwt.t

bind p f creates a promise which waits for p to become become fulfilled, then passes the resulting value to f. If p is a pending promise, then bind p f will be a pending promise too, until p is resolved. If p is rejected, then the resulting promise will be rejected with the same exception. For example, consider the following expression:

Lwt.bind
+  (Lwt_io.read_line Lwt_io.stdin)
+  (fun str -> Lwt_io.printlf "You typed %S" str)

This code will first wait for the user to enter a line of text, then print a message on the standard output.

Similarly to bind, there is a function to handle the case when p is rejected:

val catch : (unit -> 'a Lwt.t) -> (exn -> 'a Lwt.t) -> 'a Lwt.t

catch f g will call f (), then wait for it to become resolved, and if it was rejected with an exception exn, call g exn to handle it. Note that both exceptions raised with Pervasives.raise and Lwt.fail are caught by catch.

Cancelable promises

In some case, we may want to cancel a promise. For example, because it has not resolved after a timeout. This can be done with cancelable promises. To create a cancelable promise, you must use the Lwt.task function:

val task : unit -> 'a Lwt.t * 'a Lwt.u

It has the same semantics as Lwt.wait, except that the pending promise can be canceled with Lwt.cancel:

val cancel : 'a Lwt.t -> unit

The promise will then be rejected with the exception Lwt.Canceled. To execute a function when the promise is canceled, you must use Lwt.on_cancel:

val on_cancel : 'a Lwt.t -> (unit -> unit) -> unit

Note that canceling a promise does not automatically cancel the asynchronous operation that is going to resolve it. It does, however, prevent any further chained operations from running. The asynchronous operation associated with a promise can only be canceled if its implementation has taken care to set an on_cancel callback on the promise that it returned to you. In practice, most operations (such as system calls) can't be canceled once they are started anyway, so promise cancellation is useful mainly for interrupting future operations once you know that a chain of asynchronous operations will not be needed.

It is also possible to cancel a promise which has not been created directly by you with Lwt.task. In this case, the deepest cancelable promise that the given promise depends on will be canceled.

For example, consider the following code:

# let p, r = Lwt.task ();;
+val p : '_a Lwt.t = <abstr>
+val r : '_a Lwt.u = <abstr>
+# let p' = Lwt.bind p (fun x -> Lwt.return (x + 1));;
+val p' : int Lwt.t = <abstr>

Here, cancelling p' will in fact cancel p, rejecting it with Lwt.Canceled. Lwt.bind will then propagate the exception forward to p':

# Lwt.cancel p';;
+- : unit = ()
+# Lwt.state p;;
+- : int Lwt.state = Lwt.Fail Lwt.Canceled
+# Lwt.state p';;
+- : int Lwt.state = Lwt.Fail Lwt.Canceled

It is possible to prevent a promise from being canceled by using the function Lwt.protected:

val protected : 'a Lwt.t -> 'a Lwt.t

Canceling (protected p) will have no effect on p.

Primitives for concurrent composition

We now show how to compose several promises concurrently. The main functions for this are in the Lwt module: join, choose and pick.

The first one, join takes a list of promises and returns a promise that is waiting for all of them to resolve:

val join : unit Lwt.t list -> unit Lwt.t

Moreover, if at least one promise is rejected, join l will be rejected with the same exception as the first one, after all the promises are resolved.

Conversely, choose waits for at least one promise to become resolved, then resolves with the same value or exception:

val choose : 'a Lwt.t list -> 'a Lwt.t

For example:

# let p1, r1 = Lwt.wait ();;
+val p1 : '_a Lwt.t = <abstr>
+val r1 : '_a Lwt.u = <abstr>
+# let p2, r2 = Lwt.wait ();;
+val p2 : '_a Lwt.t = <abstr>
+val r2 : '_a Lwt.u = <abstr>
+# let p3 = Lwt.choose [p1; p2];;
+val p3 : '_a Lwt.t = <abstr>
+# Lwt.state p3;;
+- : '_a Lwt.state = Lwt.Sleep
+# Lwt.wakeup r2 42;;
+- : unit = ()
+# Lwt.state p3;;
+- : int Lwt.state = Lwt.Return 42

The last one, pick, is the same as choose, except that it tries to cancel all other promises when one resolves. Promises created via Lwt.wait() are not cancellable and are thus not cancelled.

Rules

A callback, like the f that you might pass to Lwt.bind, is an ordinary OCaml function. Lwt just handles ordering calls to these functions.

Lwt uses some preemptive threading internally, but all of your code runs in the main thread, except when you explicitly opt into additional threads with Lwt_preemptive.

This simplifies reasoning about critical sections: all the code in one callback cannot be interrupted by any of the code in another callback. However, it also carries the danger that if a single callback takes a very long time, it will not give Lwt a chance to run your other callbacks. In particular:

The syntax extension

Lwt offers a PPX syntax extension which increases code readability and makes coding using Lwt easier. The syntax extension is documented in Ppx_lwt.

To use the PPX syntax extension, add the lwt_ppx package when compiling:

$ ocamlfind ocamlc -package lwt_ppx -linkpkg -o foo foo.ml

Or, in utop:

# #require "lwt_ppx";;

lwt_ppx is distributed in a separate opam package of that same name.

For a brief overview of the syntax, see the Correspondence table below.

Correspondence table

Without Lwt

With Lwt

let pattern_1 = expr_1
+and pattern_2 = expr2
+…
+and pattern_n = expr_n in
+expr
let%lwt pattern_1 = expr_1
+and pattern_2 = expr2
+…
+and pattern_n = expr_n in
+expr
try expr with
+| pattern_1 = expr_1
+| pattern_2 = expr2
+…
+| pattern_n = expr_n
try%lwt expr with
+| pattern_1 = expr_1
+| pattern_2 = expr2
+…
+| pattern_n = expr_n
match expr with
+| pattern_1 = expr_1
+| pattern_2 = expr2
+…
+| pattern_n = expr_n
match%lwt expr with
+| pattern_1 = expr_1
+| pattern_2 = expr2
+…
+| pattern_n = expr_n
for ident = expr_init to expr_final do
+  expr
+done
for%lwt ident = expr_init to expr_final do
+  expr
+done
while expr do expr done
while%lwt expr do expr done
if expr then expr else expr
if%lwt expr then expr else expr
assert expr
assert%lwt expr
raise exn
[%lwt raise exn]

Backtrace support

If an exception is raised inside a callback called by Lwt, the backtrace provided by OCaml will not be very useful. It will end inside the Lwt scheduler instead of continuing into the code that started the operations that led to the callback call. To avoid this, and get good backtraces from Lwt, use the syntax extension. The let%lwt construct will properly propagate backtraces.

As always, to get backtraces from an OCaml program, you need to either declare the environment variable OCAMLRUNPARAM=b or call Printexc.record_backtrace true at the start of your program, and be sure to compile it with -g. Most modern build systems add -g by default.

let* syntax

To use Lwt with the let* syntax introduced in OCaml 4.08, you can open the Syntax module:

open Syntax

Then, you can write

let* () = Lwt_io.printl "Hello," in
+let* () = Lwt_io.printl "world!" in
+Lwt.return ()

Other modules of the core library

The core library contains several modules that only depend on Lwt. The following naming convention is used in Lwt: when a function takes as argument a function, returning a promise, that is going to be executed sequentially, it is suffixed with “_s”. And when it is going to be executed concurrently, it is suffixed with “_p”. For example, in the Lwt_list module we have:

val map_s : ('a -> 'b Lwt.t) -> 'a list -> 'b list Lwt.t
+val map_p : ('a -> 'b Lwt.t) -> 'a list -> 'b list Lwt.t

Mutexes

Lwt_mutex provides mutexes for Lwt. Its use is almost the same as the Mutex module of the thread library shipped with OCaml. In general, programs using Lwt do not need a lot of mutexes, because callbacks run without preempting each other. They are only useful for synchronising or sequencing complex operations spread over multiple callback calls.

Lists

The Lwt_list module defines iteration and scanning functions over lists, similar to the ones of the List module, but using functions that return a promise. For example:

val iter_s : ('a -> unit Lwt.t) -> 'a list -> unit Lwt.t
+val iter_p : ('a -> unit Lwt.t) -> 'a list -> unit Lwt.t

In iter_s f l, iter_s will call f on each elements of l, waiting for resolution between each element. On the contrary, in iter_p f l, iter_p will call f on all elements of l, only then wait for all the promises to resolve.

Data streams

Lwt streams are used in a lot of places in Lwt and its submodules. They offer a high-level interface to manipulate data flows.

A stream is an object which returns elements sequentially and lazily. Lazily means that the source of the stream is touched only for new elements when needed. This module contains a lot of stream transformation, iteration, and scanning functions.

The common way of creating a stream is by using Lwt_stream.from or by using Lwt_stream.create:

val from : (unit -> 'a option Lwt.t) -> 'a Lwt_stream.t
+val create : unit -> 'a Lwt_stream.t * ('a option -> unit)

As for streams of the standard library, from takes as argument a function which is used to create new elements.

create returns a function used to push new elements into the stream and the stream which will receive them.

For example:

# let stream, push = Lwt_stream.create ();;
+val stream : '_a Lwt_stream.t = <abstr>
+val push : '_a option -> unit = <fun>
+# push (Some 1);;
+- : unit = ()
+# push (Some 2);;
+- : unit = ()
+# push (Some 3);;
+- : unit = ()
+# Lwt.state (Lwt_stream.next stream);;
+- : int Lwt.state = Lwt.Return 1
+# Lwt.state (Lwt_stream.next stream);;
+- : int Lwt.state = Lwt.Return 2
+# Lwt.state (Lwt_stream.next stream);;
+- : int Lwt.state = Lwt.Return 3
+# Lwt.state (Lwt_stream.next stream);;
+- : int Lwt.state = Lwt.Sleep

Note that streams are consumable. Once you take an element from a stream, it is removed from the stream. So, if you want to iterate two times over a stream, you may consider “cloning” it, with Lwt_stream.clone. Cloned stream will return the same elements in the same order. Consuming one will not consume the other. For example:

# let s = Lwt_stream.of_list [1; 2];;
+val s : int Lwt_stream.t = <abstr>
+# let s' = Lwt_stream.clone s;;
+val s' : int Lwt_stream.t = <abstr>
+# Lwt.state (Lwt_stream.next s);;
+- : int Lwt.state = Lwt.Return 1
+# Lwt.state (Lwt_stream.next s);;
+- : int Lwt.state = Lwt.Return 2
+# Lwt.state (Lwt_stream.next s');;
+- : int Lwt.state = Lwt.Return 1
+# Lwt.state (Lwt_stream.next s');;
+- : int Lwt.state = Lwt.Return 2

Mailbox variables

The Lwt_mvar module provides mailbox variables. A mailbox variable, also called a “mvar”, is a cell which may contain 0 or 1 element. If it contains no elements, we say that the mvar is empty, if it contains one, we say that it is full. Adding an element to a full mvar will block until one is taken. Taking an element from an empty mvar will block until one is added.

Mailbox variables are commonly used to pass messages between chains of callbacks being executed concurrently.

Note that a mailbox variable can be seen as a pushable stream with a limited memory.

Running an Lwt program

An Lwt computation you have created will give you something of type Lwt.t, a promise. However, even though you have the promise, the computation may not have run yet, and the promise might still be pending.

For example if your program is just:

let _ = Lwt_io.printl "Hello, world!"

you have no guarantee that the promise for writing "Hello, world!" on the terminal will be resolved before the program exits. In order to wait for the promise to resolve, you have to call the function Lwt_main.run:

val Lwt_main.run : 'a Lwt.t -> 'a

This function waits for the given promise to resolve and returns its result. In fact it does more than that; it also runs the scheduler which is responsible for making asynchronous computations progress when events are received from the outside world.

So basically, when you write a Lwt program, you must call Lwt_main.run on your top-level, outer-most promise. For instance:

let () = Lwt_main.run (Lwt_io.printl "Hello, world!")

Note that you must not make nested calls to Lwt_main.run. It cannot be used anywhere else to get the result of a promise.

The lwt.unix library

The package lwt.unix contains all Unix-dependent modules of Lwt. Among all its features, it implements Lwt-friendly, non-blocking versions of functions of the OCaml standard and Unix libraries.

Unix primitives

Module Lwt_unix provides non-blocking system calls. For example, the Lwt counterpart of Unix.read is:

val read : file_descr -> string -> int -> int -> int Lwt.t

Lwt_io provides features similar to buffered channels of the standard library (of type in_channel or out_channel), but with non-blocking semantics.

Lwt_gc allows you to register a finalizer that returns a promise. At the end of the program, Lwt will wait for all these finalizers to resolve.

The Lwt scheduler

Operations doing I/O have to be resumed when some events are received by the process, so they can resolve their associated pending promises. For example, when you read from a file descriptor, you may have to wait for the file descriptor to become readable if no data are immediately available on it.

Lwt contains a scheduler which is responsible for managing multiple operations waiting for events, and restarting them when needed. This scheduler is implemented by the two modules Lwt_engine and Lwt_main. Lwt_engine is a low-level module, it provides a signature for custom I/O multiplexers as well as two built-in implementations, libev and select. The signature is given by the class Lwt_engine.t.

libev is used by default on Linux, because it supports any number of file descriptors, while select supports only 1024. libev is also much more efficient. On Windows, Unix.select is used because libev does not work properly. The user may change the backend in use at any time.

If you see an Invalid_argument error on Unix.select, it may be because the 1024 file descriptor limit was exceeded. Try switching to libev, if possible.

The engine can also be used directly in order to integrate other libraries with Lwt. For example, GTK needs to be notified when some events are received. If you use Lwt with GTK you need to use the Lwt scheduler to monitor GTK sources. This is what is done by the Lwt_glib library.

The Lwt_main module contains the main loop of Lwt. It is run by calling the function Lwt_main.run:

val Lwt_main.run : 'a Lwt.t -> 'a

This function continuously runs the scheduler until the promise passed as argument is resolved.

To make sure Lwt is compiled with libev support, tell opam that the library is available on the system by installing the conf-libev package. You may get the actual library with your system package manager:

Logging

For logging, we recommend the logs package from opam, which includes an Lwt-aware module Logs_lwt.

The Lwt.react library

The Lwt_react module provides helpers for using the react library with Lwt. It extends the React module by adding Lwt-specific functions. It can be used as a replacement of React. For example you can add at the beginning of your program:

open Lwt_react

instead of:

open React

or:

module React = Lwt_react

Among the added functionalities we have Lwt_react.E.next, which takes an event and returns a promise which will be pending until the next occurrence of this event. For example:

# open Lwt_react;;
+# let event, push = E.create ();;
+val event : '_a React.event = <abstr>
+val push : '_a -> unit = <fun>
+# let p = E.next event;;
+val p : '_a Lwt.t = <abstr>
+# Lwt.state p;;
+- : '_a Lwt.state = Lwt.Sleep
+# push 42;;
+- : unit = ()
+# Lwt.state p;;
+- : int Lwt.state = Lwt.Return 42

Another interesting feature is the ability to limit events (resp. signals) from occurring (resp. changing) too often. For example, suppose you are doing a program which displays something on the screen each time a signal changes. If at some point the signal changes 1000 times per second, you probably don't want to render it 1000 times per second. For that you use Lwt_react.S.limit:

val limit : (unit -> unit Lwt.t) -> 'a React.signal -> 'a React.signal

Lwt_react.S.limit f signal returns a signal which varies as signal except that two consecutive updates are separated by a call to f. For example if f returns a promise which is pending for 0.1 seconds, then there will be no more than 10 changes per second:

open Lwt_react
+
+let draw x =
+  (* Draw the screen *)
+  …
+
+let () =
+  (* The signal we are interested in: *)
+  let signal = … in
+
+  (* The limited signal: *)
+  let signal' = S.limit (fun () -> Lwt_unix.sleep 0.1) signal in
+
+  (* Redraw the screen each time the limited signal change: *)
+  S.notify_p draw signal'

Other libraries

Parallelise computations to other cores

If you have some compute-intensive steps within your program, you can execute them on a separate core. You can get performance benefits from the parallelisation. In addition, whilst your compute-intensive function is running on a different core, your normal I/O-bound tasks continue running on the original core.

The module Lwt_domain from the lwt_domain package provides all the necessary helpers to achieve this. It is based on the Domainslib library and uses similar concepts (such as tasks and pools).

First, you need to create a task pool:

val setup_pool : ?name:string -> int -> pool

Then you simple detach the function calls to the created pool:

val detach : pool -> ('a -> 'b) -> 'a -> 'b Lwt.t

The returned promise resolves as soon as the function returns.

Detaching computation to preemptive threads

It may happen that you want to run a function which will take time to compute or that you want to use a blocking function that cannot be used in a non-blocking way. For these situations, Lwt allows you to detach the computation to a preemptive thread.

This is done by the module Lwt_preemptive of the lwt.unix package which maintains a pool of system threads. The main function is:

val detach : ('a -> 'b) -> 'a -> 'b Lwt.t

detach f x will execute f x in another thread and return a pending promise, usable from the main thread, which will be fulfilled with the result of the preemptive thread.

If you want to trigger some Lwt operations from your detached thread, you have to call back into the main thread using Lwt_preemptive.run_in_main:

val run_in_main : (unit -> 'a Lwt.t) -> 'a

This is roughly the equivalent of Lwt.main_run, but for detached threads, rather than for the whole process. Note that you must not call Lwt_main.run in a detached thread.

SSL support

The library Lwt_ssl allows use of SSL asynchronously.

Writing stubs using Lwt

Thread-safe notifications

If you want to notify the main thread from another thread, you can use the Lwt thread safe notification system. First you need to create a notification identifier (which is just an integer) from the OCaml side using the Lwt_unix.make_notification function, then you can send it from either the OCaml code with Lwt_unix.send_notification function, or from the C code using the function lwt_unix_send_notification (defined in lwt_unix_.h).

Notifications are received and processed asynchronously by the main thread.

Jobs

For operations that cannot be executed asynchronously, Lwt uses a system of jobs that can be executed in a different threads. A job is composed of three functions:

With Lwt < 2.3.3, 4 functions (including 3 stubs) were required. It is still possible to use this mode but it is deprecated.

We show as example the implementation of Lwt_unix.mkdir. On the C side we have:

/**/
+/* Structure holding informations for calling [mkdir]. */
+struct job_mkdir {
+  /* Informations used by lwt.
+     It must be the first field of the structure. */
+  struct lwt_unix_job job;
+  /* This field store the result of the call. */
+  int result;
+  /* This field store the value of [errno] after the call. */
+  int errno_copy;
+  /* Pointer to a copy of the path parameter. */
+  char* path;
+  /* Copy of the mode parameter. */
+  int mode;
+  /* Buffer for storing the path. */
+  char data[];
+};
+
+/* The function calling [mkdir]. */
+static void worker_mkdir(struct job_mkdir* job)
+{
+  /* Perform the blocking call. */
+  job->result = mkdir(job->path, job->mode);
+  /* Save the value of errno. */
+  job->errno_copy = errno;
+}
+
+/* The function building the caml result. */
+static value result_mkdir(struct job_mkdir* job)
+{
+  /* Check for errors. */
+  if (job->result < 0) {
+    /* Save the value of errno so we can use it
+       once the job has been freed. */
+    int error = job->errno_copy;
+    /* Copy the contents of job->path into a caml string. */
+    value string_argument = caml_copy_string(job->path);
+    /* Free the job structure. */
+    lwt_unix_free_job(&job->job);
+    /* Raise the error. */
+    unix_error(error, "mkdir", string_argument);
+  }
+  /* Free the job structure. */
+  lwt_unix_free_job(&job->job);
+  /* Return the result. */
+  return Val_unit;
+}
+
+/* The stub creating the job structure. */
+CAMLprim value lwt_unix_mkdir_job(value path, value mode)
+{
+  /* Get the length of the path parameter. */
+  mlsize_t len_path = caml_string_length(path) + 1;
+  /* Allocate a new job. */
+  struct job_mkdir* job =
+    (struct job_mkdir*)lwt_unix_new_plus(struct job_mkdir, len_path);
+  /* Set the offset of the path parameter inside the job structure. */
+  job->path = job->data;
+  /* Copy the path parameter inside the job structure. */
+  memcpy(job->path, String_val(path), len_path);
+  /* Initialize function fields. */
+  job->job.worker = (lwt_unix_job_worker)worker_mkdir;
+  job->job.result = (lwt_unix_job_result)result_mkdir;
+  /* Copy the mode parameter. */
+  job->mode = Int_val(mode);
+  /* Wrap the structure into a caml value. */
+  return lwt_unix_alloc_job(&job->job);
+}

and on the ocaml side:

(* The stub for creating the job. *)
+external mkdir_job : string -> int -> unit job = "lwt_unix_mkdir_job"
+
+(* The ocaml function. *)
+let mkdir name perms = Lwt_unix.run_job (mkdir_job name perms)
diff --git a/moonpool/Moonpool/Trigger/index.html b/moonpool/Moonpool/Trigger/index.html index 6a317c28..e56c71e9 100644 --- a/moonpool/Moonpool/Trigger/index.html +++ b/moonpool/Moonpool/Trigger/index.html @@ -1,2 +1,2 @@ -Trigger (moonpool.Moonpool.Trigger)

Module Moonpool.Trigger

Triggers from picos

include module type of struct include Picos.Trigger end

Interface for suspending

Represents a trigger. A trigger can be in one of three states: initial, awaiting, or signaled.

ℹ️ Once a trigger becomes signaled it no longer changes state.

🏎️ A trigger in the initial and signaled states is a tiny object that does not hold onto any other objects.

val create : unit -> t

create () allocates a new trigger in the initial state.

val is_signaled : t -> bool

is_signaled trigger determines whether the trigger is in the signaled state.

This can be useful, for example, when a trigger is being inserted to multiple locations and might be signaled concurrently while doing so. In such a case one can periodically check with is_signaled trigger whether it makes sense to continue.

ℹ️ Computation.try_attach already checks that the trigger being inserted has not been signaled so when attaching a trigger to multiple computations there is no need to separately check with is_signaled.

val await : t -> (exn * Stdlib.Printexc.raw_backtrace) option

await trigger waits for the trigger to be signaled.

The return value is None in case the trigger has been signaled and the fiber was resumed normally. Otherwise the return value is Some (exn, bt), which indicates that the fiber has been canceled and the caller should raise the exception. In either case the caller is responsible for cleaning up. Usually this means making sure that no references to the trigger remain to avoid space leaks.

⚠️ As a rule of thumb, if you inserted the trigger to some data structure or attached it to some computation, then you are responsible for removing and detaching the trigger after await.

ℹ️ A trigger in the signaled state only takes a small constant amount of memory. Make sure that it is not possible for a program to accumulate unbounded numbers of signaled triggers under any circumstance.

⚠️ Only the owner or creator of a trigger may call await. It is considered an error to make multiple calls to await.

ℹ️ The behavior is that, unless await can return immediately,

  • on OCaml 5, await will perform the Await effect, and
  • on OCaml 4, await will call the await operation of the current handler.
  • raises Invalid_argument

    if the trigger was in the awaiting state, which means that multiple concurrent calls of await are being made.

Interface for resuming

val signal : t -> unit

signal trigger puts the trigger into the signaled state and calls the resume action, if any, attached using on_signal.

The intention is that calling signal trigger guarantees that any fiber awaiting the trigger will be resumed. However, when and whether a fiber having called await will be resumed normally or as canceled is determined by the scheduler that handles the Await effect.

ℹ️ Note that under normal circumstances, signal should never raise an exception. If an exception is raised by signal, it means that the handler of Await has a bug or some catastrophic failure has occurred.

⚠️ Do not call signal from an effect handler in a scheduler.

Interface for schedulers

val is_initial : t -> bool

is_initial trigger determines whether the trigger is in the initial or in the signaled state.

ℹ️ Consider using is_signaled instead of is_initial as in some contexts a trigger might reasonably be either in the initial or the awaiting state depending on the order in which things are being done.

  • raises Invalid_argument

    if the trigger was in the awaiting state.

val on_signal : t -> 'x -> 'y -> (t -> 'x -> 'y -> unit) -> bool

on_signal trigger x y resume attempts to attach the resume action to the trigger and transition the trigger to the awaiting state.

The return value is true in case the action was attached successfully. Otherwise the return value is false, which means that the trigger was already in the signaled state.

⚠️ The action that you attach to a trigger must be safe to call from any context that might end up signaling the trigger directly or indirectly through propagation. Unless you know, then you should assume that the resume action might be called from a different domain running in parallel with neither effect nor exception handlers and that if the attached action doesn't return the system may deadlock or if the action doesn't return quickly it may cause performance issues.

⚠️ It is considered an error to make multiple calls to on_signal with a specific trigger.

  • raises Invalid_argument

    if the trigger was in the awaiting state, which means that either the owner or creator of the trigger made concurrent calls to await or the handler called on_signal more than once.

  • alert handler Only a scheduler should call this in the handler of the Await effect to attach the scheduler specific resume action to the trigger. Annotate your effect handling function with [@alert "-handler"].
val from_action : 'x -> 'y -> (t -> 'x -> 'y -> unit) -> t

from_action x y resume is equivalent to let t = create () in assert (on_signal t x y resume); t.

⚠️ The action that you attach to a trigger must be safe to call from any context that might end up signaling the trigger directly or indirectly through propagation. Unless you know, then you should assume that the resume action might be called from a different domain running in parallel with neither effect nor exception handlers and that if the attached action doesn't return the system may deadlock or if the action doesn't return quickly it may cause performance issues.

⚠️ The returned trigger will be in the awaiting state, which means that it is an error to call await, on_signal, or dispose on it.

  • alert handler This is an escape hatch for experts implementing schedulers or structured concurrency mechanisms. If you know what you are doing, use [@alert "-handler"].
val dispose : t -> unit

dispose trigger transition the trigger from the initial state to the signaled state.

🚦 The intended use case of dispose is for use from the handler of Await to ensure that the trigger has been put to the signaled state after await returns.

  • raises Invalid_argument

    if the trigger was in the awaiting state.

type Stdlib.Effect.t += private
  1. | Await : t -> (exn * Stdlib.Printexc.raw_backtrace) option Stdlib.Effect.t

Schedulers must handle the Await effect to implement the behavior of await.

In case the fiber permits propagation of cancelation, the trigger must be attached to the computation of the fiber for the duration of suspending the fiber by the scheduler.

Typically the scheduler calls try_suspend, which in turn calls on_signal, to attach a scheduler specific resume action to the trigger. The scheduler must guarantee that the fiber will be resumed after signal has been called on the trigger.

Whether being resumed due to cancelation or not, the trigger must be either signaled outside of the effect handler, or disposed by the effect handler, before resuming the fiber.

In case the fiber permits propagation of cancelation and the computation associated with the fiber has been canceled the scheduler is free to continue the fiber immediately with the cancelation exception after disposing the trigger.

⚠️ A scheduler must not discontinue, i.e. raise an exception to, the fiber as a response to Await.

The scheduler is free to choose which ready fiber to resume next.

Design rationale

A key idea behind this design is that the handler for Await does not need to run arbitrary user defined code while suspending a fiber: the handler calls on_signal by itself. This should make it easier to get both the handler and the user code correct.

Another key idea is that the signal operation provides no feedback as to the outcome regarding cancelation. Calling signal merely guarantees that the caller of await will return. This means that the point at which cancelation must be determined can be as late as possible. A scheduler can check the cancelation status just before calling continue and it is, of course, possible to check the cancelation status earlier. This allows maximal flexibility for the handler of Await.

The consequence of this is that the only place to handle cancelation is at the point of await. This makes the design simpler and should make it easier for the user to get the handling of cancelation right. A minor detail is that await returns an option instead of raising an exception. The reason for this is that matching against an option is slightly faster than setting up an exception handler. Returning an option also clearly communicates the two different cases to handle.

On the other hand, the trigger mechanism does not have a way to specify a user-defined callback to perform cancelation immediately before the fiber is resumed. Such an immediately called callback could be useful for e.g. canceling an underlying IO request. One justification for not having such a callback is that cancelation is allowed to take place from outside of the scheduler, i.e. from another system level thread, and, in such a case, the callback could not be called immediately. Instead, the scheduler is free to choose how to schedule canceled and continued fibers and, assuming that fibers can be trusted, a scheduler may give priority to canceled fibers.

This design also separates the allocation of the atomic state for the trigger, or create, from await, and allows the state to be polled using is_signaled before calling await. This is particularly useful when the trigger might need to be inserted to multiple places and be signaled in parallel before the call of await.

No mechanism is provided to communicate any result with the signal. That can be done outside of the mechanism and is often not needed. This simplifies the design.

Once signal has been called, a trigger no longer refers to any other object and takes just two words of memory. This e.g. allows lazy removal of triggers, assuming the number of attached triggers can be bounded, because nothing except the trigger itself would be leaked.

To further understand the problem domain, in this design, in a suspend-resume scenario, there are three distinct pieces of state:

  1. The state of shared data structure(s) used for communication and / or synchronization.
  2. The state of the trigger.
  3. The cancelation status of the fiber.

The trigger and cancelation status are both updated independently and atomically through code in this interface. The key requirement left for the user is to make sure that the state of the shared data structure is updated correctly independently of what await returns. So, for example, a mutex implementation must check, after getting Some (exn, bt), what the state of the mutex is and how it should be updated.

val await_exn : t -> unit
+Trigger (moonpool.Moonpool.Trigger)

Module Moonpool.Trigger

Triggers from picos

include module type of struct include Picos.Trigger end

Interface for suspending

Represents a trigger. A trigger can be in one of three states: initial, awaiting, or signaled.

ℹ️ Once a trigger becomes signaled it no longer changes state.

🏎️ A trigger in the initial and signaled states is a tiny object that does not hold onto any other objects.

val create : unit -> t

create () allocates a new trigger in the initial state.

val is_signaled : t -> bool

is_signaled trigger determines whether the trigger is in the signaled state.

This can be useful, for example, when a trigger is being inserted to multiple locations and might be signaled concurrently while doing so. In such a case one can periodically check with is_signaled trigger whether it makes sense to continue.

ℹ️ Computation.try_attach already checks that the trigger being inserted has not been signaled so when attaching a trigger to multiple computations there is no need to separately check with is_signaled.

val await : t -> (exn * Stdlib.Printexc.raw_backtrace) option

await trigger waits for the trigger to be signaled.

The return value is None in case the trigger has been signaled and the fiber was resumed normally. Otherwise the return value is Some (exn, bt), which indicates that the fiber has been canceled and the caller should raise the exception. In either case the caller is responsible for cleaning up. Usually this means making sure that no references to the trigger remain to avoid space leaks.

⚠️ As a rule of thumb, if you inserted the trigger to some data structure or attached it to some computation, then you are responsible for removing and detaching the trigger after await.

ℹ️ A trigger in the signaled state only takes a small constant amount of memory. Make sure that it is not possible for a program to accumulate unbounded numbers of signaled triggers under any circumstance.

⚠️ Only the owner or creator of a trigger may call await. It is considered an error to make multiple calls to await.

ℹ️ The behavior is that, unless await can return immediately,

  • on OCaml 5, await will perform the Await effect, and
  • on OCaml 4, await will call the await operation of the current handler.
  • raises Invalid_argument

    if the trigger was in the awaiting state, which means that multiple concurrent calls of await are being made.

Interface for resuming

val signal : t -> unit

signal trigger puts the trigger into the signaled state and calls the resume action, if any, attached using on_signal.

The intention is that calling signal trigger guarantees that any fiber awaiting the trigger will be resumed. However, when and whether a fiber having called await will be resumed normally or as canceled is determined by the scheduler that handles the Await effect.

ℹ️ Note that under normal circumstances, signal should never raise an exception. If an exception is raised by signal, it means that the handler of Await has a bug or some catastrophic failure has occurred.

⚠️ Do not call signal from an effect handler in a scheduler.

Interface for schedulers

⚠️ The operations in this section are for more advanced use cases and their use requires a deeper understanding of how schedulers work.

val is_initial : t -> bool

is_initial trigger determines whether the trigger is in the initial or in the signaled state.

ℹ️ Consider using is_signaled instead of is_initial as in some contexts a trigger might reasonably be either in the initial or the awaiting state depending on the order in which things are being done.

  • raises Invalid_argument

    if the trigger was in the awaiting state.

val on_signal : t -> 'x -> 'y -> (t -> 'x -> 'y -> unit) -> bool

on_signal trigger x y resume attempts to attach the resume action to the trigger and transition the trigger to the awaiting state.

The return value is true in case the action was attached successfully. Otherwise the return value is false, which means that the trigger was already in the signaled state.

⚠️ The action that you attach to a trigger must be safe to call from any context that might end up signaling the trigger directly or indirectly through propagation. Unless you know, then you should assume that the resume action might be called from a different domain running in parallel with neither effect nor exception handlers and that if the attached action doesn't return the system may deadlock or if the action doesn't return quickly it may cause performance issues.

⚠️ It is considered an error to make multiple calls to on_signal with a specific trigger.

  • raises Invalid_argument

    if the trigger was in the awaiting state, which means that either the owner or creator of the trigger made concurrent calls to await or the handler called on_signal more than once.

  • alert handler Only a scheduler should call this in the handler of the Await effect to attach the scheduler specific resume action to the trigger. Annotate your effect handling function with [@alert "-handler"].
val from_action : 'x -> 'y -> (t -> 'x -> 'y -> unit) -> t

from_action x y resume is equivalent to let t = create () in assert (on_signal t x y resume); t.

ℹ️ This can useful when you just want to have an arbitrary callback executed when a trigger you attach to a computation is signaled.

⚠️ The action that you attach to a trigger must be safe to call from any context that might end up signaling the trigger directly or indirectly through propagation. Unless you know, then you should assume that the resume action might be called from a different domain running in parallel with neither effect nor exception handlers and that if the attached action doesn't return the system may deadlock or if the action doesn't return quickly it may cause performance issues.

⚠️ The returned trigger will be in the awaiting state, which means that it is an error to call await, on_signal, or dispose on it.

  • alert handler This is an escape hatch for experts implementing schedulers or structured concurrency mechanisms. If you know what you are doing, use [@alert "-handler"].
val dispose : t -> unit

dispose trigger transition the trigger from the initial state to the signaled state.

🚦 The intended use case of dispose is for use from the handler of Await to ensure that the trigger has been put to the signaled state after await returns.

  • raises Invalid_argument

    if the trigger was in the awaiting state.

type Stdlib.Effect.t += private
  1. | Await : t -> (exn * Stdlib.Printexc.raw_backtrace) option Stdlib.Effect.t

Schedulers must handle the Await effect to implement the behavior of await.

In case the fiber permits propagation of cancelation, the trigger must be attached to the computation of the fiber for the duration of suspending the fiber by the scheduler.

Typically the scheduler calls try_suspend, which in turn calls on_signal, to attach a scheduler specific resume action to the trigger. The scheduler must guarantee that the fiber will be resumed after signal has been called on the trigger.

Whether being resumed due to cancelation or not, the trigger must be either signaled outside of the effect handler, or disposed by the effect handler, before resuming the fiber.

In case the fiber permits propagation of cancelation and the computation associated with the fiber has been canceled the scheduler is free to continue the fiber immediately with the cancelation exception after disposing the trigger.

⚠️ A scheduler must not discontinue, i.e. raise an exception to, the fiber as a response to Await.

The scheduler is free to choose which ready fiber to resume next.

Design rationale

A key idea behind this design is that the handler for Await does not need to run arbitrary user defined code while suspending a fiber: the handler calls on_signal by itself. This should make it easier to get both the handler and the user code correct.

Another key idea is that the signal operation provides no feedback as to the outcome regarding cancelation. Calling signal merely guarantees that the caller of await will return. This means that the point at which cancelation must be determined can be as late as possible. A scheduler can check the cancelation status just before calling continue and it is, of course, possible to check the cancelation status earlier. This allows maximal flexibility for the handler of Await.

The consequence of this is that the only place to handle cancelation is at the point of await. This makes the design simpler and should make it easier for the user to get the handling of cancelation right. A minor detail is that await returns an option instead of raising an exception. The reason for this is that matching against an option is slightly faster than setting up an exception handler. Returning an option also clearly communicates the two different cases to handle.

On the other hand, the trigger mechanism does not have a way to specify a user-defined callback to perform cancelation immediately before the fiber is resumed. Such an immediately called callback could be useful for e.g. canceling an underlying IO request. One justification for not having such a callback is that cancelation is allowed to take place from outside of the scheduler, i.e. from another system level thread, and, in such a case, the callback could not be called immediately. Instead, the scheduler is free to choose how to schedule canceled and continued fibers and, assuming that fibers can be trusted, a scheduler may give priority to canceled fibers.

This design also separates the allocation of the atomic state for the trigger, or create, from await, and allows the state to be polled using is_signaled before calling await. This is particularly useful when the trigger might need to be inserted to multiple places and be signaled in parallel before the call of await.

No mechanism is provided to communicate any result with the signal. That can be done outside of the mechanism and is often not needed. This simplifies the design.

Once signal has been called, a trigger no longer refers to any other object and takes just two words of memory. This e.g. allows lazy removal of triggers, assuming the number of attached triggers can be bounded, because nothing except the trigger itself would be leaked.

To further understand the problem domain, in this design, in a suspend-resume scenario, there are three distinct pieces of state:

  1. The state of shared data structure(s) used for communication and / or synchronization.
  2. The state of the trigger.
  3. The cancelation status of the fiber.

The trigger and cancelation status are both updated independently and atomically through code in this interface. The key requirement left for the user is to make sure that the state of the shared data structure is updated correctly independently of what await returns. So, for example, a mutex implementation must check, after getting Some (exn, bt), what the state of the mutex is and how it should be updated.

val await_exn : t -> unit
diff --git a/picos/Picos/Computation/index.html b/picos/Picos/Computation/index.html index da55b4ea..37dc8b68 100644 --- a/picos/Picos/Computation/index.html +++ b/picos/Picos/Computation/index.html @@ -34,7 +34,7 @@ end

A fiber is always associated with seconds:float -> exn -> Stdlib.Printexc.raw_backtrace -> - unit

cancel_after ~seconds computation exn bt arranges to cancel the computation after the specified time with the specified exception and backtrace. Completion of the computation before the specified time effectively cancels the timeout.

ℹ️ The behavior is that cancel_after first checks that seconds is not negative, and then

Interface for polling

val is_running : 'a t -> bool

is_running computation determines whether the computation is in the running state meaning that it has not yet been completed.

val is_canceled : 'a t -> bool

is_canceled computation determines whether the computation is in the canceled state.

val canceled : 'a t -> (exn * Stdlib.Printexc.raw_backtrace) option

canceled computation returns the exception that the computation has been canceled with or returns None in case the computation has not been canceled.

val check : 'a t -> unit

check computation is equivalent to Option.iter (fun (exn, bt) -> Printexc.raise_with_backtrace exn bt) (canceled computation).

val peek : 'a t -> ('a, exn * Stdlib.Printexc.raw_backtrace) result option

peek computation returns the result of the computation or None in case the computation has not completed.

exception Running

Exception raised by peek_exn when it's used on a still running computation. This should never be surfaced to the user.

val peek_exn : 'a t -> 'a

peek_exn computation returns the result of the computation or raises an exception. It is important to catch the exception. If the computation was cancelled with exception exn then exn is re-raised with its original backtrace.

Interface for awaiting

val try_attach : 'a t -> Trigger.t -> bool

try_attach computation trigger tries to attach the trigger to be signaled on completion of the computation and returns true on success. Otherwise returns false, which means that the computation has already been completed or the trigger has already been signaled.

⚠️ Always detach a trigger after it is no longer needed unless the computation is known to have been completed.

val detach : 'a t -> Trigger.t -> unit

detach computation trigger signals the trigger and detaches it from the computation.

🏎️ The try_attach and detach operations essentially implement a lock-free bag. While not formally wait-free, the implementation is designed to avoid starvation by making sure that any potentially expensive operations are performed cooperatively.

val await : 'a t -> 'a

await computation waits for the computation to complete and either returns the value of the completed computation or raises the exception the computation was canceled with.

ℹ️ If the computation has already completed, then await returns or raises immediately without performing any effects.

val wait : _ t -> unit

wait computation waits for the computation to complete.

Interface for propagating cancelation

val canceler : from:_ t -> into:_ t -> Trigger.t

canceler ~from ~into creates a trigger that propagates cancelation from one computation into another on signal. The returned trigger is not attached to any computation.

The returned trigger is usually attached to the computation from which cancelation is to be propagated and the trigger should usually also be detached after it is no longer needed.

The intended use case of canceler is as a low level building block of structured concurrency mechanisms. Picos does not require concurrent programming models to be hierarchical or structured.

⚠️ The returned trigger will be in the awaiting state, which means that it is an error to call Trigger.await or Trigger.on_signal on it.

val attach_canceler : from:_ t -> into:_ t -> Trigger.t

attach_canceler ~from ~into tries to attach a canceler to the computation from to propagate cancelation to the computation into and returns the canceler when successful. If the computation from has already been canceled, the exception that from was canceled with will be raised.

Interface for schedulers

type Stdlib.Effect.t += private
  1. | Cancel_after : {
    1. seconds : float;
      (*

      Guaranteed to be non-negative.

      *)
    2. exn : exn;
    3. bt : Stdlib.Printexc.raw_backtrace;
    4. computation : 'a t;
    } -> unit Stdlib.Effect.t

Schedulers must handle the Cancel_after effect to implement the behavior of cancel_after.

The scheduler should typically attach a trigger to the computation passed with the effect and arrange the timeout to be canceled upon signal to avoid space leaks.

The scheduler should measure time using a monotonic clock.

In case the fiber permits propagation of cancelation and the computation associated with the fiber has been canceled the scheduler is free to discontinue the fiber before setting up the timeout.

If the fiber is continued normally, i.e. without raising an exception, the scheduler should guarantee that the cancelation will be delivered eventually.

The scheduler is free to choose which ready fiber to resume next.

val with_action : + unit

cancel_after ~seconds computation exn bt arranges to cancel the computation after the specified time with the specified exception and backtrace. Completion of the computation before the specified time effectively cancels the timeout.

ℹ️ The behavior is that cancel_after first checks that seconds is not negative, and then

Interface for polling

val is_running : 'a t -> bool

is_running computation determines whether the computation is in the running state meaning that it has not yet been completed.

val is_canceled : 'a t -> bool

is_canceled computation determines whether the computation is in the canceled state.

val canceled : 'a t -> (exn * Stdlib.Printexc.raw_backtrace) option

canceled computation returns the exception that the computation has been canceled with or returns None in case the computation has not been canceled.

val check : 'a t -> unit

check computation is equivalent to Option.iter (fun (exn, bt) -> Printexc.raise_with_backtrace exn bt) (canceled computation).

val peek : 'a t -> ('a, exn * Stdlib.Printexc.raw_backtrace) result option

peek computation returns the result of the computation or None in case the computation has not completed.

exception Running

Exception raised by peek_exn when it's used on a still running computation. This should never be surfaced to the user.

val peek_exn : 'a t -> 'a

peek_exn computation returns the result of the computation or raises an exception. It is important to catch the exception. If the computation was cancelled with exception exn then exn is re-raised with its original backtrace.

Interface for awaiting

val try_attach : 'a t -> Trigger.t -> bool

try_attach computation trigger tries to attach the trigger to be signaled on completion of the computation and returns true on success. Otherwise returns false, which means that the computation has already been completed or the trigger has already been signaled.

⚠️ Always detach a trigger after it is no longer needed unless the computation is known to have been completed.

val detach : 'a t -> Trigger.t -> unit

detach computation trigger signals the trigger and detaches it from the computation.

🏎️ The try_attach and detach operations essentially implement a lock-free bag. While not formally wait-free, the implementation is designed to avoid starvation by making sure that any potentially expensive operations are performed cooperatively.

val await : 'a t -> 'a

await computation waits for the computation to complete and either returns the value of the completed computation or raises the exception the computation was canceled with.

ℹ️ If the computation has already completed, then await returns or raises immediately without performing any effects.

val wait : _ t -> unit

wait computation waits for the computation to complete.

Interface for propagating cancelation

val canceler : from:_ t -> into:_ t -> Trigger.t

canceler ~from ~into creates a trigger that propagates cancelation from one computation into another on signal. The returned trigger is not attached to any computation.

The caller usually attaches the returned trigger to the computation from which cancelation is to be propagated and the trigger should usually also be detached after it is no longer needed. See also attach_canceler.

The intended use case of canceler is as a low level building block of structured concurrency mechanisms. Picos does not require concurrent programming models to be hierarchical or structured.

⚠️ The returned trigger will be in the awaiting state, which means that it is an error to call Trigger.await or Trigger.on_signal on it.

val attach_canceler : from:_ t -> into:_ t -> Trigger.t

attach_canceler ~from ~into tries to attach a canceler to the computation from to propagate cancelation to the computation into and returns the canceler when successful. If the computation from has already been canceled, the exception that from was canceled with will be raised.

Interface for schedulers

type Stdlib.Effect.t += private
  1. | Cancel_after : {
    1. seconds : float;
      (*

      Guaranteed to be non-negative.

      *)
    2. exn : exn;
    3. bt : Stdlib.Printexc.raw_backtrace;
    4. computation : 'a t;
    } -> unit Stdlib.Effect.t

Schedulers must handle the Cancel_after effect to implement the behavior of cancel_after.

The scheduler should typically attach a trigger to the computation passed with the effect and arrange the timeout to be canceled upon signal to avoid space leaks.

The scheduler should measure time using a monotonic clock.

In case the fiber permits propagation of cancelation and the computation associated with the fiber has been canceled the scheduler is free to discontinue the fiber before setting up the timeout.

If the fiber is continued normally, i.e. without raising an exception, the scheduler should guarantee that the cancelation will be delivered eventually.

The scheduler is free to choose which ready fiber to resume next.

val with_action : ?mode:[ `FIFO | `LIFO ] -> 'x -> 'y -> diff --git a/picos/Picos/Fiber/FLS/index.html b/picos/Picos/Fiber/FLS/index.html index 2caea212..cc240c3e 100644 --- a/picos/Picos/Fiber/FLS/index.html +++ b/picos/Picos/Fiber/FLS/index.html @@ -1,2 +1,2 @@ -FLS (picos.Picos.Fiber.FLS)

Module Fiber.FLS

Fiber local storage

Fiber local storage is intended for use as a low overhead storage mechanism for fiber extensions. For example, one might associate a priority value with each fiber for a scheduler that uses a priority queue or one might use FLS to store unique id values for fibers.

type fiber := t

Destructively substituted alias for Fiber.t.

type 'a t

Represents a key for storing values of type 'a in storage associated with fibers.

val create : unit -> 'a t

new_key initial allocates a new key for associating values in storage associated with fibers. The initial value for every fiber is either the given Constant or is Computed with the given function. If the initial value is a constant, no value needs to be stored unless the value is explicitly updated.

⚠️ New keys should not be created dynamically.

exception Not_set

Raised by get_exn in case value has not been set.

val get_exn : fiber -> 'a t -> 'a

get_exn fiber key returns the value associated with the key in the storage associated with the fiber or raises Not_set using raise_notrace.

⚠️ It is only safe to call get_exn from the fiber itself or when the fiber is known not to be running.

val get : fiber -> 'a t -> default:'a -> 'a

get fiber key ~default returns the value associated with the key in the storage associated with the fiber or the default value.

⚠️ It is only safe to call get from the fiber itself or when the fiber is known not to be running.

val set : fiber -> 'a t -> 'a -> unit

set fiber key value sets the value associated with the key to the given value in the storage associated with the fiber.

⚠️ It is only safe to call set from the fiber itself or when the fiber is known not to be running.

val remove : fiber -> 'a t -> unit

remove fiber key removes the value, if any, associated with the key from the storage associated with the fiber.

⚠️ It is only safe to call remove from the fiber itself or when the fiber is known not to be running.

val reserve : fiber -> 'a t -> unit

reserve fiber key ensures that sufficient space has been allocated to associate a value with the specified key such that a subsequent set with the key will not allocate.

ℹ️ This can be used to optimize the population of the FLS and to avoid performing memory allocations in critical sections.

⚠️ It is only safe to call reserve from the fiber itself or when the fiber is known not to be running.

+FLS (picos.Picos.Fiber.FLS)

Module Fiber.FLS

Fiber local storage

Fiber local storage is intended for use as a low overhead storage mechanism for fiber extensions. For example, one might associate a priority value with each fiber for a scheduler that uses a priority queue or one might use FLS to store unique id values for fibers.

type fiber := t

Destructively substituted alias for Fiber.t.

type 'a t

Represents a key for storing values of type 'a in storage associated with fibers.

val create : unit -> 'a t

create () allocates a new key for associating values in storage associated with fibers.

⚠️ New keys should not be created dynamically.

exception Not_set

Raised by get_exn in case value has not been set.

val get_exn : fiber -> 'a t -> 'a

get_exn fiber key returns the value associated with the key in the storage associated with the fiber or raises Not_set using raise_notrace.

⚠️ It is only safe to call get_exn from the fiber itself or when the fiber is known not to be running.

val get : fiber -> 'a t -> default:'a -> 'a

get fiber key ~default returns the value associated with the key in the storage associated with the fiber or the default value.

⚠️ It is only safe to call get from the fiber itself or when the fiber is known not to be running.

val set : fiber -> 'a t -> 'a -> unit

set fiber key value sets the value associated with the key to the given value in the storage associated with the fiber.

⚠️ It is only safe to call set from the fiber itself or when the fiber is known not to be running.

val remove : fiber -> 'a t -> unit

remove fiber key removes the value, if any, associated with the key from the storage associated with the fiber.

⚠️ It is only safe to call remove from the fiber itself or when the fiber is known not to be running.

val reserve : fiber -> 'a t -> unit

reserve fiber key ensures that sufficient space has been allocated to associate a value with the specified key such that a subsequent set with the key will not allocate.

ℹ️ This can be used to optimize the population of the FLS and to avoid performing memory allocations in critical sections.

⚠️ It is only safe to call reserve from the fiber itself or when the fiber is known not to be running.

diff --git a/picos/Picos/Trigger/index.html b/picos/Picos/Trigger/index.html index d70eab07..cecfcb1d 100644 --- a/picos/Picos/Trigger/index.html +++ b/picos/Picos/Trigger/index.html @@ -15,4 +15,4 @@ | Some (exn, bt) -> (* We were canceled. *) Printexc.raise_with_backtrace exn bt -end

⚠️ Typically we need to cleanup after await, but in the above example we didn't insert the trigger into any data structure nor did we attach the trigger to any computation.

All operations on triggers are wait-free, with the obvious exception of await. The signal operation inherits the properties of the action attached with on_signal to the trigger.

Interface for suspending

type t

Represents a trigger. A trigger can be in one of three states: initial, awaiting, or signaled.

ℹ️ Once a trigger becomes signaled it no longer changes state.

🏎️ A trigger in the initial and signaled states is a tiny object that does not hold onto any other objects.

val create : unit -> t

create () allocates a new trigger in the initial state.

val is_signaled : t -> bool

is_signaled trigger determines whether the trigger is in the signaled state.

This can be useful, for example, when a trigger is being inserted to multiple locations and might be signaled concurrently while doing so. In such a case one can periodically check with is_signaled trigger whether it makes sense to continue.

ℹ️ Computation.try_attach already checks that the trigger being inserted has not been signaled so when attaching a trigger to multiple computations there is no need to separately check with is_signaled.

val await : t -> (exn * Stdlib.Printexc.raw_backtrace) option

await trigger waits for the trigger to be signaled.

The return value is None in case the trigger has been signaled and the fiber was resumed normally. Otherwise the return value is Some (exn, bt), which indicates that the fiber has been canceled and the caller should raise the exception. In either case the caller is responsible for cleaning up. Usually this means making sure that no references to the trigger remain to avoid space leaks.

⚠️ As a rule of thumb, if you inserted the trigger to some data structure or attached it to some computation, then you are responsible for removing and detaching the trigger after await.

ℹ️ A trigger in the signaled state only takes a small constant amount of memory. Make sure that it is not possible for a program to accumulate unbounded numbers of signaled triggers under any circumstance.

⚠️ Only the owner or creator of a trigger may call await. It is considered an error to make multiple calls to await.

ℹ️ The behavior is that, unless await can return immediately,

  • on OCaml 5, await will perform the Await effect, and
  • on OCaml 4, await will call the await operation of the current handler.
  • raises Invalid_argument

    if the trigger was in the awaiting state, which means that multiple concurrent calls of await are being made.

Interface for resuming

val signal : t -> unit

signal trigger puts the trigger into the signaled state and calls the resume action, if any, attached using on_signal.

The intention is that calling signal trigger guarantees that any fiber awaiting the trigger will be resumed. However, when and whether a fiber having called await will be resumed normally or as canceled is determined by the scheduler that handles the Await effect.

ℹ️ Note that under normal circumstances, signal should never raise an exception. If an exception is raised by signal, it means that the handler of Await has a bug or some catastrophic failure has occurred.

⚠️ Do not call signal from an effect handler in a scheduler.

Interface for schedulers

val is_initial : t -> bool

is_initial trigger determines whether the trigger is in the initial or in the signaled state.

ℹ️ Consider using is_signaled instead of is_initial as in some contexts a trigger might reasonably be either in the initial or the awaiting state depending on the order in which things are being done.

  • raises Invalid_argument

    if the trigger was in the awaiting state.

val on_signal : t -> 'x -> 'y -> (t -> 'x -> 'y -> unit) -> bool

on_signal trigger x y resume attempts to attach the resume action to the trigger and transition the trigger to the awaiting state.

The return value is true in case the action was attached successfully. Otherwise the return value is false, which means that the trigger was already in the signaled state.

⚠️ The action that you attach to a trigger must be safe to call from any context that might end up signaling the trigger directly or indirectly through propagation. Unless you know, then you should assume that the resume action might be called from a different domain running in parallel with neither effect nor exception handlers and that if the attached action doesn't return the system may deadlock or if the action doesn't return quickly it may cause performance issues.

⚠️ It is considered an error to make multiple calls to on_signal with a specific trigger.

  • raises Invalid_argument

    if the trigger was in the awaiting state, which means that either the owner or creator of the trigger made concurrent calls to await or the handler called on_signal more than once.

  • alert handler Only a scheduler should call this in the handler of the Await effect to attach the scheduler specific resume action to the trigger. Annotate your effect handling function with [@alert "-handler"].
val from_action : 'x -> 'y -> (t -> 'x -> 'y -> unit) -> t

from_action x y resume is equivalent to let t = create () in assert (on_signal t x y resume); t.

⚠️ The action that you attach to a trigger must be safe to call from any context that might end up signaling the trigger directly or indirectly through propagation. Unless you know, then you should assume that the resume action might be called from a different domain running in parallel with neither effect nor exception handlers and that if the attached action doesn't return the system may deadlock or if the action doesn't return quickly it may cause performance issues.

⚠️ The returned trigger will be in the awaiting state, which means that it is an error to call await, on_signal, or dispose on it.

  • alert handler This is an escape hatch for experts implementing schedulers or structured concurrency mechanisms. If you know what you are doing, use [@alert "-handler"].
val dispose : t -> unit

dispose trigger transition the trigger from the initial state to the signaled state.

🚦 The intended use case of dispose is for use from the handler of Await to ensure that the trigger has been put to the signaled state after await returns.

  • raises Invalid_argument

    if the trigger was in the awaiting state.

type Stdlib.Effect.t += private
  1. | Await : t -> (exn * Stdlib.Printexc.raw_backtrace) option Stdlib.Effect.t

Schedulers must handle the Await effect to implement the behavior of await.

In case the fiber permits propagation of cancelation, the trigger must be attached to the computation of the fiber for the duration of suspending the fiber by the scheduler.

Typically the scheduler calls try_suspend, which in turn calls on_signal, to attach a scheduler specific resume action to the trigger. The scheduler must guarantee that the fiber will be resumed after signal has been called on the trigger.

Whether being resumed due to cancelation or not, the trigger must be either signaled outside of the effect handler, or disposed by the effect handler, before resuming the fiber.

In case the fiber permits propagation of cancelation and the computation associated with the fiber has been canceled the scheduler is free to continue the fiber immediately with the cancelation exception after disposing the trigger.

⚠️ A scheduler must not discontinue, i.e. raise an exception to, the fiber as a response to Await.

The scheduler is free to choose which ready fiber to resume next.

Design rationale

A key idea behind this design is that the handler for Await does not need to run arbitrary user defined code while suspending a fiber: the handler calls on_signal by itself. This should make it easier to get both the handler and the user code correct.

Another key idea is that the signal operation provides no feedback as to the outcome regarding cancelation. Calling signal merely guarantees that the caller of await will return. This means that the point at which cancelation must be determined can be as late as possible. A scheduler can check the cancelation status just before calling continue and it is, of course, possible to check the cancelation status earlier. This allows maximal flexibility for the handler of Await.

The consequence of this is that the only place to handle cancelation is at the point of await. This makes the design simpler and should make it easier for the user to get the handling of cancelation right. A minor detail is that await returns an option instead of raising an exception. The reason for this is that matching against an option is slightly faster than setting up an exception handler. Returning an option also clearly communicates the two different cases to handle.

On the other hand, the trigger mechanism does not have a way to specify a user-defined callback to perform cancelation immediately before the fiber is resumed. Such an immediately called callback could be useful for e.g. canceling an underlying IO request. One justification for not having such a callback is that cancelation is allowed to take place from outside of the scheduler, i.e. from another system level thread, and, in such a case, the callback could not be called immediately. Instead, the scheduler is free to choose how to schedule canceled and continued fibers and, assuming that fibers can be trusted, a scheduler may give priority to canceled fibers.

This design also separates the allocation of the atomic state for the trigger, or create, from await, and allows the state to be polled using is_signaled before calling await. This is particularly useful when the trigger might need to be inserted to multiple places and be signaled in parallel before the call of await.

No mechanism is provided to communicate any result with the signal. That can be done outside of the mechanism and is often not needed. This simplifies the design.

Once signal has been called, a trigger no longer refers to any other object and takes just two words of memory. This e.g. allows lazy removal of triggers, assuming the number of attached triggers can be bounded, because nothing except the trigger itself would be leaked.

To further understand the problem domain, in this design, in a suspend-resume scenario, there are three distinct pieces of state:

  1. The state of shared data structure(s) used for communication and / or synchronization.
  2. The state of the trigger.
  3. The cancelation status of the fiber.

The trigger and cancelation status are both updated independently and atomically through code in this interface. The key requirement left for the user is to make sure that the state of the shared data structure is updated correctly independently of what await returns. So, for example, a mutex implementation must check, after getting Some (exn, bt), what the state of the mutex is and how it should be updated.

+end

⚠️ Typically we need to cleanup after await, but in the above example we didn't insert the trigger into any data structure nor did we attach the trigger to any computation.

All operations on triggers are wait-free, with the obvious exception of await. The signal operation inherits the properties of the action attached with on_signal to the trigger.

Interface for suspending

type t

Represents a trigger. A trigger can be in one of three states: initial, awaiting, or signaled.

ℹ️ Once a trigger becomes signaled it no longer changes state.

🏎️ A trigger in the initial and signaled states is a tiny object that does not hold onto any other objects.

val create : unit -> t

create () allocates a new trigger in the initial state.

val is_signaled : t -> bool

is_signaled trigger determines whether the trigger is in the signaled state.

This can be useful, for example, when a trigger is being inserted to multiple locations and might be signaled concurrently while doing so. In such a case one can periodically check with is_signaled trigger whether it makes sense to continue.

ℹ️ Computation.try_attach already checks that the trigger being inserted has not been signaled so when attaching a trigger to multiple computations there is no need to separately check with is_signaled.

val await : t -> (exn * Stdlib.Printexc.raw_backtrace) option

await trigger waits for the trigger to be signaled.

The return value is None in case the trigger has been signaled and the fiber was resumed normally. Otherwise the return value is Some (exn, bt), which indicates that the fiber has been canceled and the caller should raise the exception. In either case the caller is responsible for cleaning up. Usually this means making sure that no references to the trigger remain to avoid space leaks.

⚠️ As a rule of thumb, if you inserted the trigger to some data structure or attached it to some computation, then you are responsible for removing and detaching the trigger after await.

ℹ️ A trigger in the signaled state only takes a small constant amount of memory. Make sure that it is not possible for a program to accumulate unbounded numbers of signaled triggers under any circumstance.

⚠️ Only the owner or creator of a trigger may call await. It is considered an error to make multiple calls to await.

ℹ️ The behavior is that, unless await can return immediately,

  • on OCaml 5, await will perform the Await effect, and
  • on OCaml 4, await will call the await operation of the current handler.
  • raises Invalid_argument

    if the trigger was in the awaiting state, which means that multiple concurrent calls of await are being made.

Interface for resuming

val signal : t -> unit

signal trigger puts the trigger into the signaled state and calls the resume action, if any, attached using on_signal.

The intention is that calling signal trigger guarantees that any fiber awaiting the trigger will be resumed. However, when and whether a fiber having called await will be resumed normally or as canceled is determined by the scheduler that handles the Await effect.

ℹ️ Note that under normal circumstances, signal should never raise an exception. If an exception is raised by signal, it means that the handler of Await has a bug or some catastrophic failure has occurred.

⚠️ Do not call signal from an effect handler in a scheduler.

Interface for schedulers

⚠️ The operations in this section are for more advanced use cases and their use requires a deeper understanding of how schedulers work.

val is_initial : t -> bool

is_initial trigger determines whether the trigger is in the initial or in the signaled state.

ℹ️ Consider using is_signaled instead of is_initial as in some contexts a trigger might reasonably be either in the initial or the awaiting state depending on the order in which things are being done.

  • raises Invalid_argument

    if the trigger was in the awaiting state.

val on_signal : t -> 'x -> 'y -> (t -> 'x -> 'y -> unit) -> bool

on_signal trigger x y resume attempts to attach the resume action to the trigger and transition the trigger to the awaiting state.

The return value is true in case the action was attached successfully. Otherwise the return value is false, which means that the trigger was already in the signaled state.

⚠️ The action that you attach to a trigger must be safe to call from any context that might end up signaling the trigger directly or indirectly through propagation. Unless you know, then you should assume that the resume action might be called from a different domain running in parallel with neither effect nor exception handlers and that if the attached action doesn't return the system may deadlock or if the action doesn't return quickly it may cause performance issues.

⚠️ It is considered an error to make multiple calls to on_signal with a specific trigger.

  • raises Invalid_argument

    if the trigger was in the awaiting state, which means that either the owner or creator of the trigger made concurrent calls to await or the handler called on_signal more than once.

  • alert handler Only a scheduler should call this in the handler of the Await effect to attach the scheduler specific resume action to the trigger. Annotate your effect handling function with [@alert "-handler"].
val from_action : 'x -> 'y -> (t -> 'x -> 'y -> unit) -> t

from_action x y resume is equivalent to let t = create () in assert (on_signal t x y resume); t.

ℹ️ This can useful when you just want to have an arbitrary callback executed when a trigger you attach to a computation is signaled.

⚠️ The action that you attach to a trigger must be safe to call from any context that might end up signaling the trigger directly or indirectly through propagation. Unless you know, then you should assume that the resume action might be called from a different domain running in parallel with neither effect nor exception handlers and that if the attached action doesn't return the system may deadlock or if the action doesn't return quickly it may cause performance issues.

⚠️ The returned trigger will be in the awaiting state, which means that it is an error to call await, on_signal, or dispose on it.

  • alert handler This is an escape hatch for experts implementing schedulers or structured concurrency mechanisms. If you know what you are doing, use [@alert "-handler"].
val dispose : t -> unit

dispose trigger transition the trigger from the initial state to the signaled state.

🚦 The intended use case of dispose is for use from the handler of Await to ensure that the trigger has been put to the signaled state after await returns.

  • raises Invalid_argument

    if the trigger was in the awaiting state.

type Stdlib.Effect.t += private
  1. | Await : t -> (exn * Stdlib.Printexc.raw_backtrace) option Stdlib.Effect.t

Schedulers must handle the Await effect to implement the behavior of await.

In case the fiber permits propagation of cancelation, the trigger must be attached to the computation of the fiber for the duration of suspending the fiber by the scheduler.

Typically the scheduler calls try_suspend, which in turn calls on_signal, to attach a scheduler specific resume action to the trigger. The scheduler must guarantee that the fiber will be resumed after signal has been called on the trigger.

Whether being resumed due to cancelation or not, the trigger must be either signaled outside of the effect handler, or disposed by the effect handler, before resuming the fiber.

In case the fiber permits propagation of cancelation and the computation associated with the fiber has been canceled the scheduler is free to continue the fiber immediately with the cancelation exception after disposing the trigger.

⚠️ A scheduler must not discontinue, i.e. raise an exception to, the fiber as a response to Await.

The scheduler is free to choose which ready fiber to resume next.

Design rationale

A key idea behind this design is that the handler for Await does not need to run arbitrary user defined code while suspending a fiber: the handler calls on_signal by itself. This should make it easier to get both the handler and the user code correct.

Another key idea is that the signal operation provides no feedback as to the outcome regarding cancelation. Calling signal merely guarantees that the caller of await will return. This means that the point at which cancelation must be determined can be as late as possible. A scheduler can check the cancelation status just before calling continue and it is, of course, possible to check the cancelation status earlier. This allows maximal flexibility for the handler of Await.

The consequence of this is that the only place to handle cancelation is at the point of await. This makes the design simpler and should make it easier for the user to get the handling of cancelation right. A minor detail is that await returns an option instead of raising an exception. The reason for this is that matching against an option is slightly faster than setting up an exception handler. Returning an option also clearly communicates the two different cases to handle.

On the other hand, the trigger mechanism does not have a way to specify a user-defined callback to perform cancelation immediately before the fiber is resumed. Such an immediately called callback could be useful for e.g. canceling an underlying IO request. One justification for not having such a callback is that cancelation is allowed to take place from outside of the scheduler, i.e. from another system level thread, and, in such a case, the callback could not be called immediately. Instead, the scheduler is free to choose how to schedule canceled and continued fibers and, assuming that fibers can be trusted, a scheduler may give priority to canceled fibers.

This design also separates the allocation of the atomic state for the trigger, or create, from await, and allows the state to be polled using is_signaled before calling await. This is particularly useful when the trigger might need to be inserted to multiple places and be signaled in parallel before the call of await.

No mechanism is provided to communicate any result with the signal. That can be done outside of the mechanism and is often not needed. This simplifies the design.

Once signal has been called, a trigger no longer refers to any other object and takes just two words of memory. This e.g. allows lazy removal of triggers, assuming the number of attached triggers can be bounded, because nothing except the trigger itself would be leaked.

To further understand the problem domain, in this design, in a suspend-resume scenario, there are three distinct pieces of state:

  1. The state of shared data structure(s) used for communication and / or synchronization.
  2. The state of the trigger.
  3. The cancelation status of the fiber.

The trigger and cancelation status are both updated independently and atomically through code in this interface. The key requirement left for the user is to make sure that the state of the shared data structure is updated correctly independently of what await returns. So, for example, a mutex implementation must check, after getting Some (exn, bt), what the state of the mutex is and how it should be updated.

diff --git a/picos/_doc-dir/CHANGES.md b/picos/_doc-dir/CHANGES.md index 5591b7b4..eb5ce11f 100644 --- a/picos/_doc-dir/CHANGES.md +++ b/picos/_doc-dir/CHANGES.md @@ -1,7 +1,20 @@ -## Next version +## 0.6.0 +- Added a futex-like `Awaitable` abstraction as the `picos_std.awaitable` + library (@polytypic) +- Changed the core Picos library to be internally built from a single `.ml` file + (@polytypic) +- Optimized heap and stack usage of fibers and resource cleanup mechanisms and + added workarounds for compiler generated space leaks due to closures + (@polytypic) +- Added `lastly` as a safe alternative to `Fun.protect` (@polytypic) +- Workarounds for the `Uri` library not being threadsafe (@polytypic) +- Fixed to raise proper error when `Picos_io_select` has not been configured + properly (@polytypic) - Forbid cancelation propagation during `release` calls in the `picos_std.finally` library (@polytypic) + - This is a change in behaviour and could be seen as a breaking change, but it + should really be considered a bug fix. - Renamed `(Ivar|Stream).poison` to `(Ivar|Stream).poison_at` and added `(Ivar|Stream).poison` with optional `?callstack:int` (@polytypic) diff --git a/picos_std/Picos_std_awaitable/Awaitable/Awaiter/index.html b/picos_std/Picos_std_awaitable/Awaitable/Awaiter/index.html new file mode 100644 index 00000000..a7702f2d --- /dev/null +++ b/picos_std/Picos_std_awaitable/Awaitable/Awaiter/index.html @@ -0,0 +1,2 @@ + +Awaiter (picos_std.Picos_std_awaitable.Awaitable.Awaiter)

Module Awaitable.Awaiter

Low level interface for more flexible waiting.

type 'a awaitable := 'a t

An erased type alias for Awaitable.t.

type t

Represents a single use awaiter of a signal to an awaitable.

val add : 'a awaitable -> Picos.Trigger.t -> t

add awaitable trigger creates a single use awaiter, adds it to the FIFO associated with the awaitable, and returns the awaiter.

val remove : t -> unit

remove awaiter marks the awaiter as having been signaled and removes it from the FIFO associated with the awaitable.

ℹ️ If the associated trigger is used with only one awaiter and the Trigger.awaitawait on the trigger returns None, there is no need to explicitly remove the awaiter, because it has already been removed.

diff --git a/picos_std/Picos_std_awaitable/Awaitable/index.html b/picos_std/Picos_std_awaitable/Awaitable/index.html new file mode 100644 index 00000000..92a78612 --- /dev/null +++ b/picos_std/Picos_std_awaitable/Awaitable/index.html @@ -0,0 +1,2 @@ + +Awaitable (picos_std.Picos_std_awaitable.Awaitable)

Module Picos_std_awaitable.Awaitable

An awaitable atomic location.

This module provides a superset of the Stdlib Atomic API with more or less identical performance. The main difference is that a non-padded awaitable location takes an extra word of memory. Additionally a futex-like API provides the ability to await until an awaitable location is explicitly signaled to potentially have a different value.

Awaitable locations can be used to implement many kinds of synchronization and communication primitives.

Atomic API

type !'a t

Represents an awaitable atomic location.

val make : ?padded:bool -> 'a -> 'a t

make initial creates a new awaitable atomic location with the given initial value.

val make_contended : 'a -> 'a t

make_contended initial is equivalent to make ~padded:true initial.

val get : 'a t -> 'a

get awaitable is essentially equivalent to Atomic.get awaitable.

val compare_and_set : 'a t -> 'a -> 'a -> bool

compare_and_set awaitable before after is essentially equivalent to Atomic.compare_and_set awaitable before after.

val exchange : 'a t -> 'a -> 'a

exchange awaitable after is essentially equivalent to Atomic.exchange awaitable after.

val set : 'a t -> 'a -> unit

set awaitable value is equivalent to exchange awaitable value |> ignore.

val fetch_and_add : int t -> int -> int

fetch_and_add awaitable delta is essentially equivalent to Atomic.fetch_and_add awaitable delta.

val incr : int t -> unit

incr awaitable is equivalent to fetch_and_add awaitable (+1) |> ignore.

val decr : int t -> unit

incr awaitable is equivalent to fetch_and_add awaitable (-1) |> ignore.

Futex API

val signal : 'a t -> unit

signal awaitable tries to wake up one fiber awaitin on the awaitable location.

🐌 Generally speaking one should avoid calling signal too frequently, because the queue of awaiters is stored separately from the awaitable location and it takes a bit of effort to locate it. For example, calling signal every time a value is added to an empty data structure might not be optimal. In many cases it is faster to explicitly mark the potential presence of awaiters in the data structure and avoid calling signal when it is definitely known that there are no awaiters.

val broadcast : 'a t -> unit

broadcast awaitable tries to wake up all fibers awaiting on the awaitable location.

🐌 The same advice as with signal applies to broadcast. In addition, it is typically a good idea to avoid potentially waking up large numbers of fibers as it can easily lead to the thundering herd phenomana.

val await : 'a t -> 'a -> unit

await awaitable before suspends the current fiber until the awaitable is explicitly signaled and has a value other than before.

⚠️ This operation is subject to the ABA problem. An await for value other than A may not return after the awaitable is signaled while having the value B, because at a later point the awaitable has again the value A. Furthermore, by the time an await for value other than A returns, the awaitable might already again have the value A.

⚠️ Atomic operations that change the value of an awaitable do not implicitly wake up awaiters.

module Awaiter : sig ... end

Low level interface for more flexible waiting.

diff --git a/picos_std/Picos_std_awaitable/index.html b/picos_std/Picos_std_awaitable/index.html new file mode 100644 index 00000000..1b6ebff4 --- /dev/null +++ b/picos_std/Picos_std_awaitable/index.html @@ -0,0 +1,43 @@ + +Picos_std_awaitable (picos_std.Picos_std_awaitable)

Module Picos_std_awaitable

Basic futex-like awaitable atomic location for Picos.

Modules

module Awaitable : sig ... end

An awaitable atomic location.

Examples

We first open the library to bring the Awaitable module into scope:

# open Picos_std_awaitable

Mutex

Here is a basic mutex implementation using awaitables:

module Mutex = struct
+  type t = int Awaitable.t
+
+  let create ?padded () = Awaitable.make ?padded 0
+
+  let lock t =
+    if not (Awaitable.compare_and_set t 0 1) then
+      while Awaitable.exchange t 2 <> 0 do
+        Awaitable.await t 2
+      done
+
+  let unlock t =
+    let before = Awaitable.fetch_and_add t (-1) in
+    if before = 2 then begin
+      Awaitable.set t 0;
+      Awaitable.signal t
+    end
+end

The above mutex outperforms most other mutexes under both no/low and high contention scenarios. In no/low contention scenarios the use of fetch_and_add provides low overhead. In high contention scenarios the above mutex allows unfairness, which avoids performance degradation due to the lock convoy phenomena.

Condition

Let's also implement a condition variable. For that we'll also make use of low level abstractions and operations from the Picos core library:

# open Picos

To implement a condition variable, we'll use the Awaiter API:

module Condition = struct
+  type t = unit Awaitable.t
+
+  let create () = Awaitable.make ()
+
+  let wait t mutex =
+    let trigger = Trigger.create () in
+    let awaiter = Awaitable.Awaiter.add t trigger in
+    Mutex.unlock mutex;
+    let lock_forbidden mutex =
+      let fiber = Fiber.current () in
+      let forbid = Fiber.exchange fiber ~forbid:true in
+      Mutex.lock mutex;
+      Fiber.set fiber ~forbid
+    in
+    match Trigger.await trigger with
+    | None -> lock_forbidden mutex
+    | Some exn_bt ->
+        Awaitable.Awaiter.remove awaiter;
+        lock_forbidden mutex;
+        Printexc.raise_with_backtrace (fst exn_bt) (snd exn_bt)
+
+  let signal = Awaitable.signal
+  let broadcast = Awaitable.broadcast
+end

Notice that the awaitable location used in the above condition variable implementation is never mutated. We just reuse the signaling mechanism of awaitables.

diff --git a/picos_std/Picos_std_sync__Awaitable/index.html b/picos_std/Picos_std_sync__Awaitable/index.html deleted file mode 100644 index 25cab728..00000000 --- a/picos_std/Picos_std_sync__Awaitable/index.html +++ /dev/null @@ -1,2 +0,0 @@ - -Picos_std_sync__Awaitable (picos_std.Picos_std_sync__Awaitable)

Module Picos_std_sync__Awaitable

This module is hidden.

diff --git a/picos_std/_doc-dir/CHANGES.md b/picos_std/_doc-dir/CHANGES.md index 5591b7b4..eb5ce11f 100644 --- a/picos_std/_doc-dir/CHANGES.md +++ b/picos_std/_doc-dir/CHANGES.md @@ -1,7 +1,20 @@ -## Next version +## 0.6.0 +- Added a futex-like `Awaitable` abstraction as the `picos_std.awaitable` + library (@polytypic) +- Changed the core Picos library to be internally built from a single `.ml` file + (@polytypic) +- Optimized heap and stack usage of fibers and resource cleanup mechanisms and + added workarounds for compiler generated space leaks due to closures + (@polytypic) +- Added `lastly` as a safe alternative to `Fun.protect` (@polytypic) +- Workarounds for the `Uri` library not being threadsafe (@polytypic) +- Fixed to raise proper error when `Picos_io_select` has not been configured + properly (@polytypic) - Forbid cancelation propagation during `release` calls in the `picos_std.finally` library (@polytypic) + - This is a change in behaviour and could be seen as a breaking change, but it + should really be considered a bug fix. - Renamed `(Ivar|Stream).poison` to `(Ivar|Stream).poison_at` and added `(Ivar|Stream).poison` with optional `?callstack:int` (@polytypic) diff --git a/picos_std/_doc-dir/odoc-pages/index.mld b/picos_std/_doc-dir/odoc-pages/index.mld index 287c8ca5..a6016200 100644 --- a/picos_std/_doc-dir/odoc-pages/index.mld +++ b/picos_std/_doc-dir/odoc-pages/index.mld @@ -5,6 +5,7 @@ the modules are intentionally designed to mimic modules from the OCaml Stdlib. {!modules: Picos_std_finally + Picos_std_awaitable Picos_std_event Picos_std_structured Picos_std_sync diff --git a/picos_std/index.html b/picos_std/index.html index 891c8f03..7d96cb4b 100644 --- a/picos_std/index.html +++ b/picos_std/index.html @@ -1,2 +1,2 @@ -index (picos_std.index)

Package picos_std

This package contains sample scheduler agnostic libraries for Picos. Many of the modules are intentionally designed to mimic modules from the OCaml Stdlib.

These libraries are both meant to serve as examples of what can be done and to also provide practical means for programming with fibers. Hopefully there will be many more libraries implemented in Picos like these providing different approaches, patterns, and idioms for structuring concurrent programs.

Package info

changes-files
license-files
readme-files
+index (picos_std.index)

Package picos_std

This package contains sample scheduler agnostic libraries for Picos. Many of the modules are intentionally designed to mimic modules from the OCaml Stdlib.

These libraries are both meant to serve as examples of what can be done and to also provide practical means for programming with fibers. Hopefully there will be many more libraries implemented in Picos like these providing different approaches, patterns, and idioms for structuring concurrent programs.

Package info

changes-files
license-files
readme-files