Skip to content

Commit

Permalink
Merge pull request #197 from clue-labs/error-handler
Browse files Browse the repository at this point in the history
Improve error reporting when custom error handler is used
  • Loading branch information
WyriHaximus committed Apr 19, 2022
2 parents 0717627 + 2e65928 commit 2bc106d
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 13 deletions.
21 changes: 16 additions & 5 deletions src/Query/TcpTransportExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,24 @@ public function handleWritable()
$this->loop->addReadStream($this->socket, array($this, 'handleRead'));
}

$written = @\fwrite($this->socket, $this->writeBuffer);
$errno = 0;
$errstr = '';
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
// Match errstr from PHP's warning message.
// fwrite(): Send of 327712 bytes failed with errno=32 Broken pipe
\preg_match('/errno=(\d+) (.+)/', $error, $m);
$errno = isset($m[1]) ? (int) $m[1] : 0;
$errstr = isset($m[2]) ? $m[2] : $error;
});

$written = \fwrite($this->socket, $this->writeBuffer);

\restore_error_handler();

if ($written === false || $written === 0) {
$error = \error_get_last();
\preg_match('/errno=(\d+) (.+)/', $error['message'], $m);
$this->closeError(
'Unable to send query to DNS server ' . $this->nameserver . ' (' . (isset($m[2]) ? $m[2] : $error['message']) . ')',
isset($m[1]) ? (int) $m[1] : 0
'Unable to send query to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
$errno
);
return;
}
Expand Down
21 changes: 15 additions & 6 deletions src/Query/UdpTransportExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ public function query(Query $query)
}

// UDP connections are instant, so try connection without a loop or timeout
$errno = 0;
$errstr = '';
$socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0);
if ($socket === false) {
return \React\Promise\reject(new \RuntimeException(
Expand All @@ -139,18 +141,25 @@ public function query(Query $query)

// set socket to non-blocking and immediately try to send (fill write buffer)
\stream_set_blocking($socket, false);
$written = @\fwrite($socket, $queryData);

if ($written !== \strlen($queryData)) {
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
// Write may potentially fail, but most common errors are already caught by connection check above.
// Among others, macOS is known to report here when trying to send to broadcast address.
// This can also be reproduced by writing data exceeding `stream_set_chunk_size()` to a server refusing UDP data.
// fwrite(): send of 8192 bytes failed with errno=111 Connection refused
$error = \error_get_last();
\preg_match('/errno=(\d+) (.+)/', $error['message'], $m);
\preg_match('/errno=(\d+) (.+)/', $error, $m);
$errno = isset($m[1]) ? (int) $m[1] : 0;
$errstr = isset($m[2]) ? $m[2] : $error;
});

$written = \fwrite($socket, $queryData);

\restore_error_handler();

if ($written !== \strlen($queryData)) {
return \React\Promise\reject(new \RuntimeException(
'DNS query for ' . $query->describe() . ' failed: Unable to send query to DNS server ' . $this->nameserver . ' (' . (isset($m[2]) ? $m[2] : $error['message']) . ')',
isset($m[1]) ? (int) $m[1] : 0
'DNS query for ' . $query->describe() . ' failed: Unable to send query to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
$errno
));
}

Expand Down
10 changes: 9 additions & 1 deletion tests/Query/TcpTransportExecutorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ public function testQueryStaysPendingWhenClientCanNotSendExcessiveMessageInOneCh
$this->assertTrue($writePending);
}

public function testQueryRejectsWhenClientKeepsSendingWhenServerClosesSocket()
public function testQueryRejectsWhenClientKeepsSendingWhenServerClosesSocketWithoutCallingCustomErrorHandler()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('addWriteStream');
Expand All @@ -380,6 +380,11 @@ public function testQueryRejectsWhenClientKeepsSendingWhenServerClosesSocket()
$client = stream_socket_accept($server);
fclose($client);

$error = null;
set_error_handler(function ($_, $errstr) use (&$error) {
$error = $errstr;
});

$executor->handleWritable();

$ref = new \ReflectionProperty($executor, 'writePending');
Expand All @@ -394,6 +399,9 @@ public function testQueryRejectsWhenClientKeepsSendingWhenServerClosesSocket()
$executor->handleWritable();
}

restore_error_handler();
$this->assertNull($error);

$exception = null;
$promise->then(null, function ($reason) use (&$exception) {
$exception = $reason;
Expand Down
10 changes: 9 additions & 1 deletion tests/Query/UdpTransportExecutorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public function testQueryRejectsIfServerConnectionFails()
throw $exception;
}

public function testQueryRejectsIfSendToServerFailsAfterConnection()
public function testQueryRejectsIfSendToServerFailsAfterConnectionWithoutCallingCustomErrorHandler()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->never())->method('addReadStream');
Expand All @@ -164,9 +164,17 @@ public function testQueryRejectsIfSendToServerFailsAfterConnection()
$ref->setAccessible(true);
$ref->setValue($executor, PHP_INT_MAX);

$error = null;
set_error_handler(function ($_, $errstr) use (&$error) {
$error = $errstr;
});

$query = new Query(str_repeat('a.', 100000) . '.example', Message::TYPE_A, Message::CLASS_IN);
$promise = $executor->query($query);

restore_error_handler();
$this->assertNull($error);

$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);

$exception = null;
Expand Down

0 comments on commit 2bc106d

Please sign in to comment.