private function getNearestHostProtocol(array $allowedServerTypes, array $readPreference)
{
$candidates = [];
$tagsets = isset($readPreference['tagsets']) ? $readPreference['tagsets'] : [[]];
foreach ($tagsets as $tagset) {
foreach ($this->replSetStatus['members'] as $key => $member) {
$tags = isset($this->replSetConf['members'][$key]['tags']) ? $this->replSetConf['members'][$key]['tags'] : [];
if (in_array($member['stateStr'], $allowedServerTypes) && array_intersect($tagset, $tags) === $tagset) {
$candidates[] = $member;
}
}
if ($candidates) {
break;
}
}
if (!$candidates) {
$msg = "No " . implode(' or ', $allowedServerTypes) . " servers available";
if (isset($readPreference['tagsets'])) {
$msg .= " matching tagsets " . json_encode($readPreference['tagsets']);
}
throw new MongoConnectionException($msg);
}
// Connect and ping all candidate servers
$min_ping = INF;
foreach ($candidates as $member) {
$host_key = $member['name'];
list($host, $port) = explode(':', $host_key);
$this->connectToHost($host, $port);
if (!isset($this->hosts[$host_key]['ping'])) {
$this->pingHost($host, $port);
}
if ($this->hosts[$host_key]['ping'] < $min_ping) {
$min_ping = $this->hosts[$host_key]['ping'];
$min_ping_host_key = $host_key;
}
}
// If we have a NEAREST read preference, always return the host with the least latency
if ($readPreference['type'] === static::RP_NEAREST && isset($min_ping_host_key)) {
return $this->protocols[$min_ping_host_key];
}
// Otherwise:
// Filter candidates to only those within the "nearest group" (default 15ms)
$candidates = array_values(array_filter($candidates, function ($member) use($min_ping) {
$host_key = $member['name'];
return $this->hosts[$host_key]['ping'] - $min_ping < self::RP_DEFAULT_ACCEPTABLE_LATENCY_MS;
}));
// Pick a random host from remaining candidates
$the_chosen_one = $candidates[mt_rand(0, count($candidates) - 1)];
return $this->protocols[$the_chosen_one['name']];
}