NotionCommotion Posted January 16, 2017 Share Posted January 16, 2017 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(); Quote Link to comment Share on other sites More sharing options...
requinix Posted January 16, 2017 Share Posted January 16, 2017 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. Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted January 16, 2017 Author Share Posted January 16, 2017 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? Quote Link to comment Share on other sites More sharing options...
requinix Posted January 16, 2017 Share Posted January 16, 2017 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. Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted January 17, 2017 Author Share Posted January 17, 2017 (edited) 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 January 17, 2017 by NotionCommotion Quote Link to comment Share on other sites More sharing options...
requinix Posted January 17, 2017 Share Posted January 17, 2017 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. Quote Link to comment Share on other sites More sharing options...
kicken Posted January 17, 2017 Share Posted January 17, 2017 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()); }); Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted January 17, 2017 Author Share Posted January 17, 2017 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) { ... } } } } Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted January 17, 2017 Author Share Posted January 17, 2017 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? Quote Link to comment Share on other sites More sharing options...
requinix Posted January 17, 2017 Share Posted January 17, 2017 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... Quote Link to comment Share on other sites More sharing options...
kicken Posted January 17, 2017 Share Posted January 17, 2017 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. Quote Link to comment Share on other sites More sharing options...
requinix Posted January 17, 2017 Share Posted January 17, 2017 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; Quote Link to comment Share on other sites More sharing options...
kicken Posted January 17, 2017 Share Posted January 17, 2017 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. 1 Quote Link to comment Share on other sites More sharing options...
NotionCommotion Posted January 18, 2017 Author Share Posted January 18, 2017 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! Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.