Skip to content

Commit 23a6243

Browse files
committed
PYTHON-1660 Clear MongoClient session pool after a fork
Note that a MongoClient instance is still not fork-safe. This change avoids "Cannot start transaction X on session <SID> because a newer transaction Y has already started" errors and other incorrect command results caused by duplicate sessions in the child process.
1 parent 94cb6ac commit 23a6243

File tree

2 files changed

+19
-3
lines changed

2 files changed

+19
-3
lines changed

pymongo/client_session.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"""
9696

9797
import collections
98+
import os
9899
import sys
99100
import uuid
100101

@@ -834,12 +835,13 @@ def _start_retryable_write(self):
834835

835836

836837
class _ServerSession(object):
837-
def __init__(self):
838+
def __init__(self, pool_id):
838839
# Ensure id is type 4, regardless of CodecOptions.uuid_representation.
839840
self.session_id = {'id': Binary(uuid.uuid4().bytes, 4)}
840841
self.last_use = monotonic.time()
841842
self._transaction_id = 0
842843
self.dirty = False
844+
self.pool_id = pool_id
843845

844846
def mark_dirty(self):
845847
"""Mark this session as dirty.
@@ -869,6 +871,14 @@ class _ServerSessionPool(collections.deque):
869871
870872
This class is not thread-safe, access it while holding the Topology lock.
871873
"""
874+
def __init__(self, *args, **kwargs):
875+
super(_ServerSessionPool, self).__init__(*args, **kwargs)
876+
self.pool_id = 0
877+
878+
def reset(self):
879+
self.pool_id += 1
880+
self.clear()
881+
872882
def pop_all(self):
873883
ids = []
874884
while self:
@@ -889,15 +899,17 @@ def get_server_session(self, session_timeout_minutes):
889899
if not s.timed_out(session_timeout_minutes):
890900
return s
891901

892-
return _ServerSession()
902+
return _ServerSession(self.pool_id)
893903

894904
def return_server_session(self, server_session, session_timeout_minutes):
895905
self._clear_stale(session_timeout_minutes)
896906
if not server_session.timed_out(session_timeout_minutes):
897907
self.return_server_session_no_lock(server_session)
898908

899909
def return_server_session_no_lock(self, server_session):
900-
if not server_session.dirty:
910+
# Discard sessions from an old pool to avoid duplicate sessions in the
911+
# child process after a fork.
912+
if server_session.pool_id == self.pool_id and not server_session.dirty:
901913
self.appendleft(server_session)
902914

903915
def _clear_stale(self, session_timeout_minutes):

pymongo/topology.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ def open(self):
156156
"after forking. See PyMongo's documentation for details: "
157157
"http://api.mongodb.org/python/current/faq.html#"
158158
"is-pymongo-fork-safe")
159+
with self._lock:
160+
# Reset the session pool to avoid duplicate sessions in
161+
# the child process.
162+
self._session_pool.reset()
159163

160164
with self._lock:
161165
self._ensure_opened()

0 commit comments

Comments
 (0)