An Fd.t is a wrapper around a Unix file descriptor, with additional information
about the kind of file descriptor and logic to ensure that we don't use a file
descriptor that has been closed, or close a file descriptor that is in use.
Since Async uses multiple threads to make read/write and other system calls on file descriptors, and Unix reuses descriptors after they are closed, Async has to be very careful that the file descriptor passed to a system call is referring to the file it intends, and not some other completely unrelated file that Unix has decided to assign to the same descriptor.
Provided that one only accesses a file descriptor within the context of the functions
below, Fd guarantees that the file descriptor will not have been closed/reused and
will correspond to the same file that it did when the Fd.t was created:
with_file_descr
with_file_descr_deferred
syscall
syscall_exn
syscall_result_exn
syscall_in_thread
syscall_in_thread_exnThe Fd module keeps track of which of these functions are currently accessing the
file descriptor, and ensures that any close happens after they complete. Also, once
close has been called, it refuses to provide further access to the file descriptor,
either by returning a variant `Already_closed or by raising an exception.
Some of the above functions take an optional ?nonblocking:bool argument. The
default is false, but if it is set to true, then before supplying the underlying
file_descr, the Fd module will first call Unix.set_nonblock file_descr, if it
hasn't previously done so on that file descriptor. This is intended to support making
nonblocking system calls (e.g., connect, read, write) directly within Async, without
releasing the OCaml lock or the Async lock, and without using another thread.
module Kind : sig ... endinclude sig ... endval sexp_of_t : t ‑> Base.Sexp.tval info : t ‑> Core.Info.tval create : ?avoid_nonblock_if_possible:bool ‑> Kind.t ‑> Core.Unix.File_descr.t ‑> Core.Info.t ‑> tcreate ?support_nonblock kind file_descr creates a new t of the underlying kind
and file descriptor.
We thought about using fstat() rather than requiring the user to supply the kind.
But fstat can block, which would require putting this in a thread, which has some
consequences, and it isn't clear that it gets us that much. Also, create is mostly
used within the Async implementation -- clients shouldn't need it unless they are
mixing Async and non-Async code.
If avoid_nonblock_if_possible, then Async will treat the file descriptor as blocking
if it can (more precisely, if it's not a bound socket).
val supports_nonblock : t ‑> boolsupports_nonblock t returns true if t supports nonblocking system calls.
val clear_nonblock : t ‑> unitclear_nonblock t clears the nonblocking flag on t and causes Async to treat the
fd as though it doesn't support nonblocking I/O. This is useful for applications that
want to share a file descriptor between Async and non-Async code and want to avoid
EWOULDBLOCK or EAGAIN being seen by the non-Async code, which would then cause a
Sys_blocked_io exception.
clear_nonblock t has no effect if not (supports_nonblock t).
module Close : sig ... endThe Close module exists to collect close and its associated types, so they
can be easily reused elsewhere, e.g., Unix_syscalls.
include module type of CloseThe Close module exists to collect close and its associated types, so they
can be easily reused elsewhere, e.g., Unix_syscalls.
type file_descriptor_handling = | Close_file_descriptor of socket_handling |
| Do_not_close_file_descriptor |
val close : ?file_descriptor_handling:file_descriptor_handling ‑> t ‑> unit Async_unix__.Import.Deferred.tclose t prevents further use of t, and makes shutdown() and close() system
calls on t's underlying file descriptor according to the
file_descriptor_handling argument and whether or not t is a socket, i.e., kind
t = Socket `Active:
| file_descriptor_handling | shutdown() | close() |
|----------------------------------------------+------------+---------|
| Do_not_close_file_descriptor | no | no |
| Close_file_descriptor Shutdown_socket | if socket | yes |
| Close_file_descriptor Do_not_shutdown_socket | no | yes |The result of close becomes determined once the system calls complete. It is OK
to call close multiple times on the same t; calls subsequent to the initial call
will have no effect, but will return the same deferred as the original call.
val close_started : t ‑> unit Async_unix__.Import.Deferred.tclose_started t becomes determined when close t is called.
val close_finished : t ‑> unit Async_unix__.Import.Deferred.tclose_finished returns the same result as close, but differs in that it does not
have the side effect of initiating a close.
val with_close : t ‑> f:(t ‑> 'a Async_unix__.Import.Deferred.t) ‑> 'a Async_unix__.Import.Deferred.twith_close t f applies f to t, returns the result of f, and closes t.
val stdin : unit ‑> tstdin, stdout, and stderr are wrappers around the standard Unix file
descriptors.
val stdout : unit ‑> tval stderr : unit ‑> tval with_file_descr : ?nonblocking:bool ‑> t ‑> (Core.Unix.File_descr.t ‑> 'a) ‑> [ `Ok of 'a | `Already_closed | `Error of exn ]with_file_descr t f runs f on the file descriptor underlying t, if is_open t,
and returns `Ok or `Error according to f. If is_closed t, then it does not
call f and returns `Already_closed.
val with_file_descr_exn : ?nonblocking:bool ‑> t ‑> (Core.Unix.File_descr.t ‑> 'a) ‑> 'awith_file_descr_exn is like with_file_descr except that it raises rather than
return `Already_closed or `Error.
val with_file_descr_deferred : t ‑> (Core.Unix.File_descr.t ‑> 'a Async_unix__.Import.Deferred.t) ‑> [ `Ok of 'a | `Already_closed | `Error of exn ] Async_unix__.Import.Deferred.twith_file_descr_deferred t f runs f on the file descriptor underlying t, if
is_open t, and returns `Ok or `Error according to f. If is_closed t, then
it does not call f and returns `Already_closed. It ensures that the file
descriptor underlying t is not closed until the result of f becomes determined (or
f raises).
val with_file_descr_deferred_exn : t ‑> (Core.Unix.File_descr.t ‑> 'a Async_unix__.Import.Deferred.t) ‑> 'a Async_unix__.Import.Deferred.twith_file_descr_deferred_exn is like with_file_descr_deferred, except that it
raises rather than return `Already_closed or `Error.
val interruptible_ready_to : t ‑> [ `Read | `Write ] ‑> interrupt:unit Async_unix__.Import.Deferred.t ‑> [ `Bad_fd | `Closed | `Interrupted | `Ready ] Async_unix__.Import.Deferred.tinterruptible_ready_to t read_write ~interrupt returns a deferred that will become
determined when the file descriptor underlying t can be read from or written to
without blocking, or when interrupt becomes determined.
val ready_to : t ‑> [ `Read | `Write ] ‑> [ `Bad_fd | `Closed | `Ready ] Async_unix__.Import.Deferred.tready_to t read_write is like interruptible_ready_to, but without the possibility
of interruption.
val interruptible_every_ready_to : t ‑> [ `Read | `Write ] ‑> interrupt:unit Async_unix__.Import.Deferred.t ‑> ('a ‑> unit) ‑> 'a ‑> [ `Bad_fd | `Closed | `Unsupported | `Interrupted ] Async_unix__.Import.Deferred.tinterruptible_every_ready_to t read_write ~interrupt f a enqueues a job to run f a
every time the file descriptor underlying t can be read from or written to without
blocking and returns a deferred that will become determined when interrupt becomes
determined or the file descriptor is closed.
val every_ready_to : t ‑> [ `Read | `Write ] ‑> ('a ‑> unit) ‑> 'a ‑> [ `Bad_fd | `Closed | `Unsupported ] Async_unix__.Import.Deferred.tevery_ready_to t read_write f x is like interruptible_every_ready_to, but without
the possibility of interruption.
val syscall : ?nonblocking:bool ‑> t ‑> (Core.Unix.File_descr.t ‑> 'a) ‑> [ `Already_closed | `Ok of 'a | `Error of exn ]syscall t f runs Async_unix.syscall with f on the file descriptor underlying
t, if is_open t, and returns `Ok or `Error according to f. If
is_closed t, it does not call f and returns `Already_closed.
val syscall_exn : ?nonblocking:bool ‑> t ‑> (Core.Unix.File_descr.t ‑> 'a) ‑> 'asyscall_exn t f is like syscall, except it raises rather than return
`Already_closed or `Error.
val syscall_result_exn : ?nonblocking:bool ‑> t ‑> 'a ‑> (Core.Unix.File_descr.t ‑> 'a ‑> 'b Core.Unix.Syscall_result.t) ‑> 'b Core.Unix.Syscall_result.tsyscall_result_exn t f a is like syscall_exn, except it does not allocate except
in exceptional cases. a is passed unchanged to f, and should be used to eliminate
allocations due to closure capture.
val syscall_in_thread : t ‑> name:string ‑> (Core.Unix.File_descr.t ‑> 'a) ‑> [ `Already_closed | `Ok of 'a | `Error of exn ] Async_unix__.Import.Deferred.tsyscall_in_thread t f runs In_thread.syscall with f on the file descriptor
underlying t, if is_open t, and returns a deferred that becomes determined with
`Ok or `Error when the system call completes. If is_closed t, it does not call
f and returns `Already_closed.
val syscall_in_thread_exn : t ‑> name:string ‑> (Core.Unix.File_descr.t ‑> 'a) ‑> 'a Async_unix__.Import.Deferred.tsyscall_in_thread_exn is like syscall_in_thread, except it raises rather than
return `Already_closed or `Error.
val of_in_channel : Core.In_channel.t ‑> Kind.t ‑> tof_in_channel and of_out_channel create an fd from their underlying file
descriptor.
val of_out_channel : Core.Out_channel.t ‑> Kind.t ‑> tval of_in_channel_auto : Core.In_channel.t ‑> t Async_unix__.Import.Deferred.tof_in_channel_auto ic is just like of_in_channel, but uses fstat to determine
the kind. It makes some assumptions about sockets, specifically it assumes that a
socket is either listening or connected to something (and it uses getsockopt to find
out which). Don't pass an in_channel containing an unconnected non-listening
socket.
val of_out_channel_auto : Core.Out_channel.t ‑> t Async_unix__.Import.Deferred.tof_out_channel_auto ic is just like of_out_channel, but uses fstat to determine
the kind. It makes some assumptions about sockets, specifically it assumes that a
socket is either listening or connected to something (and it uses getsockopt to find
out which). Don't pass an in_channel containing an unconnected non listening
socket.
val file_descr_exn : t ‑> Core.Unix.File_descr.tfile_descr_exn t returns the file descriptor underlying t, unless is_closed t,
in which case it raises. One must be very careful when using this function, and
should try not to, since any uses of the resulting file descriptor are unknown to
the Fd module, and hence can violate the guarantee it is trying to enforce.
val to_int_exn : t ‑> intto_int_exn t returns the the underlying file descriptor as an int. It has the same
caveats as file_descr_exn.
val replace : t ‑> Kind.t ‑> Core.Info.t ‑> unitreplace t kind is for internal use only, by Unix_syscalls. It is used when one
wants to reuse a file descriptor in an fd with a new kind.