Skip to content
This repository has been archived by the owner on Oct 1, 2023. It is now read-only.

IO and Filesystem namespaces

Compare
Choose a tag to compare
@fredemmott fredemmott released this 14 Nov 18:42
· 3 commits to v3.29.x since this release

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:

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.