recoilphp / recoil

Asynchronous coroutines for PHP 7.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Improved / more intuitive stream API.

jmalloc opened this issue · comments

Currently, the only stream related operations in the kernel API are read() and write(), which are essentially the same as \fread() and \fwrite(), except that they allow other strands to execute while waiting for stream activity.

While these methods are useful, they do have some limitations:

  1. Any given strand can only wait for a single stream, for a single purpose (read or write).
  2. They don't provide a way to block until all data is written, or the read buffer is filled.

To address these issues, I think it best that read() and write() are removed from the kernel API entirely, and replaced with a single select() operation.

(1) can be solved by the addition of Api::select(), which would essentially be analogous to \stream_select() but allowing other strands to execute in the meantime. Multiple strands can wait for the same streams, but only one of them is resumed when the stream becomes ready. select() would also be used for channels when they are reimplemented, allowing a strand to wait for any combination of streams or channels (perhaps strands could use a FIFO to signal the underlying event-loop when they are ready) Guh, this is not necessary.

(2) can be solved by changing the read() and write() implementations to convenience methods for reading and writing until the buffer is filled or drained, respectively. Additionally, these methods could also "lock" the individual streams so that no other strands that are using select(), read() or write() can "interject" and read/write data from/to the middle of the buffer. The existing functionality can be obtained by using select() followed by \fread() and/or \fwrite() as appropriate.

I think this will make for a very usable interface for the common case, without sacrificing any flexibility.

The implementation of this is proving a little complex in ReactApi. Because it's driven by callbacks, only a single stream is ever returned to any given strand. This might mean that if the same streams are read inside a loop only then the order in the array becomes like somewhat of a priority - if this is the case then perhaps some form of randomisation is required. Likewise, if a given strand is waiting on both readable and writable streams, it might always favour the readable ones (as their callbacks are registered with React before the writable ones). None of these things should be an issue with the "native" implementation (#85).

@ezzatron, @koden-km - I'd appreciate a proof-reading of the docblocks for read(), write() and select() as of e6b72cbc. Bare in mind this is the internals of the kernel API, so where the docs say "the calling strand is resumed with x", that approximates to "this operation returns x" when the operation is invoked from userland via Recoil::<operation>().

This came together quite nicely now. The React implementation is based on StreamQueue which provides synchronisation when several strands are using the same streams.

TODO:

  • select() - ensure timeout timer is cancelled when the calling strand is terminated (func. test)
  • read() - resume the calling strand with an appropriate exception when fread() returns false
  • read() - the default values should read the entire stream contents (equiv. to file_get_contents()).
  • write() - resume the calling strand with an appropriate exception when fwrite() returns false