Jump to content

Recommended Posts

I have an error event which is triggered upon a communication error, and I "think" $error will be set to the error text.  Am I correct?

 

How can I test it?  I wish to trigger a sockets/communication error somehow, and not just ideally wait for one to eventually happen.  How can I trigger such an error?

<?php
$loop = Factory::create();
$socket = new React\Socket\Server($loop);
$socket->on('connection', function (\React\Socket\ConnectionInterface $stream){
    $stream->on('data', function($rsp) {/*bla bla bla*/});
    $stream->on('close', function($conn) {/*bla bla bla*/});
    $stream->on('error', function($error, $stream) {
        echo "Stream Error: $error";
        $stream->close();
    });
});
$socket->listen('0.0.0.0',1337);
$loop->run();
Link to comment
https://forums.phpfreaks.com/topic/302944-how-to-cause-a-sockets-error/
Share on other sites

According to the docs, $error will be an Exception. According to the code (Server.php), it will be a RuntimeException. And there's only one argument to the callback, not two.

 

The error happens when stream_socket_accept() returns false. Following the rabbit hole, it boils down to accept(2) failing, which could be due to reasons including

- Connection was aborted between the initial connection and the server accepting the connection (hard to test)

- Interrupt before the connection was accepted (hard to test)

- Reached the limit of open file handles in the process and/or system (don't want to test)

- Out of memory (don't want to test)

- Invalid socket (probably can't test)

- Firewall does not allow connection (awkward but probably possible to test)

 

So basically it's not really testable and it's unlikely to happen anyways.

According to the docs, $error will be an Exception. According to the code (Server.php), it will be a RuntimeException. And there's only one argument to the callback, not two.

 

The error happens when stream_socket_accept() returns false. Following the rabbit hole, it boils down to accept(2) failing, which could be due to reasons including

- Connection was aborted between the initial connection and the server accepting the connection (hard to test)

- Interrupt before the connection was accepted (hard to test)

- Reached the limit of open file handles in the process and/or system (don't want to test)

- Out of memory (don't want to test)

- Invalid socket (probably can't test)

- Firewall does not allow connection (awkward but probably possible to test)

 

So basically it's not really testable and it's unlikely to happen anyways.

Thanks requinix.  I tried unplugging the Ethernet, and it did not trigger an error.  Expected?

 

 

As far as one or two arguments to the callback...

 

https://github.com/reactphp/stream/blob/c3647ea3d338ebc7332b1a29959f305e62cf2136/src/Stream.php#L61 shows the following:

$that = $this;
$this->buffer->on('error', function ($error) use ($that) {
    $that->emit('error', array($error, $that));
    $that->close();
});

And https://github.com/igorw/evenement/blob/master/src/Evenement/EventEmitterTrait.php#L62 shows the following:

public function emit($event, array $arguments = [])
{
    foreach ($this->listeners($event) as $listener) {
        call_user_func_array($listener, $arguments);
    }
}

So, is $foo in $stream->on('error', function($foo) {...}); an array with $foo[0] as the error and $foo[1] as the stream?

Thanks requinix.  I tried unplugging the Ethernet, and it did not trigger an error.  Expected?

Expected. You'd have to unplug during the accept() sequence, which only takes microseconds to complete.

 

As far as one or two arguments to the callback...

I must have seen the lower-level error being emit()ed on the buffer. So it looks like the callback you're using does take two: an Exception and a Stream.

I must have seen the lower-level error being emit()ed on the buffer. So it looks like the callback you're using does take two: an Exception and a Stream.

 

I didn't expect that. Do all events such as $stream->on('whatever', function($exception, $stream, $andMore?) {/*bla bla bla*/}); receive potentially more than one argument?  How do you know?

[Michael@devserver www]$ grep -rn '/var/www/react' -e "->emit("
/var/www/react/src/JSONStream.php:40:                $this->emit('data', [$data]);
/var/www/react/vendor/react/socket/src/Server.php:44:                $that->emit('error', array(new \RuntimeException('Error accepting new connection')));
/var/www/react/vendor/react/socket/src/Server.php:58:        $this->emit('connection', array($client));
/var/www/react/vendor/react/socket/src/SecureServer.php:63:            $that->emit('error', array($error));
/var/www/react/vendor/react/socket/src/SecureServer.php:93:                $that->emit('connection', array($conn));
/var/www/react/vendor/react/socket/src/SecureServer.php:96:                $that->emit('error', array($error));
/var/www/react/vendor/react/stream/src/WritableStream.php:36:        $this->emit('end', array($this));
/var/www/react/vendor/react/stream/src/WritableStream.php:37:        $this->emit('close', array($this));
/var/www/react/vendor/react/stream/src/ThroughStream.php:22:        $this->readable->emit('data', array($this->filter($data), $this));
/var/www/react/vendor/react/stream/src/ThroughStream.php:28:            $this->readable->emit('data', array($this->filter($data), $this));
/var/www/react/vendor/react/stream/src/Stream.php:61:            $that->emit('error', array($error, $that));
/var/www/react/vendor/react/stream/src/Stream.php:66:            $that->emit('drain', array($that));
/var/www/react/vendor/react/stream/src/Stream.php:114:        $this->emit('end', array($this));
/var/www/react/vendor/react/stream/src/Stream.php:115:        $this->emit('close', array($this));
/var/www/react/vendor/react/stream/src/Stream.php:164:            $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error), $this));
/var/www/react/vendor/react/stream/src/Stream.php:170:            $this->emit('data', array($data, $this));
/var/www/react/vendor/react/stream/src/Util.php:15:        $dest->emit('pipe', array($source));
/var/www/react/vendor/react/stream/src/Util.php:41:                $target->emit($event, func_get_args());
/var/www/react/vendor/react/stream/src/Buffer.php:71:        $this->emit('close', array($this));
/var/www/react/vendor/react/stream/src/Buffer.php:110:            $this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . $error->getMessage(), 0, $error), $this));
/var/www/react/vendor/react/stream/src/Buffer.php:120:            $this->emit('drain', array($this));
/var/www/react/vendor/react/stream/src/Buffer.php:128:            $this->emit('full-drain', array($this));
/var/www/react/vendor/react/stream/src/ReadableStream.php:38:        $this->emit('end', array($this));
/var/www/react/vendor/react/stream/src/ReadableStream.php:39:        $this->emit('close', array($this));
/var/www/react/vendor/react/stream/tests/StreamTest.php:160:        $buffer->emit('drain');
/var/www/react/vendor/react/stream/tests/StreamTest.php:161:        $buffer->emit('error', array(new \RuntimeException('Whoops')));
/var/www/react/vendor/react/stream/tests/ThroughStreamTest.php:30:        $readable->emit('data', array('foo'));
/var/www/react/vendor/react/stream/tests/CompositeStreamTest.php:93:        $readable->emit('data', array('foo'));
/var/www/react/vendor/react/stream/tests/CompositeStreamTest.php:94:        $writable->emit('drain');
/var/www/react/vendor/react/stream/tests/CompositeStreamTest.php:111:        $input->emit('data', array('foo'));
/var/www/react/vendor/react/stream/tests/CompositeStreamTest.php:135:        $input->emit('data', array('foo'));
/var/www/react/vendor/react/stream/tests/CompositeStreamTest.php:136:        $writable->emit('drain');
/var/www/react/vendor/react/stream/tests/CompositeStreamTest.php:153:        $readable->emit('data', array('foo'));
/var/www/react/vendor/react/stream/tests/WritableStreamTest.php:20:        $readable->emit('data', array('foo'));
/var/www/react/vendor/react/stream/tests/BufferedSinkTest.php:106:        $sink->emit('error', array(new \Exception('Shit happens')));
/var/www/react/vendor/react/stream/tests/BufferedSinkTest.php:150:        $readable->emit('error', array(new \Exception('Shit happens')));
/var/www/react/vendor/react/stream/tests/BufferedSinkTest.php:165:        $readable->emit('data', array('foo'));
/var/www/react/vendor/react/stream/tests/BufferedSinkTest.php:166:        $readable->emit('data', array('bar'));
/var/www/react/vendor/react/stream/tests/BufferedSinkTest.php:180:        $readable->emit('data', array('foo'));
/var/www/react/vendor/react/stream/tests/Stub/ReadableStreamStub.php:23:        $this->emit('data', array($data));
/var/www/react/vendor/react/stream/tests/Stub/ReadableStreamStub.php:29:        $this->emit('error', array($error));
/var/www/react/vendor/react/stream/tests/Stub/ReadableStreamStub.php:35:        $this->emit('end', array());
/var/www/react/vendor/react/stream/tests/Stub/ReadableStreamStub.php:52:        $this->emit('close');
/var/www/react/vendor/react/stream/tests/UtilTest.php:131:        $source->emit('data', array('hello'));
/var/www/react/vendor/react/stream/tests/UtilTest.php:132:        $source->emit('foo', array('bar'));
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:60:        $this->emitter->emit('foo');
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:64:        $this->emitter->emit('foo');
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:77:        $this->emitter->emit('foo', array('a', 'b'));
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:91:        $this->emitter->emit('foo');
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:108:        $this->emitter->emit('foo', ['bar']);
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:126:        $this->emitter->emit('foo', ['bar', 'baz']);
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:132:        $this->emitter->emit('foo');
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:133:        $this->emitter->emit('foo', ['bar']);
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:134:        $this->emitter->emit('foo', ['bar', 'baz']);
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:150:        $this->emitter->emit('foo');
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:166:        $this->emitter->emit('foo');
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:182:        $this->emitter->emit('foo');
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:197:        $this->emitter->emit('foo');
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:212:        $this->emitter->emit('foo');
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:231:        $this->emitter->emit('foo');
/var/www/react/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php:232:        $this->emitter->emit('bar');
/var/www/react/vendor/evenement/evenement/README.md:73:$emitter->emit('user.created', array($user));
[Michael@devserver www]$
Edited by NotionCommotion

Since their documentation doesn't, you know, document this - at least not unambiguously - you'd have to look at the code.

 

Start off with your code:

$socket->on('connection', function (\React\Socket\ConnectionInterface $stream){
That looks for events on $socket, so you'd look at Server for places it does an emit (if you wanted to listen for errors on it).

 

$stream->on('error', function($error, $stream) {
That's on ConnectionInterface, which is obviously an interface so the classes that matter are the ones that implement it. Check them for emits.

I didn't expect that. Do all events such as $stream->on('whatever', function($exception, $stream, $andMore?) {/*bla bla bla*/}); receive potentially more than one argument?  How do you know?

Generally that is information that would be documented. Ideally there would be some documentation detailing each event name that could be triggered, what arguments it will receive and what return value it expects (if any). Since they are lacking in the documentation area you just have to go digging around in the code to find answers to a lot of these such questions.

 

One alternative in this case if you don't want to dig into the code is to have PHP tell you what the arguments are using func_get_args.

    $stream->on('error', function($error, $stream) {
        var_dump(func_get_args());
    });

One alternative in this case if you don't want to dig into the code is to have PHP tell you what the arguments are using func_get_args.

The following is on the close event instead of the error event.  What am I looking for?

/var/www/datalogger/src/Server.php:98:
array(1) {
  [0] =>
  class React\Socket\Connection#43 ( {
    public $bufferSize =>
    int(65536)
    public $stream =>
    resource(68) of type (stream)
    protected $readable =>
    bool(false)
    protected $writable =>
    bool(false)
    protected $closing =>
    bool(false)
    protected $loop =>
    class React\EventLoop\StreamSelectLoop#30 ( {
      private $nextTickQueue =>
      class React\EventLoop\Tick\NextTickQueue#31 (2) {
        ...
      }
      private $futureTickQueue =>
      class React\EventLoop\Tick\FutureTickQueue#33 (2) {
        ...
      }
      private $timers =>
      class React\EventLoop\Timer\Timers#35 (3) {
        ...
      }
      private $readStreams =>
      array(2) {
        ...
      }
      private $readListeners =>
      array(2) {
        ...
      }
      private $writeStreams =>
      array(0) {
        ...
      }
      private $writeListeners =>
      array(0) {
        ...
      }
      private $running =>
      bool(true)
    }
    protected $buffer =>
    class React\Stream\Buffer#44 (7) {
      public $stream =>
      resource(68) of type (stream)
      public $listening =>
      bool(false)
      public $softLimit =>
      int(65536)
      private $writable =>
      bool(false)
      private $loop =>
      class React\EventLoop\StreamSelectLoop#30 ( {
        ...
      }
      private $data =>
      string(0) ""
      protected $listeners =>
      array(3) {
        ...
      }
    }
    protected $listeners =>
    array(3) {
      'data' =>
      array(1) {
        ...
      }
      'close' =>
      array(1) {
        ...
      }
      'error' =>
      array(1) {
        ...
      }
    }
  }
}

Start off with your code:

$socket->on('connection', function (\React\Socket\ConnectionInterface $stream){
That looks for events on $socket, so you'd look at Server for places it does an emit (if you wanted to listen for errors on it).

Okay, Server has two emits.  I expected the connection event which returns the $client.  Evidently, there is also an error event on $socket which I suppose I should listen for.

$this->emit('connection', array($client));
$that->emit('error', array(new \RuntimeException('Error accepting new connection')));

$stream->on('error', function($error, $stream) {
That's on ConnectionInterface, which is obviously an interface so the classes that matter are the ones that implement it. Check them for emits.

 

 

Okay, I know $stream is of class type \React\Socket\ConnectionInterface because of this line, right?

$socket->on('connection', function (\React\Socket\ConnectionInterface $stream){

So, I look there and see...

interface ConnectionInterface extends DuplexStreamInterface

And so on..

interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface

But ReadableStreamInterface and WritableStreamInterface only have the following two emits...

        $this->emit('end', array($this));
        $this->emit('close', array($this));

I see my close emit (and end whatever that is), but no data or error emit.

 

Where am I going wrong?

But ReadableStreamInterface and WritableStreamInterface only have the following two emits...

You don't need to worry about the lower-level interfaces as the callback guarantees that the object is of type ConnectionInterface. So look at classes that implement it, and subclasses of those.

 

Apparently they're Streams? Looks like Stream does a two-argument callback, while ReadableStreamStub in the test code does just one...

The following is on the close event instead of the error event.  What am I looking for?

# Function accepts one argument
array(1) {  

  # Argument number 1 is ...
  [0] =>
  class React\Socket\Connection#43 ( {
    #...
  }

  #if there were more arguments they would also be listed.
}
The code is just a quick way to get the number and type of arguments being passed into a function. When the arguments are objects you get a bit of a mess to dig through unfortunately.

I forgot a totally obvious way to test code:

 

Hack it.

 

Find the code that does the stream_socket_accept and modify it to show false instead. Then see what happens.

//$variable = stream_socket_accept($args);
$variable = false;

Where am I going wrong?

Kicken's guide to digging through code.

 

The starting point:

<?php
$loop = Factory::create();
$socket = new React\Socket\Server($loop);
$socket->on('connection', function (\React\Socket\ConnectionInterface $stream){
    $stream->on('data', function($rsp) {/*bla bla bla*/});
    $stream->on('close', function($conn) {/*bla bla bla*/});
    $stream->on('error', function($error, $stream) {
        echo "Stream Error: $error";
        $stream->close();
    });
});
$socket->listen('0.0.0.0',1337);
$loop->run();
?>
What we want to find out:
  • What is $stream exactly?
  • What events can it emit?
  • What parameters do those events provide?
What we know:
  • $socket is an instance of \React\Socket\Server
  • $socket emits a connection event that provides $stream
Lets go digging!

 

First into \React\Socket\Server to find the connection event

<?php
class Server extends EventEmitter implements ServerInterface
{
	//...
	public function handleConnection($socket)
	{
		stream_set_blocking($socket, 0);
		$client = $this->createConnection($socket);
		$this->emit('connection', array($client));
	}

	//...

	public function createConnection($socket)
	{
		return new Connection($socket, $this->loop);
	}

	//...
}
?>
We see here that the connection event provides one argument which is an instance of \React\Socket\Connection.

 

Now lets look at \React\Socket\Connection to see what kind of events it can emit.

<?php
class Connection extends Stream implements ConnectionInterface 
{
    //...
}
?>
The Connection class itself doesn't appear to emit anything.

 

However, Connection is an extension of \React\Stream\Stream so lets go look there next.

<?php
class Stream extends EventEmitter implements DuplexStreamInterface
{
    //...
    public function __construct($stream, LoopInterface $loop)
    {
        //...
        $that = $this;

        $this->buffer->on('error', function ($error) use ($that) {
            $that->emit('error', array($error, $that));
            $that->close();
        });

        $this->buffer->on('drain', function () use ($that) {
            $that->emit('drain', array($that));
        });
        //...
    }

    //...
    public function close()
    {
        //...
        $this->emit('end', array($this));
        $this->emit('close', array($this));
        //...
    }

    //...
    public function handleData($stream)
    {
        //...
        if ($error !== null) {
            $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error), $this));
            $this->close();
            return;
        }

        if ($data !== '') {
            $this->emit('data', array($data, $this));
        }
        //...
    }
}   
?>
So the Stream class seems to do most the work. It will emit error, drain, end, close, and data events.

 

error provides two arguments. An exception describing the error and the stream instance.

drain, end and close provide one argument, the stream instance.

data provides two arguments. The data that was received and the stream instance.

 

Now you could keep digging if you want but that covers what we set out to find initially.

  • Like 1

I like "Kicken's guide to digging through code"!  I can follow along, but find it a little difficult to determine when not to go down a rabbit hole when doing it by myself.  I guess I need to just dig some more and get more practice.  Thanks for the thorough examples!

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.