Skip to content

Commit 1fa894d

Browse files
committed
PYTHON-1434 Don't resend client metadata on the same socket
1 parent 2c932df commit 1fa894d

File tree

2 files changed

+54
-65
lines changed

2 files changed

+54
-65
lines changed

pymongo/monitor.py

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@
1616

1717
import weakref
1818

19-
from bson.codec_options import DEFAULT_CODEC_OPTIONS
20-
from bson.son import SON
21-
from pymongo import common, message, periodic_executor
19+
from pymongo import common, periodic_executor
20+
from pymongo.errors import OperationFailure
2221
from pymongo.server_type import SERVER_TYPE
23-
from pymongo.ismaster import IsMaster
2422
from pymongo.monotonic import time as _time
2523
from pymongo.read_preferences import MovingAverage
2624
from pymongo.server_description import ServerDescription
@@ -113,15 +111,12 @@ def _check_with_retry(self):
113111
# to Unknown only after retrying once.
114112
address = self._server_description.address
115113
retry = True
116-
metadata = None
117114
if self._server_description.server_type == SERVER_TYPE.Unknown:
118115
retry = False
119-
metadata = self._pool.opts.metadata
120116

121117
start = _time()
122118
try:
123-
# If the server type is unknown, send metadata with first check.
124-
return self._check_once(metadata=metadata)
119+
return self._check_once()
125120
except ReferenceError:
126121
raise
127122
except Exception as error:
@@ -140,7 +135,7 @@ def _check_with_retry(self):
140135
# Always send metadata: this is a new connection.
141136
start = _time()
142137
try:
143-
return self._check_once(metadata=self._pool.opts.metadata)
138+
return self._check_once()
144139
except ReferenceError:
145140
raise
146141
except Exception as error:
@@ -151,7 +146,7 @@ def _check_with_retry(self):
151146
self._avg_round_trip_time.reset()
152147
return default
153148

154-
def _check_once(self, metadata=None):
149+
def _check_once(self):
155150
"""A single attempt to call ismaster.
156151
157152
Returns a ServerDescription, or raises an exception.
@@ -160,8 +155,7 @@ def _check_once(self, metadata=None):
160155
if self._publish:
161156
self._listeners.publish_server_heartbeat_started(address)
162157
with self._pool.get_socket({}) as sock_info:
163-
response, round_trip_time = self._check_with_socket(
164-
sock_info, metadata=metadata)
158+
response, round_trip_time = self._check_with_socket(sock_info)
165159
self._avg_round_trip_time.add_sample(round_trip_time)
166160
sd = ServerDescription(
167161
address=address,
@@ -173,24 +167,12 @@ def _check_once(self, metadata=None):
173167

174168
return sd
175169

176-
def _check_with_socket(self, sock_info, metadata=None):
170+
def _check_with_socket(self, sock_info):
177171
"""Return (IsMaster, round_trip_time).
178172
179173
Can raise ConnectionFailure or OperationFailure.
180174
"""
181-
cmd = SON([('ismaster', 1)])
182-
if metadata is not None:
183-
cmd['client'] = metadata
184-
if self._server_description.max_wire_version >= 6:
185-
cluster_time = self._topology.max_cluster_time()
186-
if cluster_time is not None:
187-
cmd['$clusterTime'] = cluster_time
188175
start = _time()
189-
request_id, msg, max_doc_size = message.query(
190-
0, 'admin.$cmd', 0, -1, cmd,
191-
None, DEFAULT_CODEC_OPTIONS)
192-
193-
# TODO: use sock_info.command()
194-
sock_info.send_message(msg, max_doc_size)
195-
reply = sock_info.receive_message(request_id)
196-
return IsMaster(reply.command_response()), _time() - start
176+
return (sock_info.ismaster(self._pool.opts.metadata,
177+
self._topology.max_cluster_time()),
178+
_time() - start)

pymongo/pool.py

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ class SSLError(socket.error):
3333
from bson.py3compat import imap, itervalues, _unicode, integer_types
3434
from bson.son import SON
3535
from pymongo import auth, helpers, thread_util, __version__
36-
from pymongo.common import MAX_MESSAGE_SIZE, ORDERED_TYPES
36+
from pymongo.common import (MAX_BSON_SIZE,
37+
MAX_MESSAGE_SIZE,
38+
MAX_WIRE_VERSION,
39+
MAX_WRITE_BATCH_SIZE,
40+
ORDERED_TYPES)
3741
from pymongo.errors import (AutoReconnect,
3842
ConnectionFailure,
3943
ConfigurationError,
@@ -401,36 +405,49 @@ class SocketInfo(object):
401405
:Parameters:
402406
- `sock`: a raw socket object
403407
- `pool`: a Pool instance
404-
- `ismaster`: optional IsMaster instance, response to ismaster on `sock`
405408
- `address`: the server's (host, port)
406409
"""
407-
def __init__(self, sock, pool, ismaster, address):
410+
def __init__(self, sock, pool, address):
408411
self.sock = sock
409412
self.address = address
410413
self.authset = set()
411414
self.closed = False
412415
self.last_checkin_time = _time()
413-
self.is_writable = ismaster.is_writable if ismaster else None
414-
self.max_wire_version = ismaster.max_wire_version if ismaster else None
415-
self.max_bson_size = ismaster.max_bson_size if ismaster else None
416-
self.max_message_size = (
417-
ismaster.max_message_size if ismaster else MAX_MESSAGE_SIZE)
418-
self.max_write_batch_size = (
419-
ismaster.max_write_batch_size if ismaster else None)
420-
self.supports_sessions = (
421-
ismaster and ismaster.logical_session_timeout_minutes is not None)
416+
self.performed_handshake = False
417+
self.is_writable = False
418+
self.max_wire_version = MAX_WIRE_VERSION
419+
self.max_bson_size = MAX_BSON_SIZE
420+
self.max_message_size = MAX_MESSAGE_SIZE
421+
self.max_write_batch_size = MAX_WRITE_BATCH_SIZE
422+
self.supports_sessions = False
423+
self.is_mongos = False
422424

423425
self.listeners = pool.opts.event_listeners
424426

425-
if ismaster:
426-
self.is_mongos = ismaster.server_type == SERVER_TYPE.Mongos
427-
else:
428-
self.is_mongos = None
429-
430427
# The pool's pool_id changes with each reset() so we can close sockets
431428
# created before the last reset.
432429
self.pool_id = pool.pool_id
433430

431+
def ismaster(self, metadata, cluster_time):
432+
cmd = SON([('ismaster', 1)])
433+
if not self.performed_handshake:
434+
cmd['client'] = metadata
435+
self.performed_handshake = True
436+
437+
if self.max_wire_version >= 6 and cluster_time is not None:
438+
cmd['$clusterTime'] = cluster_time
439+
440+
ismaster = IsMaster(self.command('admin', cmd, publish_events=False))
441+
self.is_writable = ismaster.is_writable
442+
self.max_wire_version = ismaster.max_wire_version
443+
self.max_bson_size = ismaster.max_bson_size
444+
self.max_message_size = ismaster.max_message_size
445+
self.max_write_batch_size = ismaster.max_write_batch_size
446+
self.supports_sessions = (
447+
ismaster.logical_session_timeout_minutes is not None)
448+
self.is_mongos = ismaster.server_type == SERVER_TYPE.Mongos
449+
return ismaster
450+
434451
def command(self, dbname, spec, slave_ok=False,
435452
read_preference=ReadPreference.PRIMARY,
436453
codec_options=DEFAULT_CODEC_OPTIONS, check=True,
@@ -441,7 +458,8 @@ def command(self, dbname, spec, slave_ok=False,
441458
collation=None,
442459
session=None,
443460
client=None,
444-
retryable_write=False):
461+
retryable_write=False,
462+
publish_events=True):
445463
"""Execute a command or raise an error.
446464
447465
:Parameters:
@@ -461,6 +479,7 @@ def command(self, dbname, spec, slave_ok=False,
461479
- `session`: optional ClientSession instance.
462480
- `client`: optional MongoClient for gossipping $clusterTime.
463481
- `retryable_write`: True if this command is a retryable write.
482+
- `publish_events`: Should we publish events for this command?
464483
"""
465484
self.validate_session(client, session)
466485
if (read_concern and self.max_wire_version < 4
@@ -487,11 +506,12 @@ def command(self, dbname, spec, slave_ok=False,
487506
if retryable_write:
488507
spec['txnNumber'] = session._transaction_id()
489508
self.send_cluster_time(spec, session, client)
509+
listeners = self.listeners if publish_events else None
490510
try:
491511
return command(self.sock, dbname, spec, slave_ok,
492512
self.is_mongos, read_preference, codec_options,
493513
session, client, check, allowable_errors,
494-
self.address, check_keys, self.listeners,
514+
self.address, check_keys, listeners,
495515
self.max_bson_size, read_concern,
496516
parse_write_concern_error=parse_write_concern_error,
497517
collation=collation)
@@ -860,29 +880,16 @@ def connect(self):
860880
sock = None
861881
try:
862882
sock = _configured_socket(self.address, self.opts)
863-
if self.handshake:
864-
cmd = SON([
865-
('ismaster', 1),
866-
('client', self.opts.metadata)
867-
])
868-
ismaster = IsMaster(
869-
command(sock,
870-
'admin',
871-
cmd,
872-
False,
873-
False,
874-
ReadPreference.PRIMARY,
875-
DEFAULT_CODEC_OPTIONS,
876-
None,
877-
None))
878-
else:
879-
ismaster = None
880-
return SocketInfo(sock, self, ismaster, self.address)
881883
except socket.error as error:
882884
if sock is not None:
883885
sock.close()
884886
_raise_connection_failure(self.address, error)
885887

888+
sock_info = SocketInfo(sock, self, self.address)
889+
if self.handshake:
890+
sock_info.ismaster(self.opts.metadata, None)
891+
return sock_info
892+
886893
@contextlib.contextmanager
887894
def get_socket(self, all_credentials, checkout=False):
888895
"""Get a socket from the pool. Use with a "with" statement.

0 commit comments

Comments
 (0)