Skip to content

Commit ccca668

Browse files
authored
Merge pull request #290 from clue-labs/error-handler
Improve error reporting when custom error handler is used
2 parents aa95c15 + 4227053 commit ccca668

File tree

8 files changed

+132
-42
lines changed

8 files changed

+132
-42
lines changed

src/FdServer.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,21 @@ public function __construct($fd, LoopInterface $loop = null)
8989

9090
$this->loop = $loop ?: Loop::get();
9191

92-
$this->master = @\fopen('php://fd/' . $fd, 'r+');
93-
if (false === $this->master) {
92+
$errno = 0;
93+
$errstr = '';
94+
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
9495
// Match errstr from PHP's warning message.
9596
// fopen(php://fd/3): Failed to open stream: Error duping file descriptor 3; possibly it doesn't exist: [9]: Bad file descriptor
96-
$error = \error_get_last();
97-
\preg_match('/\[(\d+)\]: (.*)/', $error['message'], $m);
97+
\preg_match('/\[(\d+)\]: (.*)/', $error, $m);
9898
$errno = isset($m[1]) ? (int) $m[1] : 0;
99-
$errstr = isset($m[2]) ? $m[2] : $error['message'];
99+
$errstr = isset($m[2]) ? $m[2] : $error;
100+
});
101+
102+
$this->master = \fopen('php://fd/' . $fd, 'r+');
100103

104+
\restore_error_handler();
105+
106+
if (false === $this->master) {
101107
throw new \RuntimeException(
102108
'Failed to listen on FD ' . $fd . ': ' . $errstr . SocketServer::errconst($errno),
103109
$errno

src/SocketServer.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,20 @@ public function close()
106106
*/
107107
public static function accept($socket)
108108
{
109-
$newSocket = @\stream_socket_accept($socket, 0);
110-
111-
if (false === $newSocket) {
109+
$errno = 0;
110+
$errstr = '';
111+
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
112112
// Match errstr from PHP's warning message.
113113
// stream_socket_accept(): accept failed: Connection timed out
114-
$error = \error_get_last();
115-
$errstr = \preg_replace('#.*: #', '', $error['message']);
116-
$errno = self::errno($errstr);
114+
$errstr = \preg_replace('#.*: #', '', $error);
115+
$errno = SocketServer::errno($errstr);
116+
});
117117

118+
$newSocket = \stream_socket_accept($socket, 0);
119+
120+
\restore_error_handler();
121+
122+
if (false === $newSocket) {
118123
throw new \RuntimeException(
119124
'Unable to accept new connection: ' . $errstr . self::errconst($errno),
120125
$errno

src/TcpConnector.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,19 @@ public function connect($uri)
116116
// Linux reports socket errno and errstr again when trying to write to the dead socket.
117117
// Suppress error reporting to get error message below and close dead socket before rejecting.
118118
// This is only known to work on Linux, Mac and Windows are known to not support this.
119-
@\fwrite($stream, \PHP_EOL);
120-
$error = \error_get_last();
121-
122-
// fwrite(): send of 2 bytes failed with errno=111 Connection refused
123-
\preg_match('/errno=(\d+) (.+)/', $error['message'], $m);
124-
$errno = isset($m[1]) ? (int) $m[1] : 0;
125-
$errstr = isset($m[2]) ? $m[2] : $error['message'];
119+
$errno = 0;
120+
$errstr = '';
121+
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
122+
// Match errstr from PHP's warning message.
123+
// fwrite(): send of 1 bytes failed with errno=111 Connection refused
124+
\preg_match('/errno=(\d+) (.+)/', $error, $m);
125+
$errno = isset($m[1]) ? (int) $m[1] : 0;
126+
$errstr = isset($m[2]) ? $m[2] : $error;
127+
});
128+
129+
\fwrite($stream, \PHP_EOL);
130+
131+
\restore_error_handler();
126132
} else {
127133
// Not on Linux and ext-sockets not available? Too bad.
128134
$errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111;

src/UnixServer.php

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,25 +63,29 @@ public function __construct($path, LoopInterface $loop = null, array $context =
6363
);
6464
}
6565

66-
$this->master = @\stream_socket_server(
66+
$errno = 0;
67+
$errstr = '';
68+
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
69+
// PHP does not seem to report errno/errstr for Unix domain sockets (UDS) right now.
70+
// This only applies to UDS server sockets, see also https://3v4l.org/NAhpr.
71+
// Parse PHP warning message containing unknown error, HHVM reports proper info at least.
72+
if (\preg_match('/\(([^\)]+)\)|\[(\d+)\]: (.*)/', $error, $match)) {
73+
$errstr = isset($match[3]) ? $match['3'] : $match[1];
74+
$errno = isset($match[2]) ? (int)$match[2] : 0;
75+
}
76+
});
77+
78+
$this->master = \stream_socket_server(
6779
$path,
6880
$errno,
6981
$errstr,
7082
\STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
7183
\stream_context_create(array('socket' => $context))
7284
);
73-
if (false === $this->master) {
74-
// PHP does not seem to report errno/errstr for Unix domain sockets (UDS) right now.
75-
// This only applies to UDS server sockets, see also https://3v4l.org/NAhpr.
76-
// Parse PHP warning message containing unknown error, HHVM reports proper info at least.
77-
if ($errno === 0 && $errstr === '') {
78-
$error = \error_get_last();
79-
if (\preg_match('/\(([^\)]+)\)|\[(\d+)\]: (.*)/', $error['message'], $match)) {
80-
$errstr = isset($match[3]) ? $match['3'] : $match[1];
81-
$errno = isset($match[2]) ? (int)$match[2] : 0;
82-
}
83-
}
8485

86+
\restore_error_handler();
87+
88+
if (false === $this->master) {
8589
throw new \RuntimeException(
8690
'Failed to listen on Unix domain socket "' . $path . '": ' . $errstr . SocketServer::errconst($errno),
8791
$errno

tests/FdServerTest.php

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public function testCtorThrowsForInvalidUrl()
5151
new FdServer('tcp://127.0.0.1:8080', $loop);
5252
}
5353

54-
public function testCtorThrowsForUnknownFd()
54+
public function testCtorThrowsForUnknownFdWithoutCallingCustomErrorHandler()
5555
{
5656
if (!is_dir('/dev/fd') || defined('HHVM_VERSION')) {
5757
$this->markTestSkipped('Not supported on your platform');
@@ -62,12 +62,27 @@ public function testCtorThrowsForUnknownFd()
6262
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
6363
$loop->expects($this->never())->method('addReadStream');
6464

65+
$error = null;
66+
set_error_handler(function ($_, $errstr) use (&$error) {
67+
$error = $errstr;
68+
});
69+
6570
$this->setExpectedException(
6671
'RuntimeException',
6772
'Failed to listen on FD ' . $fd . ': ' . (function_exists('socket_strerror') ? socket_strerror(SOCKET_EBADF) . ' (EBADF)' : 'Bad file descriptor'),
6873
defined('SOCKET_EBADF') ? SOCKET_EBADF : 9
6974
);
70-
new FdServer($fd, $loop);
75+
76+
try {
77+
new FdServer($fd, $loop);
78+
79+
restore_error_handler();
80+
} catch (\Exception $e) {
81+
restore_error_handler();
82+
$this->assertNull($error);
83+
84+
throw $e;
85+
}
7186
}
7287

7388
public function testCtorThrowsIfFdIsAFileAndNotASocket()
@@ -319,7 +334,7 @@ public function testServerEmitsConnectionEventForNewConnection()
319334
$server->close();
320335
}
321336

322-
public function testEmitsErrorWhenAcceptListenerFails()
337+
public function testEmitsErrorWhenAcceptListenerFailsWithoutCallingCustomErrorHandler()
323338
{
324339
if (!is_dir('/dev/fd') || defined('HHVM_VERSION')) {
325340
$this->markTestSkipped('Not supported on your platform');
@@ -346,10 +361,18 @@ public function testEmitsErrorWhenAcceptListenerFails()
346361
$this->assertNotNull($listener);
347362
$socket = stream_socket_server('tcp://127.0.0.1:0');
348363

364+
$error = null;
365+
set_error_handler(function ($_, $errstr) use (&$error) {
366+
$error = $errstr;
367+
});
368+
349369
$time = microtime(true);
350370
$listener($socket);
351371
$time = microtime(true) - $time;
352372

373+
restore_error_handler();
374+
$this->assertNull($error);
375+
353376
$this->assertLessThan(1, $time);
354377

355378
$this->assertInstanceOf('RuntimeException', $exception);
@@ -362,7 +385,7 @@ public function testEmitsErrorWhenAcceptListenerFails()
362385
/**
363386
* @param \RuntimeException $e
364387
* @requires extension sockets
365-
* @depends testEmitsErrorWhenAcceptListenerFails
388+
* @depends testEmitsErrorWhenAcceptListenerFailsWithoutCallingCustomErrorHandler
366389
*/
367390
public function testEmitsTimeoutErrorWhenAcceptListenerFails(\RuntimeException $exception)
368391
{

tests/TcpConnectorTest.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,32 @@ public function testConstructWithoutLoopAssignsLoopAutomatically()
2525
}
2626

2727
/** @test */
28-
public function connectionToEmptyPortShouldFail()
28+
public function connectionToEmptyPortShouldFailWithoutCallingCustomErrorHandler()
2929
{
3030
$connector = new TcpConnector();
3131
$promise = $connector->connect('127.0.0.1:9999');
3232

33+
$error = null;
34+
set_error_handler(function ($_, $errstr) use (&$error) {
35+
$error = $errstr;
36+
});
37+
3338
$this->setExpectedException(
3439
'RuntimeException',
3540
'Connection to tcp://127.0.0.1:9999 failed: Connection refused' . (function_exists('socket_import_stream') ? ' (ECONNREFUSED)' : ''),
3641
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
3742
);
38-
Block\await($promise, null, self::TIMEOUT);
43+
44+
try {
45+
Block\await($promise, null, self::TIMEOUT);
46+
47+
restore_error_handler();
48+
} catch (\Exception $e) {
49+
restore_error_handler();
50+
$this->assertNull($error);
51+
52+
throw $e;
53+
}
3954
}
4055

4156
/** @test */

tests/TcpServerTest.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ public function testCloseRemovesResourceFromLoop()
276276
$server->close();
277277
}
278278

279-
public function testEmitsErrorWhenAcceptListenerFails()
279+
public function testEmitsErrorWhenAcceptListenerFailsWithoutCallingCustomErrorHandler()
280280
{
281281
$listener = null;
282282
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
@@ -295,10 +295,18 @@ public function testEmitsErrorWhenAcceptListenerFails()
295295
$this->assertNotNull($listener);
296296
$socket = stream_socket_server('tcp://127.0.0.1:0');
297297

298+
$error = null;
299+
set_error_handler(function ($_, $errstr) use (&$error) {
300+
$error = $errstr;
301+
});
302+
298303
$time = microtime(true);
299304
$listener($socket);
300305
$time = microtime(true) - $time;
301306

307+
restore_error_handler();
308+
$this->assertNull($error);
309+
302310
$this->assertLessThan(1, $time);
303311

304312
$this->assertInstanceOf('RuntimeException', $exception);
@@ -311,7 +319,7 @@ public function testEmitsErrorWhenAcceptListenerFails()
311319
/**
312320
* @param \RuntimeException $e
313321
* @requires extension sockets
314-
* @depends testEmitsErrorWhenAcceptListenerFails
322+
* @depends testEmitsErrorWhenAcceptListenerFailsWithoutCallingCustomErrorHandler
315323
*/
316324
public function testEmitsTimeoutErrorWhenAcceptListenerFails(\RuntimeException $exception)
317325
{

tests/UnixServerTest.php

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,12 +240,27 @@ public function testCtorThrowsForInvalidAddressScheme()
240240
new UnixServer('tcp://localhost:0', $loop);
241241
}
242242

243-
public function testCtorThrowsWhenPathIsNotWritable()
243+
public function testCtorThrowsWhenPathIsNotWritableWithoutCallingCustomErrorHandler()
244244
{
245245
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
246246

247+
$error = null;
248+
set_error_handler(function ($_, $errstr) use (&$error) {
249+
$error = $errstr;
250+
});
251+
247252
$this->setExpectedException('RuntimeException');
248-
$server = new UnixServer('/dev/null', $loop);
253+
254+
try {
255+
new UnixServer('/dev/null', $loop);
256+
257+
restore_error_handler();
258+
} catch (\Exception $e) {
259+
restore_error_handler();
260+
$this->assertNull($error);
261+
262+
throw $e;
263+
}
249264
}
250265

251266
public function testResumeWithoutPauseIsNoOp()
@@ -285,7 +300,7 @@ public function testCloseRemovesResourceFromLoop()
285300
$server->close();
286301
}
287302

288-
public function testEmitsErrorWhenAcceptListenerFails()
303+
public function testEmitsErrorWhenAcceptListenerFailsWithoutCallingCustomErrorHandler()
289304
{
290305
$listener = null;
291306
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
@@ -304,10 +319,18 @@ public function testEmitsErrorWhenAcceptListenerFails()
304319
$this->assertNotNull($listener);
305320
$socket = stream_socket_server('tcp://127.0.0.1:0');
306321

322+
$error = null;
323+
set_error_handler(function ($_, $errstr) use (&$error) {
324+
$error = $errstr;
325+
});
326+
307327
$time = microtime(true);
308328
$listener($socket);
309329
$time = microtime(true) - $time;
310330

331+
restore_error_handler();
332+
$this->assertNull($error);
333+
311334
$this->assertLessThan(1, $time);
312335

313336
$this->assertInstanceOf('RuntimeException', $exception);
@@ -320,7 +343,7 @@ public function testEmitsErrorWhenAcceptListenerFails()
320343
/**
321344
* @param \RuntimeException $e
322345
* @requires extension sockets
323-
* @depends testEmitsErrorWhenAcceptListenerFails
346+
* @depends testEmitsErrorWhenAcceptListenerFailsWithoutCallingCustomErrorHandler
324347
*/
325348
public function testEmitsTimeoutErrorWhenAcceptListenerFails(\RuntimeException $exception)
326349
{

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy