From 2183c08829c5d4b14e8c34374cc49c84ab5cf5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 27 Feb 2017 23:52:13 +0100 Subject: [PATCH 1/4] Add events to TOC --- README.md | 76 ++++++++++++++++++++++----------- src/ReadableStreamInterface.php | 29 +++++++++++-- src/WritableStreamInterface.php | 25 +++++++++-- 3 files changed, 97 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 133b653..64fa8c0 100644 --- a/README.md +++ b/README.md @@ -7,25 +7,27 @@ Basic readable and writable stream interfaces that support piping. In order to make the event loop easier to use, this component introduces the concept of streams. They are very similar to the streams found in PHP itself, but have an interface more suited for async I/O. - Mainly it provides interfaces for readable and writable streams, plus a file descriptor based implementation with an in-memory write buffer. -This component depends on `événement`, which is an implementation of the -`EventEmitter`. - **Table of contents** * [API](#api) * [ReadableStreamInterface](#readablestreaminterface) - * [EventEmitter Events](#eventemitter-events) + * [data event](#data-event) + * [end event](#end-event) + * [error event](#error-event) + * [close event](#close-event) * [isReadable()](#isreadable) * [pause()](#pause) * [resume()](#resume) * [pipe()](#pipe) * [close()](#close) * [WritableStreamInterface](#writablestreaminterface) - * [EventEmitter Events](#eventemitter-events-1) + * [drain event](#drain-event) + * [pipe event](#pipe-event) + * [error event](#error-event-1) + * [close event](#close-event-1) * [isWritable()](#iswritable) * [write()](#write) * [end()](#end) @@ -37,18 +39,30 @@ This component depends on `événement`, which is an implementation of the ### ReadableStreamInterface -#### EventEmitter Events +Besides defining a few methods, this interface also implements the +`EventEmitterInterface` which allows you to react to certain events. + +#### data event + +Emitted whenever data was read from the source +with a single mixed argument for incoming data. + +#### end event + +Emitted when the source has successfully reached the end +of the stream (EOF). +This event will only be emitted if the *end* was reached successfully, not +if the stream was interrupted due to an error or explicitly closed. +Also note that not all streams know the concept of a "successful end". + +#### error event + +Emitted when an error occurs +with a single `Exception` argument for error instance. + +#### close event -* `data`: Emitted whenever data was read from the source - with a single mixed argument for incoming data. -* `end`: Emitted when the source has successfully reached the end - of the stream (EOF). - This event will only be emitted if the *end* was reached successfully, not - if the stream was interrupted due to an error or explicitly closed. - Also note that not all streams know the concept of a "successful end". -* `error`: Emitted when an error occurs - with a single `Exception` argument for error instance. -* `close`: Emitted when the stream is closed. +Emitted when the stream is closed. #### isReadable() @@ -230,15 +244,27 @@ Note that this method should not be confused with the `end()` method. ### WritableStreamInterface -#### EventEmitter Events +Besides defining a few methods, this interface also implements the +`EventEmitterInterface` which allows you to react to certain events. + +#### drain event + +Emitted if the write buffer became full previously and is now ready +to accept more data. + +#### pipe event + +Emitted whenever a readable stream is `pipe()`d into this stream +with a single `ReadableStreamInterface` argument for source stream. + +#### error event + +Emitted whenever an error occurs +with a single `Exception` argument for error instance. + +#### close event -* `drain`: Emitted if the write buffer became full previously and is now ready - to accept more data. -* `error`: Emitted whenever an error occurs - with a single `Exception` argument for error instance. -* `close`: Emitted whenever the stream is closed. -* `pipe`: Emitted whenever a readable stream is `pipe()`d into this stream - with a single `ReadableStreamInterface` argument for source stream. +Emitted whenever the stream is closed. #### isWritable() diff --git a/src/ReadableStreamInterface.php b/src/ReadableStreamInterface.php index 0009d1a..f7df986 100644 --- a/src/ReadableStreamInterface.php +++ b/src/ReadableStreamInterface.php @@ -5,10 +5,31 @@ use Evenement\EventEmitterInterface; /** - * @event data with a single mixed argument for incoming data - * @event end - * @event error with a single Exception argument for error instance - * @event close + * + * Besides defining a few methods, this interface also implements the + * `EventEmitterInterface` which allows you to react to certain events: + * + * data event: + * The `data` event will be emitted whenever some data was read/received + * from this source stream. + * The event receives a single mixed argument for incoming data. + * + * end event: + * The `end` event will be emitted once the source stream has successfully + * reached the end of the stream (EOF). + * This event will only be emitted if the *end* was reached successfully, not + * if the stream was interrupted due to an error or explicitly closed. + * Also note that not all streams know the concept of a "successful end". + * + * error event: + * The `error` event will be emitted whenever an error occurs, usually while + * trying to read from this stream. + * The event receives a single `Exception` argument for the error instance. + * + * close event: + * The `close` event will be emitted once the stream closes (terminates). + * + * @see EventEmitterInterface */ interface ReadableStreamInterface extends EventEmitterInterface { diff --git a/src/WritableStreamInterface.php b/src/WritableStreamInterface.php index 0f33686..d978b5a 100644 --- a/src/WritableStreamInterface.php +++ b/src/WritableStreamInterface.php @@ -5,10 +5,27 @@ use Evenement\EventEmitterInterface; /** - * @event drain - * @event error with a single Exeption argument for error instance - * @event close - * @event pipe with a single ReadableStreamInterface argument for source stream + * + * Besides defining a few methods, this interface also implements the + * `EventEmitterInterface` which allows you to react to certain events: + * + * drain event: + * Emitted if the write buffer became full previously and is now ready + * to accept more data. + * + * pipe event: + * Emitted whenever a readable stream is `pipe()`d into this stream + * with a single `ReadableStreamInterface` argument for source stream. + * + * error event: + * Emitted whenever an error occurs + * with a single `Exception` argument for error instance. + * + * close event: + * Emitted whenever the stream is closed. + * + * @see EventEmitterInterface + * @see DuplexStreamInterface */ interface WritableStreamInterface extends EventEmitterInterface { From 05ecb323dcbdc986a260a514fd099918b41ec3a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 7 Mar 2017 10:15:05 +0100 Subject: [PATCH 2/4] Consistent event semantics for readable streams --- README.md | 142 +++++++++++++++++++++++++++++--- src/ReadableStreamInterface.php | 125 +++++++++++++++++++++++++++- 2 files changed, 254 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 64fa8c0..e0705ff 100644 --- a/README.md +++ b/README.md @@ -39,30 +39,148 @@ descriptor based implementation with an in-memory write buffer. ### ReadableStreamInterface +The `ReadableStreamInterface` is responsible for providing an interface for +read-only streams and the readable side of duplex streams. + Besides defining a few methods, this interface also implements the `EventEmitterInterface` which allows you to react to certain events. #### data event -Emitted whenever data was read from the source -with a single mixed argument for incoming data. +The `data` event will be emitted whenever some data was read/received +from this source stream. +The event receives a single mixed argument for incoming data. + +```php +$stream->on('data', function ($data) { + echo $data; +}); +``` + +This event MAY be emitted any number of times, which may be zero times if +this stream does not send any data at all. +It SHOULD not be emitted after an `end` or `close` event. + +The given `$data` argument may be of mixed type, but it's usually +recommended it SHOULD be a `string` value or MAY use a type that allows +representation as a `string` for maximum compatibility. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will emit the raw (binary) payload data that is received over the wire as +chunks of `string` values. + +Due to the stream-based nature of this, the sender may send any number +of chunks with varying sizes. There are no guarantees that these chunks +will be received with the exact same framing the sender intended to send. +In other words, many lower-level protocols (such as TCP/IP) transfer the +data in chunks that may be anywhere between single-byte values to several +dozens of kilobytes. You may want to apply a higher-level protocol to +these low-level data chunks in order to achieve proper message framing. #### end event -Emitted when the source has successfully reached the end -of the stream (EOF). -This event will only be emitted if the *end* was reached successfully, not -if the stream was interrupted due to an error or explicitly closed. -Also note that not all streams know the concept of a "successful end". +The `end` event will be emitted once the source stream has successfully +reached the end of the stream (EOF). + +```php +$stream->on('end', function () { + echo 'END'; +}); +``` + +This event SHOULD be emitted once or never at all, depending on whether +a successful end was detected. +It SHOULD NOT be emitted after a previous `end` or `close` event. +It MUST NOT be emitted if the stream closes due to a non-successful +end, such as after a previous `error` event. + +After the stream is ended, it MUST switch to non-readable mode, +see also `isReadable()`. + +This event will only be emitted if the *end* was reached successfully, +not if the stream was interrupted by an unrecoverable error or explicitly +closed. Not all streams know this concept of a "successful end". +Many use-cases involve detecting when the stream closes (terminates) +instead, in this case you should use the `close` event. +After the stream emits an `end` event, it SHOULD usually be followed by a +`close` event. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will emit this event if either the remote side closes the connection or +a file handle was successfully read until reaching its end (EOF). + +Note that this event should not be confused with the `end()` method. +This event defines a successful end *reading* from a source stream, while +the `end()` method defines *writing* a successful end to a destination +stream. #### error event -Emitted when an error occurs -with a single `Exception` argument for error instance. +The `error` event will be emitted whenever an error occurs, usually while +trying to read from this stream. +The event receives a single `Exception` argument for the error instance. + +```php +$server->on('error', function (Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; +}); +``` + +This event MAY be emitted any number of times, which should be zero +times if this is a stream that is successfully terminated. +It SHOULD be emitted whenever the stream detects an error, such as a +transmission error or after an unexpected `data` or premature `end` event. +It SHOULD NOT be emitted after a `close` event. + +Many common streams (such as a TCP/IP connection or a file-based stream) +only deal with data transmission and do not make assumption about data +boundaries (such as unexpected `data` or premature `end` events). +In other words, many lower-level protocols (such as TCP/IP) may choose +to only emit this for a fatal transmission error once and will thus +likely close (terminate) the stream in response. +If this is a fatal error that results in the stream being closed, it +SHOULD be followed by a `close` event. + +Other higher-level protocols may choose to keep the stream alive after +this event, if they can recover from an error condition. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the writable side of the stream also implements an `error` event. +In other words, an error may occur while either reading or writing the +stream which should result in the same error processing. #### close event -Emitted when the stream is closed. +The `close` event will be emitted once the stream closes (terminates). + +```php +$stream->on('close', function () { + echo 'CLOSED'; +}); +``` + +This event SHOULD be emitted once or never at all, depending on whether +the stream ever terminates. +It SHOULD NOT be emitted after a previous `close` event. + +After the stream is closed, it MUST switch to non-readable mode, +see also `isReadable()`. + +Unlike the `end` event, this event SHOULD be emitted whenever the stream +closes, irrespective of whether this happens implicitly due to an +unrecoverable error or explicitly when either side closes the stream. +If you only want to detect a *succesful* end, you should use the `end` +event instead. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will likely choose to emit this event after reading a *successful* `end` +event or after a fatal transmission `error` event. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the writable side of the stream also implements a `close` event. +In other words, after receiving this event, the stream MUST switch into +non-writable AND non-readable mode, see also `isWritable()`. +Note that this event should not be confused with the `end` event. #### isReadable() @@ -224,6 +342,10 @@ This method can be used to (forcefully) close the stream. $stream->close(); ``` +Once the stream is closed, it SHOULD emit a `close` event. +Note that this event SHOULD NOT be emitted more than once, in particular +if this method is called multiple times. + After calling this method, the stream MUST switch into a non-readable mode, see also `isReadable()`. This means that no further `data` or `end` events SHOULD be emitted. diff --git a/src/ReadableStreamInterface.php b/src/ReadableStreamInterface.php index f7df986..15a7ced 100644 --- a/src/ReadableStreamInterface.php +++ b/src/ReadableStreamInterface.php @@ -5,6 +5,8 @@ use Evenement\EventEmitterInterface; /** + * The `ReadableStreamInterface` is responsible for providing an interface for + * read-only streams and the readable side of duplex streams. * * Besides defining a few methods, this interface also implements the * `EventEmitterInterface` which allows you to react to certain events: @@ -14,21 +16,134 @@ * from this source stream. * The event receives a single mixed argument for incoming data. * + * ```php + * $stream->on('data', function ($data) { + * echo $data; + * }); + * ``` + * + * This event MAY be emitted any number of times, which may be zero times if + * this stream does not send any data at all. + * It SHOULD not be emitted after an `end` or `close` event. + * + * The given `$data` argument may be of mixed type, but it's usually + * recommended it SHOULD be a `string` value or MAY use a type that allows + * representation as a `string` for maximum compatibility. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will emit the raw (binary) payload data that is received over the wire as + * chunks of `string` values. + * + * Due to the stream-based nature of this, the sender may send any number + * of chunks with varying sizes. There are no guarantees that these chunks + * will be received with the exact same framing the sender intended to send. + * In other words, many lower-level protocols (such as TCP/IP) transfer the + * data in chunks that may be anywhere between single-byte values to several + * dozens of kilobytes. You may want to apply a higher-level protocol to + * these low-level data chunks in order to achieve proper message framing. + * * end event: * The `end` event will be emitted once the source stream has successfully * reached the end of the stream (EOF). - * This event will only be emitted if the *end* was reached successfully, not - * if the stream was interrupted due to an error or explicitly closed. - * Also note that not all streams know the concept of a "successful end". + * + * ```php + * $stream->on('end', function () { + * echo 'END'; + * }); + * ``` + * + * This event SHOULD be emitted once or never at all, depending on whether + * a successful end was detected. + * It SHOULD NOT be emitted after a previous `end` or `close` event. + * It MUST NOT be emitted if the stream closes due to a non-successful + * end, such as after a previous `error` event. + * + * After the stream is ended, it MUST switch to non-readable mode, + * see also `isReadable()`. + * + * This event will only be emitted if the *end* was reached successfully, + * not if the stream was interrupted by an unrecoverable error or explicitly + * closed. Not all streams know this concept of a "successful end". + * Many use-cases involve detecting when the stream closes (terminates) + * instead, in this case you should use the `close` event. + * After the stream emits an `end` event, it SHOULD usually be followed by a + * `close` event. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will emit this event if either the remote side closes the connection or + * a file handle was successfully read until reaching its end (EOF). + * + * Note that this event should not be confused with the `end()` method. + * This event defines a successful end *reading* from a source stream, while + * the `end()` method defines *writing* a successful end to a destination + * stream. * * error event: * The `error` event will be emitted whenever an error occurs, usually while * trying to read from this stream. * The event receives a single `Exception` argument for the error instance. * + * ```php + * $stream->on('error', function (Exception $e) { + * echo 'Error: ' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * This event MAY be emitted any number of times, which should be zero + * times if this is a stream that is successfully terminated. + * It SHOULD be emitted whenever the stream detects an error, such as a + * transmission error or after an unexpected `data` or premature `end` event. + * It SHOULD NOT be emitted after a `close` event. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * only deal with data transmission and do not make assumption about data + * boundaries (such as unexpected `data` or premature `end` events). + * In other words, many lower-level protocols (such as TCP/IP) may choose + * to only emit this for a fatal transmission error once and will thus + * likely close (terminate) the stream in response. + * If this is a fatal error that results in the stream being closed, it + * SHOULD be followed by a `close` event. + * + * Other higher-level protocols may choose to keep the stream alive after + * this event, if they can recover from an error condition. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the writable side of the stream also implements an `error` event. + * In other words, an error may occur while either reading or writing the + * stream which should result in the same error processing. + * * close event: * The `close` event will be emitted once the stream closes (terminates). * + * ```php + * $stream->on('close', function () { + * echo 'CLOSED'; + * }); + * ``` + * + * This event SHOULD be emitted once or never at all, depending on whether + * the stream ever terminates. + * It SHOULD NOT be emitted after a previous `close` event. + * + * After the stream is closed, it MUST switch to non-readable mode, + * see also `isReadable()`. + * + * Unlike the `end` event, this event SHOULD be emitted whenever the stream + * closes, irrespective of whether this happens implicitly due to an + * unrecoverable error or explicitly when either side closes the stream. + * If you only want to detect a *succesful* end, you should use the `end` + * event instead. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will likely choose to emit this event after reading a *successful* `end` + * event or after a fatal transmission `error` event. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the writable side of the stream also implements a `close` event. + * In other words, after receiving this event, the stream MUST switch into + * non-writable AND non-readable mode, see also `isWritable()`. + * Note that this event should not be confused with the `end` event. + * * @see EventEmitterInterface */ interface ReadableStreamInterface extends EventEmitterInterface @@ -199,6 +314,10 @@ public function pipe(WritableStreamInterface $dest, array $options = array()); * $stream->close(); * ``` * + * Once the stream is closed, it SHOULD emit a `close` event. + * Note that this event SHOULD NOT be emitted more than once, in particular + * if this method is called multiple times. + * * After calling this method, the stream MUST switch into a non-readable * mode, see also `isReadable()`. * This means that no further `data` or `end` events SHOULD be emitted. From 2c323a819c113deb82b1648cd1e217d742746f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 7 Mar 2017 10:44:54 +0100 Subject: [PATCH 3/4] Consistent event semantics for writable streams --- README.md | 124 +++++++++++++++++++++++++++++--- src/ReadableStreamInterface.php | 3 + src/WritableStreamInterface.php | 120 ++++++++++++++++++++++++++++--- 3 files changed, 231 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index e0705ff..e38497d 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,9 @@ $source->pipe($dest); $dest->close(); // calls $source->pause() ``` +Once the pipe is set up successfully, the destination stream MUST emit +a `pipe` event with this source stream an event argument. + #### close() The `close(): void` method can be used to @@ -366,27 +369,125 @@ Note that this method should not be confused with the `end()` method. ### WritableStreamInterface +The `WritableStreamInterface` is responsible for providing an interface for +write-only streams and the writable side of duplex streams. + Besides defining a few methods, this interface also implements the `EventEmitterInterface` which allows you to react to certain events. #### drain event -Emitted if the write buffer became full previously and is now ready -to accept more data. +The `drain` event will be emitted whenever the write buffer became full +previously and is now ready to accept more data. + +```php +$stream->on('drain', function () use ($stream) { + echo 'Stream is now ready to accept more data'; +}); +``` + +This event SHOULD be emitted once every time the buffer became full +previously and is now ready to accept more data. +In other words, this event MAY be emitted any number of times, which may +be zero times if the buffer never became full in the first place. +This event SHOULD NOT be emitted if the buffer has not become full +previously. + +This event is mostly used internally, see also `write()` for more details. #### pipe event -Emitted whenever a readable stream is `pipe()`d into this stream -with a single `ReadableStreamInterface` argument for source stream. +The `pipe` event will be emitted whenever a readable stream is `pipe()`d +into this stream. +The event receives a single `ReadableStreamInterface` argument for the +source stream. + +```php +$stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) { + echo 'Now receiving piped data'; + + // explicitly close target if source emits an error + $source->on('error', function () use ($stream) { + $stream->close(); + }); +}); + +$source->pipe($stream); +``` + +This event MUST be emitted once for each readable stream that is +successfully piped into this destination stream. +In other words, this event MAY be emitted any number of times, which may +be zero times if no stream is ever piped into this stream. +This event MUST NOT be emitted if either the source is not readable +(closed already) or this destination is not writable (closed already). + +This event is mostly used internally, see also `pipe()` for more details. #### error event -Emitted whenever an error occurs -with a single `Exception` argument for error instance. +The `error` event will be emitted whenever an error occurs, usually while +trying to write to this stream. +The event receives a single `Exception` argument for the error instance. + +```php +$stream->on('error', function (Exception $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; +}); +``` + +This event MAY be emitted any number of times, which should be zero +times if this is a stream that is successfully terminated. +It SHOULD be emitted whenever the stream detects an error, such as a +transmission error. +It SHOULD NOT be emitted after a `close` event. + +Many common streams (such as a TCP/IP connection or a file-based stream) +only deal with data transmission and may choose +to only emit this for a fatal transmission error once and will thus +likely close (terminate) the stream in response. +If this is a fatal error that results in the stream being closed, it +SHOULD be followed by a `close` event. + +Other higher-level protocols may choose to keep the stream alive after +this event, if they can recover from an error condition. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the readable side of the stream also implements an `error` event. +In other words, an error may occur while either reading or writing the +stream which should result in the same error processing. #### close event -Emitted whenever the stream is closed. +The `close` event will be emitted once the stream closes (terminates). + +```php +$stream->on('close', function () { + echo 'CLOSED'; +}); +``` + +This event SHOULD be emitted once or never at all, depending on whether +the stream ever terminates. +It SHOULD NOT be emitted after a previous `close` event. + +After the stream is closed, it MUST switch to non-writable mode, +see also `isWritable()`. + +This event SHOULD be emitted whenever the stream closes, irrespective of +whether this happens implicitly due to an unrecoverable error or +explicitly when either side closes the stream. + +Many common streams (such as a TCP/IP connection or a file-based stream) +will likely choose to emit this event after flushing the buffer from +the `end()` method, after receiving a *successful* `end` event or after +a fatal transmission `error` event. + +If this stream is a `DuplexStreamInterface`, you should also notice +how the readable side of the stream also implements a `close` event. +In other words, after receiving this event, the stream MUST switch into +non-writable AND non-readable mode, see also `isReadable()`. +Note that this event should not be confused with the `end` event. #### isWritable() @@ -439,7 +540,9 @@ this error. If the internal buffer is full after adding `$data`, then `write()` SHOULD return `false`, indicating that the caller should stop sending -data until the buffer `drain`s. +data until the buffer drains. +The stream SHOULD send a `drain` event once the buffer is ready to accept +more data. Similarly, if the the stream is not writable (already in a closed state) it MUST NOT process the given `$data` and SHOULD return `false`, @@ -481,6 +584,7 @@ this method MAY `close()` the stream immediately. If there's still data in the buffer that needs to be flushed first, then this method SHOULD try to write out this data and only then `close()` the stream. +Once the stream is closed, it SHOULD emit a `close` event. Note that this interface gives you no control over explicitly flushing the buffered data, as finding the appropriate time for this is beyond the @@ -533,6 +637,10 @@ If there's still data in the buffer, this data SHOULD be discarded. $stream->close(); ``` +Once the stream is closed, it SHOULD emit a `close` event. +Note that this event SHOULD NOT be emitted more than once, in particular +if this method is called multiple times. + After calling this method, the stream MUST switch into a non-writable mode, see also `isWritable()`. This means that no further writes are possible, so any additional diff --git a/src/ReadableStreamInterface.php b/src/ReadableStreamInterface.php index 15a7ced..bba1834 100644 --- a/src/ReadableStreamInterface.php +++ b/src/ReadableStreamInterface.php @@ -299,6 +299,9 @@ public function resume(); * $dest->close(); // calls $source->pause() * ``` * + * Once the pipe is set up successfully, the destination stream MUST emit + * a `pipe` event with this source stream an event argument. + * * @param WritableStreamInterface $dest * @param array $options * @return WritableStreamInterface $dest stream as-is diff --git a/src/WritableStreamInterface.php b/src/WritableStreamInterface.php index d978b5a..a22db78 100644 --- a/src/WritableStreamInterface.php +++ b/src/WritableStreamInterface.php @@ -5,24 +5,121 @@ use Evenement\EventEmitterInterface; /** + * The `WritableStreamInterface` is responsible for providing an interface for + * write-only streams and the writable side of duplex streams. * * Besides defining a few methods, this interface also implements the * `EventEmitterInterface` which allows you to react to certain events: * * drain event: - * Emitted if the write buffer became full previously and is now ready - * to accept more data. + * The `drain` event will be emitted whenever the write buffer became full + * previously and is now ready to accept more data. + * + * ```php + * $stream->on('drain', function () use ($stream) { + * echo 'Stream is now ready to accept more data'; + * }); + * ``` + * + * This event SHOULD be emitted once every time the buffer became full + * previously and is now ready to accept more data. + * In other words, this event MAY be emitted any number of times, which may + * be zero times if the buffer never became full in the first place. + * This event SHOULD NOT be emitted if the buffer has not become full + * previously. + * + * This event is mostly used internally, see also `write()` for more details. * * pipe event: - * Emitted whenever a readable stream is `pipe()`d into this stream - * with a single `ReadableStreamInterface` argument for source stream. + * The `pipe` event will be emitted whenever a readable stream is `pipe()`d + * into this stream. + * The event receives a single `ReadableStreamInterface` argument for the + * source stream. + * + * ```php + * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) { + * echo 'Now receiving piped data'; + * + * // explicitly close target if source emits an error + * $source->on('error', function () use ($stream) { + * $stream->close(); + * }); + * }); + * + * $source->pipe($stream); + * ``` + * + * This event MUST be emitted once for each readable stream that is + * successfully piped into this destination stream. + * In other words, this event MAY be emitted any number of times, which may + * be zero times if no stream is ever piped into this stream. + * This event MUST NOT be emitted if either the source is not readable + * (closed already) or this destination is not writable (closed already). + * + * This event is mostly used internally, see also `pipe()` for more details. * * error event: - * Emitted whenever an error occurs - * with a single `Exception` argument for error instance. + * The `error` event will be emitted whenever an error occurs, usually while + * trying to write to this stream. + * The event receives a single `Exception` argument for the error instance. + * + * ```php + * $stream->on('error', function (Exception $e) { + * echo 'Error: ' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * This event MAY be emitted any number of times, which should be zero + * times if this is a stream that is successfully terminated. + * It SHOULD be emitted whenever the stream detects an error, such as a + * transmission error. + * It SHOULD NOT be emitted after a `close` event. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * only deal with data transmission and may choose + * to only emit this for a fatal transmission error once and will thus + * likely close (terminate) the stream in response. + * If this is a fatal error that results in the stream being closed, it + * SHOULD be followed by a `close` event. + * + * Other higher-level protocols may choose to keep the stream alive after + * this event, if they can recover from an error condition. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the readable side of the stream also implements an `error` event. + * In other words, an error may occur while either reading or writing the + * stream which should result in the same error processing. * * close event: - * Emitted whenever the stream is closed. + * The `close` event will be emitted once the stream closes (terminates). + * + * ```php + * $stream->on('close', function () { + * echo 'CLOSED'; + * }); + * ``` + * + * This event SHOULD be emitted once or never at all, depending on whether + * the stream ever terminates. + * It SHOULD NOT be emitted after a previous `close` event. + * + * After the stream is closed, it MUST switch to non-writable mode, + * see also `isWritable()`. + * + * This event SHOULD be emitted whenever the stream closes, irrespective of + * whether this happens implicitly due to an unrecoverable error or + * explicitly when either side closes the stream. + * + * Many common streams (such as a TCP/IP connection or a file-based stream) + * will likely choose to emit this event after flushing the buffer from + * the `end()` method, after receiving a *successful* `end` event or after + * a fatal transmission `error` event. + * + * If this stream is a `DuplexStreamInterface`, you should also notice + * how the readable side of the stream also implements a `close` event. + * In other words, after receiving this event, the stream MUST switch into + * non-writable AND non-readable mode, see also `isReadable()`. + * Note that this event should not be confused with the `end` event. * * @see EventEmitterInterface * @see DuplexStreamInterface @@ -80,7 +177,9 @@ public function isWritable(); * * If the internal buffer is full after adding `$data`, then `write()` * SHOULD return `false`, indicating that the caller should stop sending - * data until the buffer `drain`s. + * data until the buffer drains. + * The stream SHOULD send a `drain` event once the buffer is ready to accept + * more data. * * Similarly, if the the stream is not writable (already in a closed state) * it MUST NOT process the given `$data` and SHOULD return `false`, @@ -125,6 +224,7 @@ public function write($data); * If there's still data in the buffer that needs to be flushed first, then * this method SHOULD try to write out this data and only then `close()` * the stream. + * Once the stream is closed, it SHOULD emit a `close` event. * * Note that this interface gives you no control over explicitly flushing * the buffered data, as finding the appropriate time for this is beyond the @@ -180,6 +280,10 @@ public function end($data = null); * $stream->close(); * ``` * + * Once the stream is closed, it SHOULD emit a `close` event. + * Note that this event SHOULD NOT be emitted more than once, in particular + * if this method is called multiple times. + * * After calling this method, the stream MUST switch into a non-writable * mode, see also `isWritable()`. * This means that no further writes are possible, so any additional From 8af6086b1ffd791541b5ee96593de84d37d1c6c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 7 Mar 2017 10:15:12 +0100 Subject: [PATCH 4/4] Consistent event semantics for duplex streams --- README.md | 18 ++++++++++++++++++ src/DuplexStreamInterface.php | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/README.md b/README.md index e38497d..953dc9c 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ descriptor based implementation with an in-memory write buffer. * [write()](#write) * [end()](#end) * [close()](#close-1) + * [DuplexStreamInterface](#duplexstreaminterface) * [Usage](#usage) * [Install](#install) @@ -672,6 +673,23 @@ how the readable side of the stream also implements a `close()` method. In other words, after calling this method, the stream MUST switch into non-writable AND non-readable mode, see also `isReadable()`. +### DuplexStreamInterface + +The `DuplexStreamInterface` is responsible for providing an interface for +duplex streams (both readable and writable). + +It builds on top of the existing interfaces for readable and writable streams +and follows the exact same method and event semantics. +If you're new to this concept, you should look into the +`ReadableStreamInterface` and `WritableStreamInterface` first. + +Besides defining a few methods, this interface also implements the +`EventEmitterInterface` which allows you to react to the same events defined +on the `ReadbleStreamInterface` and `WritableStreamInterface`. + +See also [`ReadableStreamInterface`](#readablestreaminterface) and +[`WritableStreamInterface`](#writablestreaminterface) for more details. + ## Usage ```php $loop = React\EventLoop\Factory::create(); diff --git a/src/DuplexStreamInterface.php b/src/DuplexStreamInterface.php index bd370d0..d0575da 100644 --- a/src/DuplexStreamInterface.php +++ b/src/DuplexStreamInterface.php @@ -2,6 +2,22 @@ namespace React\Stream; +/** + * The `DuplexStreamInterface` is responsible for providing an interface for + * duplex streams (both readable and writable). + * + * It builds on top of the existing interfaces for readable and writable streams + * and follows the exact same method and event semantics. + * If you're new to this concept, you should look into the + * `ReadableStreamInterface` and `WritableStreamInterface` first. + * + * Besides defining a few methods, this interface also implements the + * `EventEmitterInterface` which allows you to react to the same events defined + * on the `ReadbleStreamInterface` and `WritableStreamInterface`. + * + * @see ReadableStreamInterface + * @see WritableStreamInterface + */ interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface { }