Skip to content

Commit dcc0c46

Browse files
authored
Merge pull request #86 from clue-labs/end
Consistent semantics for DuplexStreamInterface::end() to ensure it SHOULD also end readable side
2 parents 5f7bd5b + 7ff110b commit dcc0c46

6 files changed

Lines changed: 61 additions & 0 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,16 @@ $stream->write('nope'); // NO-OP
629629
$stream->end(); // NO-OP
630630
```
631631

632+
If this stream is a `DuplexStreamInterface`, calling this method SHOULD
633+
also end its readable side, unless the stream supports half-open mode.
634+
In other words, after calling this method, these streams SHOULD switch
635+
into non-writable AND non-readable mode, see also `isReadable()`.
636+
This implies that in this case, the stream SHOULD NOT emit any `data`
637+
or `end` events anymore.
638+
Streams MAY choose to use the `pause()` method logic for this, but
639+
special care may have to be taken to ensure a following call to the
640+
`resume()` method SHOULD NOT continue emitting readable events.
641+
632642
Note that this method should not be confused with the `close()` method.
633643

634644
#### close()

src/CompositeStream.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ public function pause()
4646

4747
public function resume()
4848
{
49+
if (!$this->writable->isWritable()) {
50+
return;
51+
}
52+
4953
if ($this->pipeSource) {
5054
$this->pipeSource->resume();
5155
}
@@ -70,6 +74,7 @@ public function write($data)
7074

7175
public function end($data = null)
7276
{
77+
$this->readable->pause();
7378
$this->writable->end($data);
7479
}
7580

src/DuplexResourceStream.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ public function end($data = null)
146146

147147
$this->readable = false;
148148
$this->writable = false;
149+
$this->pause();
149150

150151
$this->buffer->end($data);
151152
}

src/WritableStreamInterface.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,16 @@ public function write($data);
262262
* $stream->end(); // NO-OP
263263
* ```
264264
*
265+
* If this stream is a `DuplexStreamInterface`, calling this method SHOULD
266+
* also end its readable side, unless the stream supports half-open mode.
267+
* In other words, after calling this method, these streams SHOULD switch
268+
* into non-writable AND non-readable mode, see also `isReadable()`.
269+
* This implies that in this case, the stream SHOULD NOT emit any `data`
270+
* or `end` events anymore.
271+
* Streams MAY choose to use the `pause()` method logic for this, but
272+
* special care may have to be taken to ensure a following call to the
273+
* `resume()` method SHOULD NOT continue emitting readable events.
274+
*
265275
* Note that this method should not be confused with the `close()` method.
266276
*
267277
* @param mixed|string|null $data

tests/CompositeStreamTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,35 @@ public function itShouldForwardReadableCallsToReadableStream()
4343
->expects($this->once())
4444
->method('resume');
4545
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
46+
$writable
47+
->expects($this->any())
48+
->method('isWritable')
49+
->willReturn(true);
4650

4751
$composite = new CompositeStream($readable, $writable);
4852
$composite->isReadable();
4953
$composite->pause();
5054
$composite->resume();
5155
}
5256

57+
/** @test */
58+
public function itShouldNotForwardResumeIfStreamIsNotWritable()
59+
{
60+
$readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
61+
$readable
62+
->expects($this->never())
63+
->method('resume');
64+
65+
$writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
66+
$writable
67+
->expects($this->once())
68+
->method('isWritable')
69+
->willReturn(false);
70+
71+
$composite = new CompositeStream($readable, $writable);
72+
$composite->resume();
73+
}
74+
5375
/** @test */
5476
public function endShouldDelegateToWritableWithData()
5577
{

tests/DuplexResourceStreamTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,19 @@ public function testEnd()
237237
$this->assertFalse($conn->isWritable());
238238
}
239239

240+
/**
241+
* @covers React\Stream\DuplexResourceStream::end
242+
*/
243+
public function testEndRemovesReadStreamFromLoop()
244+
{
245+
$stream = fopen('php://temp', 'r+');
246+
$loop = $this->createLoopMock();
247+
$loop->expects($this->once())->method('removeReadStream');
248+
249+
$conn = new DuplexResourceStream($stream, $loop);
250+
$conn->end('bye');
251+
}
252+
240253
public function testEndedStreamsShouldNotWrite()
241254
{
242255
$file = tempnam(sys_get_temp_dir(), 'reactphptest_');

0 commit comments

Comments
 (0)