Legend:
Library
Module
Module type
Parameter
Class
Class type
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:
The 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.
create ?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_setting_nonblock, then Async will not set nonblock flag on the file descriptor. The fd will be used in accordance with the existing flag.
create_borrowed kind descr info ~f borrows a file descriptor that is not managed by Async, creates an Fd.t (see create), and runs f that uses that fd in async.
After f is finished (or raises an exception), it returns the file descriptor to its original owner (leaving Fd.t in a closed state, but not closing descr).
The caller must not close descr while create_borrowed is running.
clear_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).
close 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.
deregister t causes Async to stop tracking this file descriptor, and prevents further use of t. The file descriptor remains open; it can be used by other libraries.
You should only call this function if you have a file descriptor created by Async and you need to move ownership of that file descriptor to another IO library which expects to be able to close the file descriptor itself. Otherwise, just use close.
This is like calling close with file_descriptor_handling set to Do_not_close_file_descriptor.
It is OK to call deregister multiple times on the same t, like close.
val 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.
with_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).
interruptible_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.
It's an error to make multiple concurrent calls to *ready_to functions on the same file descriptor.
interruptible_every_ready_to t read_write ~interrupt f a checks every Async cycle whether the file descriptor underlying t can be read from or written to without blocking, and if so, enqueues a job to run f a. interruptible_every_ready_to is level triggered -- it will enqueue a job every cycle if I/O is available, even if the prior job hasn't run yet, or the job ran but did not consume the available data. interruptible_every_ready_to returns a deferred that will become determined when interrupt becomes determined or the file descriptor is closed.
every_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.
syscall_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.
syscall_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.
of_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.
of_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.
file_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.