Description
Using Unix.select
is problematic, because if any file descriptor value is beyond 1024 then the call will fail with an exception.
Note that this refers to the value of the file descriptor, and not the number of file descriptors that select is watching: you could fail even if you only ever watch a single file descriptor, if you open that file descriptor late enough that it gets a number >1024.
This is not a concern for short-lived programs, but can be a real problem for long-lived daemons.
It'd be better to use poll(3p)
, which is part of POSIX.1-2017 and available nearly everywhere.
This is available in the iomux library.
Unfortunately Unix.select
in the stdlib cannot be efficiently reimplemented using poll
, because it has a different interface, but we shouldn't encourage more uses of select
, other than for compatibility with legacy applications (I am currently working on removing all uses of Unix.select
from such a legacy application...)
I know a project that has replaced select
with poll
in an application by writing a drop-in replacement that kept the same signature, and it was a very bad idea for performance: it allocates a lot, and it performs O(watched-fds)
work every time.
More efficient OS specific implementations are also possible:
-
epoll
,kqueue
,wepoll
(iomux
claims it'll eventually support these, but doesn't yet), available in:
https://ocaml.org/p/poll/latest -
io_submit
withIOCB_CMD_POLL
https://ocaml.org/p/aio/latest/doc/Aio/index.html#val-poll
This one also has the potential to check for the presence of events without making any system calls. https://spdk.io/ has such a pure userspaceio_getevents
implementation. (of course to wait for an event when there are none available you have to make the actual system call) -
io_uring
, as implemented by the uring package
As you can see there are plenty of choices, and the interfaces are quite distinct, and most of these are modelled more after a poll
-like interface than a select
-like interface. It is probably out of scope for this project to unify all these mechanisms, but it'd be good if the default is something better than select
.
A neutral approach might be to use poll
by default, and provide the ability for the user to override the implementation (via a functor, at main thread configure time, or via a settable global, etc.).
A picos_select.core
functor with an implementation in picos_select.iomux
might provide the most flexibility:
- you get an acceptable default that is available on a lot of platforms
- if for some reason you don't want to depend on
iomux
in your application, you can choose not to, by linking withpicos_select.core
and providing your own implementation for the poll signature. - for backwards compat
picos_select
could pickiomux
by default
(There might be other ways, I haven't looked at how Dune's virtual libraries could be used for this).
If you want I can attempt to open a draft PR with a proof of concept, but thought to open an issue describing the problem first, and maybe you already have a design on how you'd like this handled.
Activity