1414
1515"""Class to monitor a MongoDB server on a background thread."""
1616
17- import atexit
18- import threading
19- import time
2017import weakref
2118
22- from pymongo import common , helpers , message , thread_util
19+ from pymongo import common , helpers , message , periodic_executor
2320from pymongo .server_type import SERVER_TYPE
2421from pymongo .ismaster import IsMaster
2522from pymongo .monotonic import time as _time
@@ -42,7 +39,6 @@ def __init__(
4239 The Topology is weakly referenced. The Pool must be exclusive to this
4340 Monitor.
4441 """
45- super (Monitor , self ).__init__ ()
4642 self ._server_description = server_description
4743
4844 # A weakref callback, takes ref to the dead topology as its parameter.
@@ -52,70 +48,52 @@ def close(dummy):
5248 self ._topology = weakref .proxy (topology , close )
5349 self ._pool = pool
5450 self ._settings = topology_settings
55- self ._stopped = False
56- self ._event = thread_util .Event (self ._settings .condition_class )
57- self ._thread = None
5851 self ._avg_round_trip_time = MovingAverage ()
5952
53+ # We strongly reference the executor and it weakly references us via
54+ # this closure. When the monitor is freed, a call to target() raises
55+ # ReferenceError and stops the executor.
56+ def target ():
57+ Monitor ._run (weakref .proxy (self ))
58+
59+ self ._executor = periodic_executor .PeriodicExecutor (
60+ condition_class = self ._settings .condition_class ,
61+ interval = common .HEARTBEAT_FREQUENCY ,
62+ min_interval = common .MIN_HEARTBEAT_INTERVAL ,
63+ target = target )
64+
6065 def open (self ):
6166 """Start monitoring, or restart after a fork.
6267
6368 Multiple calls have no effect.
6469 """
65- self ._stopped = False
66- started = False
67- try :
68- started = self ._thread and self ._thread .is_alive ()
69- except ReferenceError :
70- # Thread terminated.
71- pass
72-
73- if not started :
74- thread = threading .Thread (target = self .run )
75- thread .daemon = True
76- self ._thread = weakref .proxy (thread )
77- register_monitor (self )
78- thread .start ()
70+ self ._executor .open ()
7971
8072 def close (self ):
8173 """Disconnect and stop monitoring.
8274
8375 open() restarts the monitor after closing.
8476 """
85- self ._stopped = True
86- self ._pool .reset ()
77+ self ._executor .close ()
8778
88- # Wake the thread so it notices that _stopped is True.
89- self .request_check ()
79+ # Increment the pool_id and maybe close the socket. If the executor
80+ # thread has the socket checked out, it will be closed when checked in.
81+ self ._pool .reset ()
9082
9183 def join (self , timeout = None ):
92- if self ._thread is not None :
93- try :
94- self ._thread .join (timeout )
95- except ReferenceError :
96- # Thread already terminated.
97- pass
84+ self ._executor .join (timeout )
9885
9986 def request_check (self ):
10087 """If the monitor is sleeping, wake and check the server soon."""
101- self ._event . set ()
88+ self ._executor . wake ()
10289
103- def run (self ):
104- while not self ._stopped :
105- try :
106- self ._server_description = self ._check_with_retry ()
107- self ._topology .on_change (self ._server_description )
108- except ReferenceError :
109- # Topology was garbage-collected.
110- self .close ()
111- else :
112- start = _time ()
113- self ._event .wait (common .HEARTBEAT_FREQUENCY )
114- self ._event .clear ()
115- wait_time = _time () - start
116- if wait_time < common .MIN_HEARTBEAT_INTERVAL :
117- # request_check() was called before min interval passed.
118- time .sleep (common .MIN_HEARTBEAT_INTERVAL - wait_time )
90+ def _run (self ):
91+ try :
92+ self ._server_description = self ._check_with_retry ()
93+ self ._topology .on_change (self ._server_description )
94+ except ReferenceError :
95+ # Topology was garbage-collected.
96+ self .close ()
11997
12098 def _check_with_retry (self ):
12199 """Call ismaster once or twice. Reset server's pool on error.
@@ -175,34 +153,3 @@ def _check_with_socket(self, sock_info):
175153 raw_response = sock_info .receive_message (1 , request_id )
176154 result = helpers ._unpack_response (raw_response )
177155 return IsMaster (result ['data' ][0 ]), _time () - start
178-
179-
180- # MONITORS has a weakref to each running Monitor. A Monitor is kept alive by
181- # a strong reference from its Server and its Thread. Once both are destroyed
182- # the Monitor is garbage-collected and removed from MONITORS. If, however,
183- # any threads are still running when the interpreter begins to shut down,
184- # we attempt to halt and join them to avoid spurious errors.
185- MONITORS = set ()
186-
187-
188- def register_monitor (monitor ):
189- ref = weakref .ref (monitor , _on_monitor_deleted )
190- MONITORS .add (ref )
191-
192-
193- def _on_monitor_deleted (ref ):
194- MONITORS .remove (ref )
195-
196-
197- def shutdown_monitors ():
198- # Keep a local copy of MONITORS as
199- # shutting down threads has a side effect
200- # of removing them from the MONITORS set()
201- monitors = list (MONITORS )
202- for ref in monitors :
203- monitor = ref ()
204- if monitor :
205- monitor .close ()
206- monitor .join (10 )
207-
208- atexit .register (shutdown_monitors )
0 commit comments