IO and Filesystem namespaces
This release:
- adds the
HH\Lib\Experimental\{IO, Filesystem}
namespaces - supports typed user attributes (experimental feature in 3.29)
IO and Filesystem
These namespaces are focused around
async IO, using several interfaces:
For 'on-disk' files, there are extended by:
These interfaces are not used for other handles that are considered 'files' by Unix, such as pipes, and STDIN/OUT/ERR.
IO handles can currently be obtained by:
Filesystem\open_read_only
(andopen_read_only_non_disposable
)Filesystem\open_write_only
(andopen_write_only_non_disposable
)Filesystem\TemporaryFile
IO\pipe_non_disposable()
IO\stdout()
,IO\stderr()
, andIO\stdin()
Temporary Files
Temporary files are currently write-only, due to not supporting read-write file access yet. The most common pattern we see is generating
a temporary file to pass to another process; this is supported by:
await using ($tf = new Filesystem\TemporaryFile()) {
await $tf->writeAsync("Foo\n");
await $tf->flush();
call_some_subprocess($tf->getPath()->toString());
}
Passing handles
We recommend using:
function writesToHandle(<<__AcceptHandle>> IO\WriteHandle $handle) {
// do stuff
}
This will work with both disposable and non-disposable handles. The same pattern can be used for IO\ReadHandle
.
Design ideas
- multiple writers for a single stream makes sense; for protocols that allow request multiplexing (such as FastCGI, HTTP2, and LSP), having an awaitable handler per request,
it is a reasonable model to have a separate async handler for each request that writes back to the same handle - this requires queuing: if two separate handlers write messages larger than the buffer size, the two messages must not be interleaved
- reads at least must be a separate queue: using multiplexing as an example again, if a huge response is being written, this shouldn't block starting the handler for a new request
- in general, multiple readers for a single handle don't make sense: this is only truly workable if dealing with fixed-length messages
- for this reason, reads are always unqueued
Future work
This can be followed in #21. The biggest areas are:
- add read-write file opens
- add support for other forms of handles, such as sockets
- consider adding support for a disposable
pipe()
(currently not supported due to being unable to return a tuple of disposables)
Read-write file opens
We're unsure if the usual approach of allowing both reading and writing on the same file descriptor is best in an API centered around async IO: read and write have significantly different behavior, especially around ordering.
We are currently evaluting if it might instead be better for read-write file modes to always dup
the file descriptor, and
have truly independent read and write paths for the same file. We believe this would largely be transparent and avoid
complicated bugs - but, it would be at the cost of both the dup
call, and using additional file descriptors. We welcome
feedback on this point.
We would do this for files, but we are not aware of a reason to do this for sockets (or a way that doing so would be observable),
due to sockets not supporting operations such as seek
.