Skip to content

Commit ceaafec

Browse files
DOCSP-37005 - Periodic Executors (#12)
Co-authored-by: Jordan Smith <[email protected]>
1 parent 73d95df commit ceaafec

File tree

3 files changed

+50
-47
lines changed

3 files changed

+50
-47
lines changed

source/faq.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ MongoDB backends for Django sessions and authentication (bypassing
517517
Does PyMongo Work with mod_wsgi?
518518
--------------------------------
519519

520-
Yes. See the configuration guide for :ref:`pymongo-and-mod_wsgi`.
520+
Yes. See the configuration guide for :ref:`pymongo-mod_wsgi`.
521521

522522
Does PyMongo Work with PythonAnywhere?
523523
--------------------------------------

source/fundamentals.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ Fundamentals
55
============
66

77
.. meta::
8-
:description: Learn how to use the (+driver-short+} to execute operations on MongoDB.
8+
:description: Learn how to use (+driver-short+} to execute operations on MongoDB.
99

1010
.. toctree::
1111
:titlesonly:
1212
:maxdepth: 1
1313

14+
/fundamentals/periodic-executors
1415
/fundamentals/type-hints
1516

17+
- :ref:`pymongo-periodic-executors`
1618
- :ref:`pymongo-type-hints`

source/fundamentals/periodic-executors.txt

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1-
.. uses periodic-executor.rst
1+
.. _pymongo-periodic-executors:
22

33
Periodic Executors
44
==================
55

6+
.. contents:: On this page
7+
:local:
8+
:backlinks: none
9+
:depth: 1
10+
:class: singlecol
11+
12+
.. facet::
13+
:name: genre
14+
:values: reference
15+
16+
.. meta::
17+
:keywords: sleep, timeout, wake
18+
619
PyMongo implements a ``~periodic_executor.PeriodicExecutor`` for two
720
purposes: as the background thread for ``~monitor.Monitor``, and to
821
regularly check if there are ``OP_KILL_CURSORS`` messages that must be sent to the server.
@@ -16,44 +29,42 @@ the cursor before finishing iteration:
1629

1730
.. code-block:: python
1831

19-
for doc in collection.find():
20-
raise Exception()
32+
for doc in collection.find():
33+
raise Exception()
2134

22-
We try to send an ``OP_KILL_CURSORS`` to the server to tell it to clean up the
23-
server-side cursor. But we must not take any locks directly from the cursor's
24-
destructor (see `PYTHON-799`_), so we cannot safely use the PyMongo data
25-
structures required to send a message. The solution is to add the cursor's id
35+
The client tries to send an ``OP_KILL_CURSORS`` to the server to tell it to clean up the
36+
server-side cursor. But the client must not take any locks directly from the cursor's
37+
destructor (see `PYTHON-799 <https://jira.mongodb.org/browse/PYTHON-799>`__
38+
), so we cannot safely use the PyMongo data
39+
structures required to send a message. The solution is to add the cursor's ID
2640
to an array on the ``~mongo_client.MongoClient`` without taking any locks.
2741

2842
Each client has a ``~periodic_executor.PeriodicExecutor`` devoted to
29-
checking the array for cursor ids. Any it sees are the result of cursors that
43+
checking the array for cursor IDs. Any it sees are the result of cursors that
3044
were freed while the server-side cursor was still open. The executor can safely
3145
take the locks it needs in order to send the ``OP_KILL_CURSORS`` message.
3246

33-
.. _PYTHON-799: https://jira.mongodb.org/browse/PYTHON-799
34-
3547
Stopping Executors
3648
------------------
3749

3850
Just as ``~cursor.Cursor`` must not take any locks from its destructor,
3951
neither can ``~mongo_client.MongoClient`` and ``~topology.Topology``.
40-
Thus, although the client calls the ``close`` method on its kill-cursors thread, and
41-
the topology calls the ``close`` method on all its monitor threads, the the ``close`` method
42-
method cannot actually call the ``wake`` method on the executor, since the ``wake`` method
52+
Thus, although the client calls the ``close()`` method on its kill-cursors thread, and
53+
the topology calls the ``close()`` method on all its monitor threads, the ``close()`` method
54+
method cannot actually call the ``wake()`` method on the executor, since the ``wake()`` method
4355
takes a lock.
4456

4557
Instead, executors wake periodically to check if ``self.close`` is set,
46-
and if so they exit.
58+
and if so, they exit.
4759

4860
A thread can log spurious errors if it wakes late in the Python interpreter's
4961
shutdown sequence, so we try to join threads before then. Each periodic
5062
executor (either a monitor or a kill-cursors thread) adds a weakref to itself
5163
to a set called ``_EXECUTORS``, in the ``periodic_executor`` module.
5264

53-
An `exit handler`_ runs on shutdown and tells all executors to stop, then
54-
tries (with a short timeout) to join all executor threads.
55-
56-
.. _exit handler: https://docs.python.org/2/library/atexit.html
65+
An `exit handler <https://docs.python.org/2/library/atexit.html>`__ runs on shutdown
66+
and tells all executors to stop, then tries (with a short timeout) to join all
67+
executor threads.
5768

5869
Monitoring
5970
----------
@@ -65,52 +76,42 @@ callback to terminate itself soon after the topology is freed.
6576

6677
Solid lines represent strong references, dashed lines weak ones:
6778

68-
.. generated with graphviz: "dot -Tpng periodic-executor-refs.dot > periodic-executor-refs.png"
69-
7079
.. image:: ../images/periodic-executor-refs.png
7180
:alt: Periodic executor references
7281

73-
See Stopping Executors above for an explanation of the ``_EXECUTORS`` set.
74-
75-
It is a requirement of the `Server Discovery And Monitoring Spec`_ that a
76-
sleeping monitor can be awakened early. Aside from infrequent wakeups to do
82+
Aside from infrequent wakeups to do
7783
their appointed chores, and occasional interruptions, periodic executors also
7884
wake periodically to check if they should terminate.
7985

80-
Our first implementation of this idea was the obvious one: use the Python
81-
standard library's threading.Condition.wait with a timeout. Another thread
82-
wakes the executor early by signaling the condition variable.
86+
Initially, {+driver-short+} used the Python
87+
standard library's ``threading.Condition.wait()`` method with a timeout. Another thread
88+
woke the executor early by signaling the condition variable.
8389

8490
A topology cannot signal the condition variable to tell the executor to
8591
terminate, because it would risk a deadlock in the garbage collector: no
8692
destructor or weakref callback can take a lock to signal the condition variable
87-
(see `PYTHON-863`_); thus the only way for a dying object to terminate a
88-
periodic executor is to set its "stopped" flag and let the executor see the
89-
flag next time it wakes.
93+
(see `PYTHON-863 <https://jira.mongodb.org/browse/PYTHON-863>`__). The only way for a
94+
dying object to terminate a periodic executor is to set its "stopped" flag and let the
95+
executor see the flag next time it wakes.
9096

9197
We erred on the side of prompt cleanup, and set the check interval at 100ms. We
9298
assumed that checking a flag and going back to sleep 10 times a second was
9399
cheap on modern machines.
94100

95-
Starting in Python 3.2, the builtin C implementation of lock.acquire takes a
96-
timeout parameter, so Python 3.2+ Condition variables sleep simply by calling
97-
lock.acquire; they are implemented as efficiently as expected.
101+
Starting in Python 3.2, the built-in C implementation of the ``lock.acquire()`` method
102+
takes a ``timeout`` parameter, so Python 3.2+ condition variables sleep simply by calling
103+
``lock.acquire()``. They are implemented as efficiently as expected.
98104

99-
But in Python 2, lock.acquire has no timeout. To wait with a timeout, a Python
100-
2 condition variable sleeps a millisecond, tries to acquire the lock, sleeps
105+
But in Python 2, the ``lock.acquire()`` method has no timeout. To wait with a timeout, a Python
106+
2 condition variable sleeps for a millisecond, tries to acquire the lock, sleeps
101107
twice as long, and tries again. This exponential backoff reaches a maximum
102108
sleep time of 50ms.
103109

104-
If PyMongo calls the condition variable's "wait" method with a short timeout,
110+
If PyMongo calls the condition variable's ``wait()`` method with a short timeout,
105111
the exponential backoff is restarted frequently. Overall, the condition variable
106-
is not waking a few times a second, but hundreds of times. (See `PYTHON-983`_.)
112+
is not waking a few times a second, but hundreds of times.
113+
(See `PYTHON-983 <https://jira.mongodb.org/browse/PYTHON-983>`__.)
107114

108-
Thus the current design of periodic executors is surprisingly simple: they
109-
do a simple ``time.sleep`` for a half-second, check if it is time to wake or
115+
Thus, the current design of periodic executors is surprisingly simple: they
116+
call the ``time.sleep()`` method to pause for a half-second, check if it is time to wake or
110117
terminate, and sleep again.
111-
112-
.. _Server Discovery And Monitoring Spec: https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-monitoring.rst#requesting-an-immediate-check
113-
114-
.. _PYTHON-863: https://jira.mongodb.org/browse/PYTHON-863
115-
116-
.. _PYTHON-983: https://jira.mongodb.org/browse/PYTHON-983

0 commit comments

Comments
 (0)