1
0
mirror of https://github.com/danog/dns.git synced 2024-11-30 04:29:06 +01:00

Allow reusing of outstanding requests

Refactoring to ensure only one concurrent request is sent to the server for a unique name/type tuple
This commit is contained in:
Chris Wright 2014-06-16 03:28:59 +01:00
parent f7e199ff75
commit 8e82108197
2 changed files with 129 additions and 48 deletions

View File

@ -39,13 +39,28 @@ class Client
/**
* @var array
*/
private $outstandingLookups = [];
private $pendingLookups = [];
/**
* @var array
*/
private $pendingRequestsByNameAndType = [];
/**
* @var array
*/
private $pendingRequestsById = [];
/**
* @var int
*/
private $requestIdCounter = 0;
/**
* @var int
*/
private $lookupIdCounter = 0;
/**
* Constructor
*
@ -69,9 +84,9 @@ class Client
$this->requestBuilder = $requestBuilder;
$this->responseInterpreter = $responseInterpreter;
$serverAddress = $serverAddress === null ? (string)$serverAddress : '8.8.8.8';
$serverPort = $serverPort === null ? (int)$serverPort : 53;
$requestTimeout = $requestTimeout === null ? (int)$requestTimeout : 2000;
$serverAddress = $serverAddress !== null ? (string)$serverAddress : '8.8.8.8';
$serverPort = $serverPort !== null ? (int)$serverPort : 53;
$requestTimeout = $requestTimeout !== null ? (int)$requestTimeout : 2000;
$address = sprintf('udp://%s:%d', $serverAddress, $serverPort);
$this->socket = stream_socket_client($address, $errNo, $errStr);
@ -96,7 +111,25 @@ class Client
if ($this->requestIdCounter >= 65536) {
$this->requestIdCounter = 0;
}
} while(isset($this->outstandingLookups[$result]));
} while(isset($this->pendingRequestsById[$result]));
return $result;
}
/**
* Get the next available lookup ID
*
* @return int
*/
private function getNextFreeLookupId()
{
do {
$result = $this->lookupIdCounter++;
if ($this->lookupIdCounter >= PHP_INT_MAX) {
$this->lookupIdCounter = 0;
}
} while(isset($this->pendingLookups[$result]));
return $result;
}
@ -130,6 +163,34 @@ class Client
return $result;
}
/**
* Send a request to the server
*
* @param array $request
*/
private function sendRequest($request)
{
$packet = $this->requestBuilder->buildRequest($request['id'], $request['name'], $request['type']);
fwrite($this->socket, $packet);
$request['timeout_id'] = $this->reactor->once(function() use($request) {
unset($this->pendingRequestsByNameAndType[$request['name']][$request['type']]);
foreach ($request['lookups'] as $id => $lookup) {
$this->completePendingLookup($id, null, ResolutionErrors::ERR_SERVER_TIMEOUT);
}
}, $this->requestTimeout);
if ($this->readWatcherId === null) {
$this->readWatcherId = $this->reactor->onReadable($this->socket, function() {
$this->onSocketReadable();
});
}
$this->pendingRequestsById[$request['id']] = $request;
$this->pendingRequestsByNameAndType[$request['name']][$request['type']] = &$this->pendingRequestsById[$request['id']];
}
/**
* Handle data waiting to be read from the socket
*/
@ -143,10 +204,29 @@ class Client
}
list($id, $addr, $ttl) = $response;
$request = $this->pendingRequestsById[$id];
$type = $request['type'];
$name = $request['name'];
$this->reactor->cancel($request['timeout_id']);
unset($this->pendingRequestsById[$id], $this->pendingRequestsByNameAndType[$name][$type]);
if (!$this->pendingRequestsById) {
$this->reactor->cancel($this->readWatcherId);
$this->readWatcherId = null;
}
if ($addr !== null) {
$this->completeOutstandingRequest($id, $addr, $this->outstandingLookups[$id]['last_type'], $ttl);
foreach ($request['lookups'] as $id => $lookup) {
$this->completePendingLookup($id, $addr, $type);
}
if ($request['cache_store']) {
call_user_func($request['cache_store'], $name, $addr, $type, $ttl);
}
} else {
$this->processOutstandingLookup($id);
foreach ($request['lookups'] as $id => $lookup) {
$this->processPendingLookup($id);
}
}
}
@ -156,18 +236,11 @@ class Client
* @param int $id
* @param string $addr
* @param int $type
* @param int $ttl
*/
private function completeOutstandingRequest($id, $addr, $type, $ttl = null)
private function completePendingLookup($id, $addr, $type)
{
$this->reactor->cancel($this->outstandingLookups[$id]['timeout_id']);
call_user_func($this->outstandingLookups[$id]['callback'], $addr, $type, $ttl);
unset($this->outstandingLookups[$id]);
if (!$this->outstandingLookups) {
$this->reactor->cancel($this->readWatcherId);
$this->readWatcherId = null;
}
call_user_func($this->pendingLookups[$id]['callback'], $addr, $type);
unset($this->pendingLookups[$id]);
}
/**
@ -175,27 +248,32 @@ class Client
*
* @param int $id
*/
private function processOutstandingLookup($id)
private function processPendingLookup($id)
{
if (!$this->outstandingLookups[$id]['requests']) {
$this->completeOutstandingRequest($id, null, ResolutionErrors::ERR_NO_RECORD);
$lookup = &$this->pendingLookups[$id];
if (!$lookup['requests']) {
$this->completePendingLookup($id, null, ResolutionErrors::ERR_NO_RECORD);
return;
}
$type = array_shift($this->outstandingLookups[$id]['requests']);
$this->outstandingLookups[$id]['last_type'] = $type;
$name = $lookup['name'];
$type = array_shift($lookup['requests']);
$lookup['last_type'] = $type;
$packet = $this->requestBuilder->buildRequest($id, $this->outstandingLookups[$id]['name'], $type);
fwrite($this->socket, $packet);
$this->pendingRequestsByNameAndType[$name][$type]['lookups'][$id] = $lookup;
$this->outstandingLookups[$id]['timeout_id'] = $this->reactor->once(function() use($id) {
$this->completeOutstandingRequest($id, null, ResolutionErrors::ERR_SERVER_TIMEOUT);
}, $this->requestTimeout);
if (count($this->pendingRequestsByNameAndType[$name][$type]) === 1) {
$request = [
'id' => $this->getNextFreeRequestId(),
'name' => $name,
'type' => $type,
'lookups' => [$id => $lookup],
'timeout_id' => null,
'cache_store' => $lookup['cache_store'],
];
if ($this->readWatcherId === null) {
$this->readWatcherId = $this->reactor->onReadable($this->socket, function() {
$this->onSocketReadable();
});
$this->sendRequest($request);
}
}
@ -205,20 +283,20 @@ class Client
* @param string $name
* @param int $mode
* @param callable $callback
* @param callable $cacheStore
*/
public function resolve($name, $mode, callable $callback)
public function resolve($name, $mode, callable $callback, callable $cacheStore = null)
{
$requests = $this->getRequestList($mode);
$id = $this->getNextFreeRequestId();
$id = $this->getNextFreeLookupId();
$this->outstandingLookups[$id] = [
'name' => $name,
'requests' => $requests,
'last_type' => null,
'timeout_id' => null,
'callback' => $callback
$this->pendingLookups[$id] = [
'name' => $name,
'requests' => $this->getRequestList($mode),
'last_type' => null,
'callback' => $callback,
'cache_store' => $cacheStore,
];
$this->processOutstandingLookup($id);
$this->processPendingLookup($id);
}
}

View File

@ -31,6 +31,11 @@ class Resolver
*/
private $hostsFile;
/**
* @var callable
*/
private $cacheStoreCallback;
/**
* Constructor
*
@ -52,6 +57,10 @@ class Resolver
$this->client = $client;
$this->cache = $cache;
$this->hostsFile = $hostsFile;
if ($cache) {
$this->cacheStoreCallback = [$cache, 'store'];
}
}
/**
@ -158,13 +167,7 @@ class Resolver
return;
}
$this->client->resolve($name, $mode, function($addr, $type, $ttl) use($name, $callback) {
if ($addr !== null && $this->cache) {
$this->cache->store($name, $addr, $type, $ttl);
}
call_user_func($callback, $addr, $type);
});
$this->client->resolve($name, $mode, $callback, $this->cacheStoreCallback);
}
/**