@@ -7,22 +7,6 @@ PyMongo implements a :class:`~periodic_executor.PeriodicExecutor` for two
77purposes: as the background thread for :class: `~monitor.Monitor `, and to
88regularly check if there are `OP_KILL_CURSORS ` messages that must be sent to the server.
99
10- Monitoring
11- ----------
12-
13- For each server in the topology, :class: `~topology.Topology ` launches a
14- monitor thread. This thread must not prevent the topology from being freed,
15- so it weakrefs the topology. Furthermore, it uses a weakref callback to close
16- itself promptly when the topology is freed.
17-
18- Solid lines represent strong references, dashed lines weak ones:
19-
20- .. generated with graphviz from periodic-executor-refs.dot
21-
22- .. image :: ../static/periodic-executor-refs.png
23-
24- See `Stopping Executors `_ below for an explanation of ``_EXECUTORS ``.
25-
2610Killing Cursors
2711---------------
2812
@@ -35,16 +19,17 @@ the cursor before finishing iteration::
3519
3620We try to send an `OP_KILL_CURSORS ` to the server to tell it to clean up the
3721server-side cursor. But we must not take any locks directly from the cursor's
38- destructor (see `PYTHON-799 <https://jira.mongodb.org/browse/PYTHON-799 >`_),
39- so we cannot safely use the PyMongo data structures required to send a message.
40- The solution is to add the cursor's id to an array on the
41- :class: `~mongo_client.MongoClient ` without taking any locks.
22+ destructor (see `PYTHON-799 `_), so we cannot safely use the PyMongo data
23+ structures required to send a message. The solution is to add the cursor's id
24+ to an array on the :class: `~mongo_client.MongoClient ` without taking any locks.
4225
4326Each client has a :class: `~periodic_executor.PeriodicExecutor ` devoted to
4427checking the array for cursor ids. Any it sees are the result of cursors that
4528were freed while the server-side cursor was still open. The executor can safely
4629take the locks it needs in order to send the `OP_KILL_CURSORS ` message.
4730
31+ .. _PYTHON-799 : https://jira.mongodb.org/browse/PYTHON-799
32+
4833Stopping Executors
4934------------------
5035
@@ -55,7 +40,7 @@ the topology calls :meth:`close` on all its monitor threads, the :meth:`close`
5540method cannot actually call :meth: `wake ` on the executor, since :meth: `wake `
5641takes a lock.
5742
58- Instead, executors wake very frequently to check if ``self.close `` is set,
43+ Instead, executors wake periodically to check if ``self.close `` is set,
5944and if so they exit.
6045
6146A thread can log spurious errors if it wakes late in the Python interpreter's
@@ -67,3 +52,62 @@ An `exit handler`_ runs on shutdown and tells all executors to stop, then
6752tries (with a short timeout) to join all executor threads.
6853
6954.. _exit handler : https://docs.python.org/2/library/atexit.html
55+
56+ Monitoring
57+ ----------
58+
59+ For each server in the topology, :class: `~topology.Topology ` uses a periodic
60+ executor to launch a monitor thread. This thread must not prevent the topology
61+ from being freed, so it weakrefs the topology. Furthermore, it uses a weakref
62+ callback to terminate itself soon after the topology is freed.
63+
64+ Solid lines represent strong references, dashed lines weak ones:
65+
66+ .. generated with graphviz: "dot -Tpng periodic-executor-refs.dot > periodic-executor-refs.png"
67+
68+ .. image :: ../static/periodic-executor-refs.png
69+
70+ See `Stopping Executors `_ above for an explanation of the ``_EXECUTORS `` set.
71+
72+ It is a requirement of the `Server Discovery And Monitoring Spec `_ that a
73+ sleeping monitor can be awakened early. Aside from infrequent wakeups to do
74+ their appointed chores, and occasional interruptions, periodic executors also
75+ wake periodically to check if they should terminate.
76+
77+ Our first implementation of this idea was the obvious one: use the Python
78+ standard library's threading.Condition.wait with a timeout. Another thread
79+ wakes the executor early by signaling the condition variable.
80+
81+ A topology cannot signal the condition variable to tell the executor to
82+ terminate, because it would risk a deadlock in the garbage collector: no
83+ destructor or weakref callback can take a lock to signal the condition variable
84+ (see `PYTHON-863 `_); thus the only way for a dying object to terminate a
85+ periodic executor is to set its "stopped" flag and let the executor see the
86+ flag next time it wakes.
87+
88+ We erred on the side of prompt cleanup, and set the check interval at 100ms. We
89+ assumed that checking a flag and going back to sleep 10 times a second was
90+ cheap on modern machines.
91+
92+ Starting in Python 3.2, the builtin C implementation of lock.acquire takes a
93+ timeout parameter, so Python 3.2+ Condition variables sleep simply by calling
94+ lock.acquire; they are implemented as efficiently as expected.
95+
96+ But in Python 2, lock.acquire has no timeout. To wait with a timeout, a Python
97+ 2 condition variable sleeps a millisecond, tries to acquire the lock, sleeps
98+ twice as long, and tries again. This exponential backoff reaches a maximum
99+ sleep time of 50ms.
100+
101+ If PyMongo calls the condition variable's "wait" method with a short timeout,
102+ the exponential backoff is restarted frequently. Overall, the condition variable
103+ is not waking a few times a second, but hundreds of times. (See `PYTHON-983 `_.)
104+
105+ Thus the current design of periodic executors is surprisingly simple: they
106+ do a simple `time.sleep ` for a half-second, check if it is time to wake or
107+ terminate, and sleep again.
108+
109+ .. _Server Discovery And Monitoring Spec : https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#requesting-an-immediate-check
110+
111+ .. _PYTHON-863 : https://jira.mongodb.org/browse/PYTHON-863
112+
113+ .. _PYTHON-983 : https://jira.mongodb.org/browse/PYTHON-983
0 commit comments