···11+## v0.4 (2025-09-17)
22+33+* Clamp max open files to 2^19, as macOS sometimes returns
44+ 2^32-1 (#7 @avsm).
55+* Upgrade to dune 3.19 and move to ocaml-multicore organisation (@avsm).
66+77+## v0.3 (2023-03-10)
88+99+* Round timeouts up, not down in Poll.poll (spotted by @talex5)
1010+* Properly guard negative indexes (spotted by @talex5)
1111+* Use half the maximum fds for the leak test (@haesbaert)
1212+1313+## v0.2 (2023-02-27)
1414+1515+* Narrowed the type of `Util.fd_of_unix` (@reynir)
1616+* Use older school uerror instead of `caml_uerror` (@reynir)
1717+* Added `c_standard` to dune build flags (@reynir)
1818+* Addded ppoll(2) discoverability and a mini compat layer (@haesbaert)
1919+* Improved tests (@haesbaert)
2020+* Re-added macos support (@haesbaert)
2121+2222+## v0.1 (2023-02-17)
2323+2424+* Initial release
+14
vendor/opam/iomux/LICENSE.md
···11+Copyright (c) 2023 Christiano Haesbaert <haesbaert@haesbaert.org>
22+33+Permission to use, copy, modify, and distribute this software for any
44+purpose with or without fee is hereby granted, provided that the above
55+copyright notice and this permission notice appear in all copies.
66+77+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
88+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
99+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1010+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1111+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1212+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1313+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1414+
+80
vendor/opam/iomux/README.md
···11+# Io Multiplexers for Ocaml
22+33+[API ONLINE](https://haesbaert.github.io/ocaml-iomux)
44+55+This aims to provide very direct, as in low level bindings to poll(2),
66+ppoll(2), kevent(2) and epoll(2). At this time just poll(2) and
77+ppoll(2) are implemented.
88+99+## Poll & Ppoll
1010+1111+Poll and ppoll are the second generation of IO multiplexers, they're
1212+mostly the same, but ppoll takes nanoseconds as a timeout and allows
1313+for a list of signals to be masked atomically. Most people will be
1414+happier with poll.
1515+1616+This binding operates by maintaining an opaque buffer of fd slots. You
1717+access the internal buffer via `Poll.set_index`, `Poll.get_fd`,
1818+`Poll.get_revents` etc. These will just fill the pollfd structure at
1919+the given index. It's way easier to grasp this if you read the poll(2)
2020+manpage first.
2121+2222+### Portability
2323+2424+The poll(2) system call first appeared AT&T System V Release 3 UNIX,
2525+it is present "everywhere" and defined in "POSIX.1".
2626+2727+The ppoll(2) is a linux extension but portable enough:
2828+ * OpenBSD added it in OpenBSD 5.4
2929+ * FreeBSD added it in FreeBSD 11.0
3030+ * NetBSD added it in NetBSD 10.0
3131+ * DragonFly added it in DragonFly 4.6
3232+ * Macos **still does NOT have it as of 2023**
3333+3434+Consider using `Poll.ppoll_or_poll` to make things play nicely with
3535+macos.
3636+3737+### Usage
3838+3939+A very basic usage would be something like this. We create a pipe, and
4040+we want to poll for reading on input and writing on output.
4141+4242+```ocaml
4343+let poll = Poll.create () in
4444+let pipe_r, pipe_w = Unix.pipe () in
4545+(* We'll use index 0 for the pipe, and 7 for pipe output, just because.
4646+ First we want to make sure we can write to the pipe without blocking *)
4747+Poll.set_index poll 7 pipe_w Poll.Flags.pollout;
4848+(* Wait why 8 ? we tell the kernel the number of file descriptors to scan,
4949+ unset filedescriptors are skipped, so indexes 1-6 are ignored *)
5050+let nready = Poll.poll poll 8 Nowait in
5151+assert (nready = 1); (* only one entry should be ready, since we added only one *)
5252+let n = Unix.write pipe_w (Bytes.create 1) 0 1 in
5353+assert (n = 1);
5454+(* We'll now poll for both events, note that we don't need to re-add index 7 *)
5555+Poll.set_index poll 0 pipe_r Poll.Flags.pollin;
5656+let nready = Poll.poll poll 8 Nowait in
5757+assert (nready = 2); (* pipe input + pipe output *)
5858+Poll.iter_ready poll nready (fun index fd flags ->
5959+ if Poll.Flags.mem flags Poll.Flags.pollin then
6060+ Printf.printf "fd %d (from index %d) can be read without blocking\n%!"
6161+ (Util.fd_of_unix fd) index
6262+ else if Poll.Flags.mem flags Poll.Flags.pollout then
6363+ Printf.printf "fd %d (from index %d) can be written without blocking\n%!"
6464+ (Util.fd_of_unix fd) index
6565+ else
6666+ assert false);
6767+Unix.close pipe_r;
6868+Unix.close pipe_w;
6969+(* clean up *)
7070+Poll.invalidate_index poll 0;
7171+Poll.invalidate_index poll 7
7272+```
7373+Should produce:
7474+```
7575+fd 3 (from index 0) can be read without blocking
7676+fd 4 (from index 7) can be written without blocking
7777+```
7878+7979+This interface might look a bit weird, but it does zero allocations per `set`
8080+or `get` and only one block allocation per actual `poll` or `ppoll`.
···11+(*
22+ * Copyright (c) 2023 Christiano Haesbaert <haesbaert@haesbaert.org>
33+ *
44+ * Permission to use, copy, modify, and distribute this software for any
55+ * purpose with or without fee is hereby granted, provided that the above
66+ * copyright notice and this permission notice appear in all copies.
77+ *
88+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1111+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1212+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1313+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1515+ *)
1616+1717+open Util
1818+1919+type buffer = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t
2020+2121+module Raw = struct
2222+ external poll : buffer -> int -> int -> int = "caml_iomux_poll"
2323+ external ppoll : buffer -> int -> int64 -> int list -> int = "caml_iomux_ppoll"
2424+ external set_index : buffer -> int -> int -> int -> unit = "caml_iomux_poll_set_index" [@@noalloc]
2525+ external init : buffer -> int -> unit = "caml_iomux_poll_init"
2626+ external get_revents : buffer -> int -> int = "caml_iomux_poll_get_revents" [@@noalloc]
2727+ external get_fd : buffer -> int -> int = "caml_iomux_poll_get_fd" [@@noalloc]
2828+end
2929+3030+module Flags = struct
3131+ type t = int
3232+3333+ let pollin = Config.pollin
3434+ let pollpri = Config.pollpri
3535+ let pollout = Config.pollout
3636+ let pollerr = Config.pollerr
3737+ let pollhup = Config.pollhup
3838+ let pollnval = Config.pollnval
3939+4040+ let empty = 0
4141+4242+ let ( + ) = ( lor )
4343+4444+ let mem a b = (a land b) <> 0
4545+4646+ let to_int = Fun.id
4747+ let of_int = Fun.id
4848+end
4949+5050+let has_ppoll = Config.has_ppoll
5151+5252+let invalid_fd = unix_of_fd (-1)
5353+5454+type t = {
5555+ buffer : buffer;
5656+ maxfds : int;
5757+}
5858+5959+type poll_timeout =
6060+ | Infinite
6161+ | Nowait
6262+ | Milliseconds of int
6363+6464+let poll t used timeout =
6565+ let timeout = match timeout with
6666+ | Infinite -> (-1)
6767+ | Nowait -> 0
6868+ | Milliseconds ms -> ms
6969+ in
7070+ Raw.poll t.buffer used timeout
7171+7272+type ppoll_timeout =
7373+ | Infinite
7474+ | Nowait
7575+ | Nanoseconds of int64
7676+7777+let ppoll t used timeout sigmask =
7878+ let timeout = match timeout with
7979+ | Infinite -> Int64.minus_one
8080+ | Nowait -> Int64.zero
8181+ | Nanoseconds timo -> timo
8282+ in
8383+ Raw.ppoll t.buffer used timeout sigmask
8484+8585+let ppoll_or_poll t used (timeout : ppoll_timeout) =
8686+ if has_ppoll then
8787+ ppoll t used timeout []
8888+ else
8989+ let timeout : poll_timeout = match timeout with
9090+ | Infinite -> Infinite
9191+ | Nowait -> Nowait
9292+ | Nanoseconds timo_ns ->
9393+ Milliseconds (Int64.(to_int @@ div (add timo_ns 999_999L) 1_000_000L))
9494+ in
9595+ poll t used timeout
9696+9797+let guard_index t index =
9898+ if index >= t.maxfds || index < 0 then
9999+ invalid_arg "index out of bounds"
100100+101101+let set_index t index fd events =
102102+ guard_index t index;
103103+ Raw.set_index t.buffer index (fd_of_unix fd) events
104104+105105+let invalidate_index t index =
106106+ guard_index t index;
107107+ Raw.set_index t.buffer index (-1) 0
108108+109109+let get_revents t index =
110110+ guard_index t index;
111111+ Raw.get_revents t.buffer index
112112+113113+let get_fd t index =
114114+ guard_index t index;
115115+ Raw.get_fd t.buffer index |> unix_of_fd
116116+117117+let create ?(maxfds=Util.max_open_files ()) () =
118118+ let len = maxfds * Config.sizeof_pollfd in
119119+ let buffer = Bigarray.(Array1.create char c_layout len) in
120120+ let t = { buffer; maxfds } in
121121+ Raw.init buffer maxfds;
122122+ t
123123+124124+let maxfds t = t.maxfds
125125+126126+let iter_ready t nready (f : int -> Unix.file_descr -> Flags.t -> unit) =
127127+ let rec loop index nready =
128128+ match nready with
129129+ | 0 -> ()
130130+ | _ ->
131131+ let fd = get_fd t index in
132132+ let revents = get_revents t index in
133133+ if fd <> invalid_fd && revents <> 0 then (
134134+ f index fd revents;
135135+ loop (succ index) (pred nready)
136136+ ) else
137137+ loop (succ index) nready
138138+ in
139139+ loop 0 nready
+140
vendor/opam/iomux/lib/poll.mli
···11+(*
22+ * Copyright (c) 2023 Christiano Haesbaert <haesbaert@haesbaert.org>
33+ *
44+ * Permission to use, copy, modify, and distribute this software for any
55+ * purpose with or without fee is hereby granted, provided that the above
66+ * copyright notice and this permission notice appear in all copies.
77+ *
88+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1111+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1212+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1313+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1515+ *)
1616+1717+(** A direct binding of poll(2). *)
1818+1919+type t
2020+(** Main type for a poller. *)
2121+2222+val create : ?maxfds:int -> unit -> t
2323+(** [create ?maxfds ()] creates a new poller.
2424+ @param maxfds Maximum number of fds, defaults to {!Util.max_open_files}. *)
2525+2626+val maxfds : t -> int
2727+(** [maxfds t] is the maximum number of file descriptor slots allocated for [t]. *)
2828+2929+(** The set of flags associated with a file descriptor event. *)
3030+module Flags : sig
3131+3232+ type t
3333+ (** The actual set. *)
3434+3535+ val pollin : t
3636+ (** POLLIN from poll(2). *)
3737+3838+ val pollpri : t
3939+ (** POLLPRI from poll(2). *)
4040+4141+ val pollout : t
4242+ (** POLLOUT from poll(2). *)
4343+4444+ val pollerr : t
4545+ (** POLLERR from poll(2). Only expected as output, invalid as input. *)
4646+4747+ val pollhup : t
4848+ (** POLLHUP from poll(2). Only expected as output, invalid as input. *)
4949+5050+ val pollnval : t
5151+ (** POLLNVAL from poll(2). Only expected as output, invalid as input. *)
5252+5353+ val empty : t
5454+ (** aka zero. *)
5555+5656+ val ( + ) : t -> t -> t
5757+ (** The union of flags, fancy way of doing {!lor}. *)
5858+5959+ val mem : t -> t -> bool
6060+ (** [mem x y] checks if [y] belongs to [m]. The fancy way of doing {!land}. *)
6161+6262+ val to_int : t -> int
6363+ (** [to_int x] exposes [x] as an integer, this is an identity function. *)
6464+6565+ val of_int : int -> t
6666+ (** [of_int x] imports [x] as {!t}, this is an identity function. *)
6767+6868+end
6969+7070+val has_ppoll : bool
7171+(** [has_ppoll] is true if the system supports the ppoll(2) system
7272+ call. Notably macos as of 2023 does not have it. *)
7373+7474+val invalid_fd : Unix.file_descr
7575+(** [invalid_fd] is the {!Unix.file_descr} of value -1. *)
7676+7777+(** The timeout parameter for {!poll}. *)
7878+type poll_timeout =
7979+ | Infinite (** No timeout, wait forever *)
8080+ | Nowait (** Don't block, return immediately *)
8181+ | Milliseconds of int (** Block for at most [int] milliseconds *)
8282+8383+(** The actual poll(2) call *)
8484+val poll : t -> int -> poll_timeout -> int
8585+(** [poll t nfds timeout] polls for the first [nfds], like the system
8686+ call, invalid (-1) entries are ignored. The internal buffer is not
8787+ modified after the call. It returns the number of ready file
8888+ descriptors suitable to be used with {!iter_ready}. *)
8989+9090+(** The timeout parameter for {!ppoll}. Supports nanoseconds instead of milliseconds. *)
9191+type ppoll_timeout =
9292+ | Infinite (** No timeout, wait forever *)
9393+ | Nowait (** Don't block, return immediately *)
9494+ | Nanoseconds of int64 (** Block for at most [int64] nanoseconds *)
9595+9696+(** The actual ppoll(2) call *)
9797+val ppoll : t -> int -> ppoll_timeout -> int list -> int
9898+(** [ppoll t nfds timeout sigmask] is like {!poll} but supports
9999+ nanoseconds and a list of signals that are atomically masked
100100+ during execution and restored uppon return. If the system does not
101101+ {!has_ppoll} this call will raise {!Unix.Unix_error} with
102102+ ENOSYS. You most likely want to use {!ppoll_or_poll}, see
103103+ below. *)
104104+105105+(** A more portable ppoll(2) call *)
106106+val ppoll_or_poll : t -> int -> ppoll_timeout -> int
107107+(** [ppoll_or_poll t nfds tiemout] is like {!ppoll} if the system
108108+ {!has_ppoll}, otherwise the call is emulated via {!poll}, notably
109109+ the timeout is internally converted to milliseconds and there is
110110+ no support for signal masking. You most likely want to use this
111111+ instead of {!ppoll}, the two calls are kept to prevent the user
112112+ from expecting nanoseconds resolution from an emulated {!ppoll}
113113+ call. *)
114114+115115+val set_index : t -> int -> Unix.file_descr -> Flags.t -> unit
116116+(** [set_index t index fd flag] modifies the internal buffer at
117117+ [index] to listen to [flag] events of [fd]. This overwrites any
118118+ previous value of [flag] and [fd] internally. {!invalid_fd} (-1)
119119+ can be used to deactivate the slot, but usage of
120120+ {!invalidate_index} is preferred. *)
121121+122122+val invalidate_index : t -> int -> unit
123123+(** [invalidate_index t index] modifies the internal buffer by
124124+ invalidating [index]. The kernel will ignore that slot. We also
125125+ clear flags, just for kicks. *)
126126+127127+val get_revents : t -> int -> Flags.t
128128+(** [get_revents t index] is the returned event flags set after a call
129129+ to {!poll} or {!ppoll}. *)
130130+131131+val get_fd : t -> int -> Unix.file_descr
132132+(** [get_fd t index] is the file descriptor associated with [index]. *)
133133+134134+val iter_ready : t -> int -> (int -> Unix.file_descr -> Flags.t -> unit) -> unit
135135+(** [iter_ready t nready fn] scans the internal buffer for every ready
136136+ file descriptor and calls [fn index fd flags], the scanning is
137137+ aborted after [nready] entries are found. Invalid file descriptors
138138+ (set to -1 via invalidate_index) are skipped. Typical usage is that
139139+ [nready] is the return of {!poll} or {!ppoll}. The internal buffer
140140+ is left unmodified. *)
···11+open Iomux
22+33+exception Fdleak
44+55+let _check_raises = Alcotest.check_raises
66+let _check_string = Alcotest.(check string)
77+let check_int = Alcotest.(check int)
88+let check_bool = Alcotest.(check bool)
99+1010+module U = struct
1111+ let with_leak_checker (f : unit -> unit) () =
1212+ let fetch () =
1313+ let l = List.init (Util.max_open_files () / 2) (fun _ -> Unix.(socket PF_UNIX SOCK_STREAM 0)) in
1414+ List.iter Unix.close l;
1515+ l
1616+ in
1717+ let l1 = fetch () in
1818+ match f () with
1919+ | exception exn -> raise exn
2020+ | () ->
2121+ (* Linux is buggy. In multithreaded programs not always the
2222+ file-descriptor is released immediatelly if it has/have been
2323+ used in another thread. This causes the list to be
2424+ re-ordered, with sometimes one file descriptor showing up
2525+ only later (but it shows up so it's not a leak). So we just
2626+ fetch again. *)
2727+ if (l1 <> fetch ()) && (l1 <> fetch ()) then
2828+ raise Fdleak
2929+3030+ let _coinflip () = Random.bool ()
3131+3232+ let one_second_in_ns = 1000_000_000L
3333+ let hundred_ms_in_ns = 100_000_000L
3434+3535+end
3636+3737+module T = struct
3838+3939+ let init () =
4040+ let maxfds = 1024 in
4141+ let poll = Poll.create ~maxfds () in
4242+ for i = 0 to maxfds - 1 do
4343+ let v = (Poll.get_fd poll i) = Poll.invalid_fd in
4444+ check_bool "initialized" v true
4545+ done
4646+4747+ let basic () =
4848+ let poll = Poll.create ~maxfds:16 () in
4949+ let r, w = Unix.pipe () in
5050+ Poll.set_index poll 0 r Poll.Flags.pollin;
5151+ let b = Bytes.create 1 in
5252+ check_int "write" (Unix.write w b 0 1) 1;
5353+ let nready = Poll.poll poll 1 Nowait in
5454+ check_int "nready" nready 1;
5555+ let fd = Poll.get_fd poll 0 in
5656+ let revents = Poll.get_revents poll 0 in
5757+ check_bool "fd" true (r = fd);
5858+ check_bool "revents" true (Poll.Flags.mem revents Poll.Flags.pollin);
5959+ check_bool "revents-eq" true (revents = Poll.Flags.pollin);
6060+ Unix.close w;
6161+ Unix.close r
6262+6363+ let ppoll_timo () =
6464+ let pollfds = Poll.create () in
6565+ try
6666+ ignore @@ Poll.ppoll pollfds 0 (Nanoseconds U.one_second_in_ns) [];
6767+ check_bool "has_ppoll true" Poll.has_ppoll true
6868+ with
6969+ Unix.Unix_error (Unix.ENOSYS,_,_) ->
7070+ check_bool "has_ppoll false" Poll.has_ppoll false
7171+7272+ let ppoll_or_poll () =
7373+ let poll = Poll.create () in
7474+ let n = Poll.ppoll_or_poll poll 0 Nowait in
7575+ check_int "n is zero" n 0;
7676+ let n = Poll.ppoll_or_poll poll 0 (Nanoseconds U.hundred_ms_in_ns) in
7777+ check_int "n is zero" n 0
7878+7979+ let example () =
8080+ let poll = Poll.create () in
8181+ let pipe_r, pipe_w = Unix.pipe () in
8282+ (* We'll use index 0 for the pipe, and 7 for pipe output, just because.
8383+ First we want to make sure we can write to the pipe without blocking *)
8484+ Poll.set_index poll 7 pipe_w Poll.Flags.pollout;
8585+ (* Wait why 8 ? we tell the kernel the number of file descriptors to scan,
8686+ unset filedescriptors are skipped, so indexes 1-6 are ignored *)
8787+ let nready = Poll.poll poll 8 Nowait in
8888+ check_int "nread 1" 1 nready; (* only one entry should be ready, since we added only one *)
8989+ let n = Unix.write pipe_w (Bytes.create 1) 0 1 in
9090+ check_int "n" 1 n;
9191+ (* We'll now poll for both events, note that we don't need to re-add index 7 *)
9292+ Poll.set_index poll 0 pipe_r Poll.Flags.pollin;
9393+ let nready = Poll.poll poll 8 Nowait in
9494+ check_int "nready" 2 nready;
9595+ Poll.iter_ready poll nready (fun index fd flags ->
9696+ if Poll.Flags.mem flags Poll.Flags.pollin then
9797+ Printf.printf "fd %d (from index %d) can be read without blocking\n%!"
9898+ (Util.fd_of_unix fd) index
9999+ else if Poll.Flags.mem flags Poll.Flags.pollout then
100100+ Printf.printf "fd %d (from index %d) can be written without blocking\n%!"
101101+ (Util.fd_of_unix fd) index
102102+ else
103103+ assert false);
104104+ Unix.close pipe_r;
105105+ Unix.close pipe_w;
106106+ (* clean up *)
107107+ Poll.invalidate_index poll 0;
108108+ Poll.invalidate_index poll 7
109109+110110+ let () =
111111+ let open Alcotest in
112112+ let wlc = U.with_leak_checker in
113113+ run "Iomux" [
114114+ "init", [ test_case "" `Quick (wlc init) ];
115115+ "unit", [ test_case "" `Quick (wlc basic) ];
116116+ "ppoll_timo", [ test_case "" `Quick (wlc ppoll_timo) ];
117117+ "ppoll_or_poll", [ test_case "" `Quick (wlc ppoll_or_poll) ];
118118+ "example", [ test_case "" `Quick (wlc example) ];
119119+ ]
120120+121121+end