5858 InvalidDocument ,
5959 OperationFailure ,
6060 InvalidOperation )
61+ from pymongo .thread_util import DummyLock
6162
6263EMPTY = b ("" )
6364MAX_RETRY = 3
@@ -277,6 +278,10 @@ def __init__(self, rsc, event_class):
277278 def start_sync (self ):
278279 """Start the Monitor and block until it's really started.
279280 """
281+ # start() can return before the thread is fully bootstrapped,
282+ # so a fork can leave the thread thinking it's alive in a child
283+ # process when it's really dead:
284+ # http://bugs.python.org/issue18418.
280285 self .start () # Implemented in subclasses.
281286 self .started_event .wait (5 )
282287
@@ -337,13 +342,6 @@ def __init__(self, rsc):
337342 self .setName ("ReplicaSetMonitorThread" )
338343 self .setDaemon (True )
339344
340- # Track whether the thread has started. (Greenlets track this already.)
341- self .started = False
342-
343- def start (self ):
344- self .started = True
345- super (MonitorThread , self ).start ()
346-
347345 def run (self ):
348346 """Override Thread's run method.
349347 """
@@ -363,9 +361,16 @@ class MonitorGreenlet(Monitor, Greenlet):
363361 """Greenlet based replica set monitor.
364362 """
365363 def __init__ (self , rsc ):
364+ self .monitor_greenlet_alive = False
366365 Monitor .__init__ (self , rsc , Event )
367366 Greenlet .__init__ (self )
368367
368+ def start_sync (self ):
369+ self .monitor_greenlet_alive = True
370+
371+ # Call superclass.
372+ Monitor .start_sync (self )
373+
369374 # Don't override `run` in a Greenlet. Add _run instead.
370375 # Refer to gevent's Greenlet docs and source for more
371376 # information.
@@ -375,8 +380,11 @@ def _run(self):
375380 self .monitor ()
376381
377382 def isAlive (self ):
378- # Gevent defines bool(Greenlet) as True if it's alive.
379- return bool (self )
383+ # bool(self) isn't immediately True after someone calls start(),
384+ # but isAlive() is. Thus it's safe for greenlets to do:
385+ # "if not monitor.isAlive(): monitor.start()"
386+ # ... and be guaranteed only one greenlet starts the monitor.
387+ return self .monitor_greenlet_alive
380388
381389except ImportError :
382390 pass
@@ -641,6 +649,7 @@ def __init__(self, hosts_or_uri=None, max_pool_size=100,
641649 self .__tz_aware = common .validate_boolean ('tz_aware' , tz_aware )
642650 self .__document_class = document_class
643651 self .__monitor = None
652+ self .__closed = False
644653
645654 # Compatibility with mongo_client.MongoClient
646655 host = kwargs .pop ('host' , hosts_or_uri )
@@ -765,17 +774,18 @@ def __init__(self, hosts_or_uri=None, max_pool_size=100,
765774 # Common case: monitor RS with a background thread.
766775 self .__monitor_class = MonitorThread
767776
768- self .__monitor = self .__monitor_class (self )
769- register_monitor (self .__monitor )
770-
771- self .__monitor_lock = threading .Lock ()
777+ if self .__use_greenlets :
778+ # Greenlets don't need to lock around access to the monitor.
779+ # A Greenlet can safely do:
780+ # "if not self.__monitor: self.__monitor = monitor_class()"
781+ # because it won't be interrupted between the check and the
782+ # assignment.
783+ self .__monitor_lock = DummyLock ()
784+ else :
785+ self .__monitor_lock = threading .Lock ()
772786
773787 if _connect :
774- # Wait for the monitor to really start. Otherwise if we return to
775- # caller and caller forks immediately, the monitor could think it's
776- # still alive in the child process when it really isn't.
777- # See http://bugs.python.org/issue18418.
778- self .__monitor .start_sync ()
788+ self .__ensure_monitor ()
779789
780790 def _cached (self , dbname , coll , index ):
781791 """Test if `index` is cached.
@@ -1074,28 +1084,26 @@ def __schedule_refresh(self, sync=False):
10741084 is in progress, the work of refreshing the state is only performed
10751085 once.
10761086 """
1077- if not self .__monitor :
1087+ if self .__closed :
10781088 raise InvalidOperation ('MongoReplicaSetClient has been closed' )
10791089
1080- if not self .__monitor .isAlive ():
1081- # We've forked since monitor was created.
1082- self .__restart_monitor ()
1083-
1084- self .__monitor .schedule_refresh ()
1090+ monitor = self .__ensure_monitor ()
1091+ monitor .schedule_refresh ()
10851092 if sync :
1086- self . __monitor .wait_for_refresh (timeout_seconds = 5 )
1093+ monitor .wait_for_refresh (timeout_seconds = 5 )
10871094
1088- def __restart_monitor (self ):
1095+ def __ensure_monitor (self ):
1096+ """Ensure the monitor is started, and return it."""
10891097 self .__monitor_lock .acquire ()
10901098 try :
1091- # Another thread may have restarted the monitor while we were
1092- # waiting for the lock.
1093- if self .__monitor . isAlive ():
1094- return
1095-
1096- self . __monitor = self . __monitor_class ( self )
1097- register_monitor ( self . __monitor )
1098- self . __monitor . start_sync ()
1099+ # Another thread can start the monitor while we wait for the lock.
1100+ if self . __monitor is not None and self . __monitor . isAlive ():
1101+ return self .__monitor
1102+
1103+ monitor = self . __monitor = self . __monitor_class ( self )
1104+ register_monitor ( monitor )
1105+ monitor . start_sync ( )
1106+ return monitor
10991107 finally :
11001108 self .__monitor_lock .release ()
11011109
@@ -1261,13 +1269,8 @@ def _ensure_connected(self, sync=False):
12611269 """Ensure this client instance is connected to a primary.
12621270 """
12631271 # This may be the first time we're connecting to the set.
1264- if self .__monitor and not self .__monitor .started :
1265- try :
1266- self .__monitor .start ()
1267- # Minor race condition. It's possible that two (or more)
1268- # threads could call monitor.start() consecutively. Just pass.
1269- except RuntimeError :
1270- pass
1272+ self .__ensure_monitor ()
1273+
12711274 if sync :
12721275 rs_state = self .__rs_state
12731276 if not rs_state .primary_member :
@@ -1301,6 +1304,7 @@ def close(self):
13011304 .. versionchanged:: 2.2.1
13021305 The :meth:`close` method now terminates the replica set monitor.
13031306 """
1307+ self .__closed = True
13041308 self .__rs_state = RSState (self .__make_threadlocal ())
13051309
13061310 monitor , self .__monitor = self .__monitor , None
0 commit comments