class _RttMonitor(MonitorBase): def __init__(self, topology, topology_settings, pool): """Maintain round trip times for a server. The Topology is weakly referenced. """ super(_RttMonitor, self).__init__( topology, "pymongo_server_rtt_thread", topology_settings.heartbeat_frequency, common.MIN_HEARTBEAT_INTERVAL, ) self._pool = pool self._moving_average = MovingAverage() self._lock = threading.Lock() def close(self): self.gc_safe_close() # Increment the generation and maybe close the socket. If the executor # thread has the socket checked out, it will be closed when checked in. self._pool.reset() def add_sample(self, sample): """Add a RTT sample.""" with self._lock: self._moving_average.add_sample(sample) def average(self): """Get the calculated average, or None if no samples yet.""" with self._lock: return self._moving_average.get() def reset(self): """Reset the average RTT.""" with self._lock: return self._moving_average.reset() def _run(self): try: # NOTE: This thread is only run when when using the streaming # heartbeat protocol (MongoDB 4.4+). # XXX: Skip check if the server is unknown? rtt = self._ping() self.add_sample(rtt) except ReferenceError: # Topology was garbage-collected. self.close() except Exception: self._pool.reset() def _ping(self): """Run a "hello" command and return the RTT.""" with self._pool.get_socket() as sock_info: if self._executor._stopped: raise Exception("_RttMonitor closed") start = time.monotonic() sock_info.hello() return time.monotonic() - start
def run_scenario(self): moving_average = MovingAverage() if scenario_def['avg_rtt_ms'] != "NULL": moving_average.add_sample(scenario_def['avg_rtt_ms']) if scenario_def['new_rtt_ms'] != "NULL": moving_average.add_sample(scenario_def['new_rtt_ms']) self.assertAlmostEqual(moving_average.get(), scenario_def['new_avg_rtt'])
def test_moving_average(self): avg = MovingAverage() self.assertIsNone(avg.get()) avg.add_sample(10) self.assertAlmostEqual(10, avg.get()) avg.add_sample(20) self.assertAlmostEqual(12, avg.get()) avg.add_sample(30) self.assertAlmostEqual(15.6, avg.get())
class Monitor(object): def __init__( self, server_description, topology, pool, topology_settings): """Class to monitor a MongoDB server on a background thread. Pass an initial ServerDescription, a Topology, a Pool, and TopologySettings. The Topology is weakly referenced. The Pool must be exclusive to this Monitor. """ self._server_description = server_description self._pool = pool self._settings = topology_settings self._avg_round_trip_time = MovingAverage() self._listeners = self._settings._pool_options.event_listeners pub = self._listeners is not None self._publish = pub and self._listeners.enabled_for_server_heartbeat # We strongly reference the executor and it weakly references us via # this closure. When the monitor is freed, stop the executor soon. def target(): monitor = self_ref() if monitor is None: return False # Stop the executor. Monitor._run(monitor) return True executor = periodic_executor.PeriodicExecutor( interval=self._settings.heartbeat_frequency, min_interval=common.MIN_HEARTBEAT_INTERVAL, target=target, name="pymongo_server_monitor_thread") self._executor = executor # Avoid cycles. When self or topology is freed, stop executor soon. self_ref = weakref.ref(self, executor.close) self._topology = weakref.proxy(topology, executor.close) def open(self): """Start monitoring, or restart after a fork. Multiple calls have no effect. """ self._executor.open() def close(self): """Close and stop monitoring. open() restarts the monitor after closing. """ self._executor.close() # Increment the pool_id and maybe close the socket. If the executor # thread has the socket checked out, it will be closed when checked in. self._pool.reset() def join(self, timeout=None): self._executor.join(timeout) def request_check(self): """If the monitor is sleeping, wake and check the server soon.""" self._executor.wake() def _run(self): try: self._server_description = self._check_with_retry() self._topology.on_change(self._server_description) except ReferenceError: # Topology was garbage-collected. self.close() def _check_with_retry(self): """Call ismaster once or twice. Reset server's pool on error. Returns a ServerDescription. """ # According to the spec, if an ismaster call fails we reset the # server's pool. If a server was once connected, change its type # to Unknown only after retrying once. address = self._server_description.address retry = True metadata = None if self._server_description.server_type == SERVER_TYPE.Unknown: retry = False metadata = self._pool.opts.metadata start = _time() try: # If the server type is unknown, send metadata with first check. return self._check_once(metadata=metadata) except ReferenceError: raise except Exception as error: error_time = _time() - start if self._publish: self._listeners.publish_server_heartbeat_failed( address, error_time, error) self._topology.reset_pool(address) default = ServerDescription(address, error=error) if not retry: self._avg_round_trip_time.reset() # Server type defaults to Unknown. return default # Try a second and final time. If it fails return original error. # Always send metadata: this is a new connection. start = _time() try: return self._check_once(metadata=self._pool.opts.metadata) except ReferenceError: raise except Exception as error: error_time = _time() - start if self._publish: self._listeners.publish_server_heartbeat_failed( address, error_time, error) self._avg_round_trip_time.reset() return default def _check_once(self, metadata=None): """A single attempt to call ismaster. Returns a ServerDescription, or raises an exception. """ address = self._server_description.address if self._publish: self._listeners.publish_server_heartbeat_started(address) with self._pool.get_socket({}) as sock_info: response, round_trip_time = self._check_with_socket( sock_info, metadata=metadata) self._avg_round_trip_time.add_sample(round_trip_time) sd = ServerDescription( address=address, ismaster=response, round_trip_time=self._avg_round_trip_time.get()) if self._publish: self._listeners.publish_server_heartbeat_succeeded( address, round_trip_time, response) return sd def _check_with_socket(self, sock_info, metadata=None): """Return (IsMaster, round_trip_time). Can raise ConnectionFailure or OperationFailure. """ cmd = SON([('ismaster', 1)]) if metadata is not None: cmd['client'] = metadata if self._server_description.max_wire_version >= 6: cluster_time = self._topology.max_cluster_time() if cluster_time is not None: cmd['$clusterTime'] = cluster_time start = _time() request_id, msg, max_doc_size = message.query( 0, 'admin.$cmd', 0, -1, cmd, None, DEFAULT_CODEC_OPTIONS) # TODO: use sock_info.command() sock_info.send_message(msg, max_doc_size) reply = sock_info.receive_message(request_id) return IsMaster(reply.command_response()), _time() - start
class Monitor(object): def __init__(self, server_description, topology, pool, topology_settings): """Class to monitor a MongoDB server on a background thread. Pass an initial ServerDescription, a Topology, a Pool, and TopologySettings. The Topology is weakly referenced. The Pool must be exclusive to this Monitor. """ self._server_description = server_description self._pool = pool self._settings = topology_settings self._avg_round_trip_time = MovingAverage() # We strongly reference the executor and it weakly references us via # this closure. When the monitor is freed, stop the executor soon. def target(): monitor = self_ref() if monitor is None: return False # Stop the executor. Monitor._run(monitor) return True executor = periodic_executor.PeriodicExecutor( condition_class=self._settings.condition_class, interval=common.HEARTBEAT_FREQUENCY, min_interval=common.MIN_HEARTBEAT_INTERVAL, target=target, name="pymongo_server_monitor_thread") self._executor = executor # Avoid cycles. When self or topology is freed, stop executor soon. self_ref = weakref.ref(self, executor.close) self._topology = weakref.proxy(topology, executor.close) def open(self): """Start monitoring, or restart after a fork. Multiple calls have no effect. """ self._executor.open() def close(self): """Close and stop monitoring. open() restarts the monitor after closing. """ self._executor.close() # Increment the pool_id and maybe close the socket. If the executor # thread has the socket checked out, it will be closed when checked in. self._pool.reset() def join(self, timeout=None): self._executor.join(timeout) def request_check(self): """If the monitor is sleeping, wake and check the server soon.""" self._executor.wake() def _run(self): try: self._server_description = self._check_with_retry() self._topology.on_change(self._server_description) except ReferenceError: # Topology was garbage-collected. self.close() def _check_with_retry(self): """Call ismaster once or twice. Reset server's pool on error. Returns a ServerDescription. """ # According to the spec, if an ismaster call fails we reset the # server's pool. If a server was once connected, change its type # to Unknown only after retrying once. address = self._server_description.address retry = self._server_description.server_type != SERVER_TYPE.Unknown try: return self._check_once() except ReferenceError: raise except Exception as error: self._topology.reset_pool(address) default = ServerDescription(address, error=error) if not retry: self._avg_round_trip_time.reset() # Server type defaults to Unknown. return default # Try a second and final time. If it fails return original error. try: return self._check_once() except ReferenceError: raise except Exception: self._avg_round_trip_time.reset() return default def _check_once(self): """A single attempt to call ismaster. Returns a ServerDescription, or raises an exception. """ with self._pool.get_socket({}) as sock_info: response, round_trip_time = self._check_with_socket(sock_info) self._avg_round_trip_time.add_sample(round_trip_time) sd = ServerDescription( address=self._server_description.address, ismaster=response, round_trip_time=self._avg_round_trip_time.get()) return sd def _check_with_socket(self, sock_info): """Return (IsMaster, round_trip_time). Can raise ConnectionFailure or OperationFailure. """ start = _time() request_id, msg, max_doc_size = message.query(0, 'admin.$cmd', 0, -1, {'ismaster': 1}, None, DEFAULT_CODEC_OPTIONS) # TODO: use sock_info.command() sock_info.send_message(msg, max_doc_size) raw_response = sock_info.receive_message(1, request_id) result = helpers._unpack_response(raw_response) return IsMaster(result['data'][0]), _time() - start
class Monitor(object): def __init__( self, server_description, topology, pool, topology_settings): """Class to monitor a MongoDB server on a background thread. Pass an initial ServerDescription, a Topology, a Pool, and TopologySettings. The Topology is weakly referenced. The Pool must be exclusive to this Monitor. """ self._server_description = server_description self._pool = pool self._settings = topology_settings self._avg_round_trip_time = MovingAverage() # We strongly reference the executor and it weakly references us via # this closure. When the monitor is freed, stop the executor soon. def target(): monitor = self_ref() if monitor is None: return False # Stop the executor. Monitor._run(monitor) return True executor = periodic_executor.PeriodicExecutor( interval=common.HEARTBEAT_FREQUENCY, min_interval=common.MIN_HEARTBEAT_INTERVAL, target=target, name="pymongo_server_monitor_thread") self._executor = executor # Avoid cycles. When self or topology is freed, stop executor soon. self_ref = weakref.ref(self, executor.close) self._topology = weakref.proxy(topology, executor.close) def open(self): """Start monitoring, or restart after a fork. Multiple calls have no effect. """ self._executor.open() def close(self): """Close and stop monitoring. open() restarts the monitor after closing. """ self._executor.close() # Increment the pool_id and maybe close the socket. If the executor # thread has the socket checked out, it will be closed when checked in. self._pool.reset() def join(self, timeout=None): self._executor.join(timeout) def request_check(self): """If the monitor is sleeping, wake and check the server soon.""" self._executor.wake() def _run(self): try: self._server_description = self._check_with_retry() self._topology.on_change(self._server_description) except ReferenceError: # Topology was garbage-collected. self.close() def _check_with_retry(self): """Call ismaster once or twice. Reset server's pool on error. Returns a ServerDescription. """ # According to the spec, if an ismaster call fails we reset the # server's pool. If a server was once connected, change its type # to Unknown only after retrying once. address = self._server_description.address retry = self._server_description.server_type != SERVER_TYPE.Unknown try: return self._check_once() except ReferenceError: raise except Exception as error: self._topology.reset_pool(address) default = ServerDescription(address, error=error) if not retry: self._avg_round_trip_time.reset() # Server type defaults to Unknown. return default # Try a second and final time. If it fails return original error. try: return self._check_once() except ReferenceError: raise except Exception: self._avg_round_trip_time.reset() return default def _check_once(self): """A single attempt to call ismaster. Returns a ServerDescription, or raises an exception. """ with self._pool.get_socket({}) as sock_info: response, round_trip_time = self._check_with_socket(sock_info) self._avg_round_trip_time.add_sample(round_trip_time) sd = ServerDescription( address=self._server_description.address, ismaster=response, round_trip_time=self._avg_round_trip_time.get()) return sd def _check_with_socket(self, sock_info): """Return (IsMaster, round_trip_time). Can raise ConnectionFailure or OperationFailure. """ start = _time() request_id, msg, max_doc_size = message.query( 0, 'admin.$cmd', 0, -1, {'ismaster': 1}, None, DEFAULT_CODEC_OPTIONS) # TODO: use sock_info.command() sock_info.send_message(msg, max_doc_size) raw_response = sock_info.receive_message(1, request_id) result = helpers._unpack_response(raw_response) return IsMaster(result['data'][0]), _time() - start
class Monitor(MonitorBase): def __init__(self, server_description, topology, pool, topology_settings): """Class to monitor a MongoDB server on a background thread. Pass an initial ServerDescription, a Topology, a Pool, and TopologySettings. The Topology is weakly referenced. The Pool must be exclusive to this Monitor. """ self._server_description = server_description self._pool = pool self._settings = topology_settings self._avg_round_trip_time = MovingAverage() self._listeners = self._settings._pool_options.event_listeners pub = self._listeners is not None self._publish = pub and self._listeners.enabled_for_server_heartbeat # We strongly reference the executor and it weakly references us via # this closure. When the monitor is freed, stop the executor soon. def target(): monitor = self_ref() if monitor is None: return False # Stop the executor. Monitor._run(monitor) return True executor = periodic_executor.PeriodicExecutor( interval=self._settings.heartbeat_frequency, min_interval=common.MIN_HEARTBEAT_INTERVAL, target=target, name="pymongo_server_monitor_thread") self._executor = executor # Avoid cycles. When self or topology is freed, stop executor soon. self_ref = weakref.ref(self, executor.close) self._topology = weakref.proxy(topology, executor.close) def close(self): super(Monitor, self).close() # Increment the pool_id and maybe close the socket. If the executor # thread has the socket checked out, it will be closed when checked in. self._pool.reset() def _run(self): try: self._server_description = self._check_with_retry() self._topology.on_change(self._server_description) except ReferenceError: # Topology was garbage-collected. self.close() def _check_with_retry(self): """Call ismaster once or twice. Reset server's pool on error. Returns a ServerDescription. """ # According to the spec, if an ismaster call fails we reset the # server's pool. If a server was once connected, change its type # to Unknown only after retrying once. address = self._server_description.address retry = True if self._server_description.server_type == SERVER_TYPE.Unknown: retry = False start = _time() try: return self._check_once() except ReferenceError: raise except Exception as error: error_time = _time() - start if self._publish: self._listeners.publish_server_heartbeat_failed( address, error_time, error) self._topology.reset_pool(address) default = ServerDescription(address, error=error) if not retry: self._avg_round_trip_time.reset() # Server type defaults to Unknown. return default # Try a second and final time. If it fails return original error. # Always send metadata: this is a new connection. start = _time() try: return self._check_once() except ReferenceError: raise except Exception as error: error_time = _time() - start if self._publish: self._listeners.publish_server_heartbeat_failed( address, error_time, error) self._avg_round_trip_time.reset() return default def _check_once(self): """A single attempt to call ismaster. Returns a ServerDescription, or raises an exception. """ address = self._server_description.address if self._publish: self._listeners.publish_server_heartbeat_started(address) with self._pool.get_socket({}) as sock_info: response, round_trip_time = self._check_with_socket(sock_info) self._avg_round_trip_time.add_sample(round_trip_time) sd = ServerDescription( address=address, ismaster=response, round_trip_time=self._avg_round_trip_time.get()) if self._publish: self._listeners.publish_server_heartbeat_succeeded( address, round_trip_time, response) return sd def _check_with_socket(self, sock_info): """Return (IsMaster, round_trip_time). Can raise ConnectionFailure or OperationFailure. """ start = _time() try: return (sock_info.ismaster(self._pool.opts.metadata, self._topology.max_cluster_time()), _time() - start) except OperationFailure as exc: # Update max cluster time even when isMaster fails. self._topology.receive_cluster_time( exc.details.get('$clusterTime')) raise