Skip to content

Commit a4ccfa5

Browse files
authored
PYTHON-2938 Fix race condition caused by MongoClient._process_periodic_tasks(client) (#752)
1 parent 6bb8a1f commit a4ccfa5

File tree

2 files changed

+39
-7
lines changed

2 files changed

+39
-7
lines changed

pymongo/mongo_client.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,8 +1596,14 @@ def _process_kill_cursors(self):
15961596
try:
15971597
self._cleanup_cursor(True, cursor_id, address, sock_mgr,
15981598
None, False)
1599-
except Exception:
1600-
helpers._handle_exception()
1599+
except Exception as exc:
1600+
if (isinstance(exc, InvalidOperation)
1601+
and self._topology._closed):
1602+
# Raise the exception when client is closed so that it
1603+
# can be caught in _process_periodic_tasks
1604+
raise
1605+
else:
1606+
helpers._handle_exception()
16011607

16021608
# Don't re-open topology if it's closed and there's no pending cursors.
16031609
if address_to_cursor_ids:
@@ -1606,18 +1612,25 @@ def _process_kill_cursors(self):
16061612
try:
16071613
self._kill_cursors(
16081614
cursor_ids, address, topology, session=None)
1609-
except Exception:
1610-
helpers._handle_exception()
1615+
except Exception as exc:
1616+
if (isinstance(exc, InvalidOperation) and
1617+
self._topology._closed):
1618+
raise
1619+
else:
1620+
helpers._handle_exception()
16111621

16121622
# This method is run periodically by a background thread.
16131623
def _process_periodic_tasks(self):
16141624
"""Process any pending kill cursors requests and
16151625
maintain connection pool parameters."""
1616-
self._process_kill_cursors()
16171626
try:
1627+
self._process_kill_cursors()
16181628
self._topology.update_pool(self.__all_credentials)
1619-
except Exception:
1620-
helpers._handle_exception()
1629+
except Exception as exc:
1630+
if isinstance(exc, InvalidOperation) and self._topology._closed:
1631+
return
1632+
else:
1633+
helpers._handle_exception()
16211634

16221635
def __start_session(self, implicit, **kwargs):
16231636
# Raises ConfigurationError if sessions are not supported.

test/test_client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,24 @@ def test_network_error_message(self):
15911591
with self.assertRaisesRegex(AutoReconnect, expected):
15921592
client.pymongo_test.test.find_one({})
15931593

1594+
@unittest.skipIf('PyPy' in sys.version, 'PYTHON-2938 could fail on PyPy')
1595+
def test_process_periodic_tasks(self):
1596+
client = rs_or_single_client()
1597+
coll = client.db.collection
1598+
coll.insert_many([{} for _ in range(5)])
1599+
cursor = coll.find(batch_size=2)
1600+
cursor.next()
1601+
c_id = cursor.cursor_id
1602+
self.assertIsNotNone(c_id)
1603+
client.close()
1604+
# Add cursor to kill cursors queue
1605+
del cursor
1606+
wait_until(lambda: client._MongoClient__kill_cursors_queue,
1607+
"waited for cursor to be added to queue")
1608+
client._process_periodic_tasks() # This must not raise or print any exceptions
1609+
with self.assertRaises(InvalidOperation):
1610+
coll.insert_many([{} for _ in range(5)])
1611+
15941612
@unittest.skipUnless(
15951613
_HAVE_DNSPYTHON, "DNS-related tests need dnspython to be installed")
15961614
def test_service_name_from_kwargs(self):
@@ -1613,6 +1631,7 @@ def test_service_name_from_kwargs(self):
16131631
'customname')
16141632

16151633

1634+
16161635
class TestExhaustCursor(IntegrationTest):
16171636
"""Test that clients properly handle errors from exhaust cursors."""
16181637

0 commit comments

Comments
 (0)