@@ -7,22 +7,6 @@ PyMongo implements a :class:`~periodic_executor.PeriodicExecutor` for two
7
7
purposes: as the background thread for :class: `~monitor.Monitor `, and to
8
8
regularly check if there are `OP_KILL_CURSORS ` messages that must be sent to the server.
9
9
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
-
26
10
Killing Cursors
27
11
---------------
28
12
@@ -35,16 +19,17 @@ the cursor before finishing iteration::
35
19
36
20
We try to send an `OP_KILL_CURSORS ` to the server to tell it to clean up the
37
21
server-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.
42
25
43
26
Each client has a :class: `~periodic_executor.PeriodicExecutor ` devoted to
44
27
checking the array for cursor ids. Any it sees are the result of cursors that
45
28
were freed while the server-side cursor was still open. The executor can safely
46
29
take the locks it needs in order to send the `OP_KILL_CURSORS ` message.
47
30
31
+ .. _PYTHON-799 : https://jira.mongodb.org/browse/PYTHON-799
32
+
48
33
Stopping Executors
49
34
------------------
50
35
@@ -55,7 +40,7 @@ the topology calls :meth:`close` on all its monitor threads, the :meth:`close`
55
40
method cannot actually call :meth: `wake ` on the executor, since :meth: `wake `
56
41
takes a lock.
57
42
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,
59
44
and if so they exit.
60
45
61
46
A 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
67
52
tries (with a short timeout) to join all executor threads.
68
53
69
54
.. _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