Skip to content

Emit error event and close Stream when writing to stream resource fails #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 19, 2016
Merged

Conversation

bohdanly
Copy link
Contributor

@bohdanly bohdanly commented Jul 1, 2015

Hi.
I have Ratchet working 24/7 and sometimes it starting to use 100% of CPU. In this case I need to restart it and this helps. But it happend very often, so I debug it and found that foef($this->stream) do not return true when client disconnects while event loop tick is not ended.
Same with is_resource($this->stream) it returns true.

I'm using:

  • PHP 5.5.13 / Win8 x64
  • PHP 5.4.10 / Win7 x64
  • react/event-loop 0.4.1
  • react/socket 0.4.2
  • react/stream 0.4.2
  • cboden/ratchet 0.3.3

In this case after

$sent = fwrite($this->stream, $this->data);

$this->lastError['message'] contains an error:

grabilla ya9688

There is no way to catch such behavior using best practices, so I decide to check $this->lastError['message'] on 'errno=10054'.
Maybe it will be more efficient if we will check $this->lastError['number'] on any error > 0. But to prevent just on this case I decide to check only errno=10054 in message.

I know this is not good resolution but it works.

@cboden
Copy link
Member

cboden commented Oct 1, 2015

Excellent troubleshooting @lysenkobv! I agree with you it doesn't look like a good resolution but PHP socket error handling probably doesn't allow something more elegant.

Are you able to provide a unit test proving and fixing this or steps to reproduce so we can see/test this manually?

@WyriHaximus
Copy link
Member

Impressive find 👍 , it isn't not neat but it's the best we can get, LGTM 👍

@cboden cboden modified the milestones: v0.4.4, v0.4.3 Oct 7, 2015
adamlc added a commit to Jumplead/stream that referenced this pull request Oct 8, 2015
Just testing to see if the above PR fixes out 100% CPU issues!
@bohdanly
Copy link
Contributor Author

bohdanly commented Nov 1, 2015

I wrote a test and found that BufferTest::testWritingToClosedStream() have the same logic I wrote.
I decide to not duplicate it. But it is required to make some changes in the code.

When $sent = fwrite($this->stream, $this->data); executes we need to check errors.
Better solution is to check errno. set_error_handler always handle error if one occurred.

if ($this->lastError['number'] > 0) {

In this case, that part of code is not needed anymore:

if (0 === $sent && feof($this->stream)) {
     $this->emit('error', array(new \RuntimeException('Tried to write to closed stream.'), $this));
     return;
}

...because in test testWritingToClosedStream() this type of error is catched by set_error_handler().
Error message contains fwrite(): send of 3 bytes failed with errno=32 Broken pipe, errno=8.

To be sure I replaced part of code I mentioned above by checking $sent === false

if ($sent === false) {
    $this->emit('error', array(new \RuntimeException('Send failed'), $this));
    return;
}

In conclusion error checking looks like that:

if ($this->lastError['number'] > 0) {
    $this->emit('error', array(
        new \ErrorException(
            $this->lastError['message'],
            0,
            $this->lastError['number'],
            $this->lastError['file'],
            $this->lastError['line']
        ),
        $this
    ));

    return;
}

if ($sent === false) {
    $this->emit('error', array(new \RuntimeException('Send failed'), $this));
    return;
}

and testWritingToClosedStream asserion looks like that:

$this->assertSame('fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage());

All 66 tests have executed successfully.

@bohdanly
Copy link
Contributor Author

Is anybody here? 😄

@cboden cboden merged commit 5ee8289 into reactphp:master Jan 19, 2016
@cboden
Copy link
Member

cboden commented Jan 19, 2016

👍 Again, just awesome work on this @lysenkobv! I'm very weary about changing the buffer code because of stupid strange edge cases that still haunt me to this day, but after much deliberation and testing am comfortable with this change. :-)

@clue clue changed the title Prevent infinite loop on connection close Emit error event and close Stream when writing to stream resource fails Aug 14, 2016
if (0 === $sent && feof($this->stream)) {
$this->emit('error', array(new \RuntimeException('Tried to write to closed stream.'), $this));

if ($sent === false) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this change is known to cause issues with running the Autobahn testsuite against Ratchet. Thanks to @cboden for digging into this!

I'm currently looking into providing a test case so that this is properly covered in the Stream component.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It ended up being line 90. The error I'm receiving is:

fwrite(): send of 8192 bytes failed with errno=11 Resource temporarily unavailable

On attempting to send a large amount of data (256k) to fwrite. The error simply states the buffer is full and to try again later, however the updated code now halts on all errors.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be fixed via #52.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants