PYTHON-525 Socket timeout for monitoring is connect_timeout.

The spec's justification is that while a client waits for a server to respond to a
connection, the client does not know if the server will respond eventually or if it is
down. Users help the client guess by supplying a reasonable connectTimeoutMS for
their network.

The socketTimeoutMS, on the other hand, must account for both network latency
and the operation's duration. Applications should set a long or infinite
socketTimeoutMS to wait for long-running operations.

A socket used for monitoring does two things: it connects and calls ismaster. Both
operations are fast on the server, so only network latency matters. Thus both
operations use connectTimeoutMS, since that is the value users supply to help the
client guess if a server is down, based on users' knowledge of expected latencies
on their networks.
This commit is contained in:
A. Jesse Jiryu Davis 2014-09-24 22:28:05 -04:00
parent 148bbc2fb2
commit 081ca08163
2 changed files with 46 additions and 6 deletions

View File

@ -20,6 +20,7 @@ import time
from bson.py3compat import itervalues
from pymongo import common
from pymongo.pool import PoolOptions
from pymongo.topology_description import (updated_topology_description,
TOPOLOGY_TYPE,
TopologyDescription)
@ -248,12 +249,19 @@ class Topology(object):
"""
for address, sd in self._description.server_descriptions().items():
if address not in self._servers:
m = self._settings.monitor_class(
sd, self, self._create_pool(address), self._settings)
monitor = self._settings.monitor_class(
server_description=sd,
topology=self,
pool=self._create_pool_for_monitor(address),
topology_settings=self._settings)
s = Server(sd, self._create_pool(address), m)
self._servers[address] = s
s.open()
server = Server(
server_description=sd,
pool=self._create_pool_for_server(address),
monitor=monitor)
self._servers[address] = server
server.open()
else:
self._servers[address].description = sd
@ -262,5 +270,18 @@ class Topology(object):
server.close()
self._servers.pop(address)
def _create_pool(self, address):
def _create_pool_for_server(self, address):
return self._settings.pool_class(address, self._settings.pool_options)
def _create_pool_for_monitor(self, address):
options = self._settings.pool_options
# According to the Server Discovery And Monitoring Spec, monitors use
# connect_timeout for both connect_timeout and socket_timeout. The
# pool only has one socket so max_pool_size and so on aren't needed.
return self._settings.pool_class(
address,
PoolOptions(connect_timeout=options.connect_timeout,
socket_timeout=options.connect_timeout,
ssl_context=options.ssl_context,
socket_keepalive=True))

View File

@ -30,6 +30,7 @@ from pymongo.errors import (ConfigurationError,
ConnectionFailure)
from pymongo.ismaster import IsMaster
from pymongo.monitor import Monitor
from pymongo.pool import PoolOptions
from pymongo.read_preferences import MovingAverage
from pymongo.server_description import ServerDescription
from pymongo.server_selectors import (any_server_selector,
@ -133,6 +134,24 @@ class TopologyTest(unittest.TestCase):
super(TopologyTest, self).tearDown()
class TestTopologyConfiguration(TopologyTest):
def test_timeout_configuration(self):
pool_options = PoolOptions(connect_timeout=1, socket_timeout=2)
topology_settings = TopologySettings(pool_options=pool_options)
t = Topology(topology_settings=topology_settings)
server = t.select_server(any_server_selector)
# The pool for application operations obeys our settings.
self.assertEqual(1, server._pool.opts.connect_timeout)
self.assertEqual(2, server._pool.opts.socket_timeout)
# The pool for monitoring operations uses our connect_timeout as both
# its connect_timeout and its socket_timeout.
monitor = server._monitor
self.assertEqual(1, monitor._pool.opts.connect_timeout)
self.assertEqual(1, monitor._pool.opts.socket_timeout)
class TestSingleServerTopology(TopologyTest):
def test_direct_connection(self):
for server_type, ismaster_response in [