summaryrefslogtreecommitdiffstats
path: root/Lib/logging/__init__.py
diff options
context:
space:
mode:
authorGregory P. Smith <greg@krypto.org>2019-05-07 16:18:20 (GMT)
committerGitHub <noreply@github.com>2019-05-07 16:18:20 (GMT)
commit64aa6d2000665efb1a2eccae176df9520bf5f5e6 (patch)
treeb4c0efc6ae8ccc4d1da0c0d701f75f73e2d879c0 /Lib/logging/__init__.py
parente85ef7a7eacdef2f43e6bf2e67f335100e7ef2da (diff)
downloadcpython-64aa6d2000665efb1a2eccae176df9520bf5f5e6.zip
cpython-64aa6d2000665efb1a2eccae176df9520bf5f5e6.tar.gz
cpython-64aa6d2000665efb1a2eccae176df9520bf5f5e6.tar.bz2
bpo-36533: Reinit logging.Handler locks on fork(). (GH-12704)
Instead of attempting to acquire and release them all across fork which was leading to deadlocks in some applications that had chained their own handlers while holding multiple locks.
Diffstat (limited to 'Lib/logging/__init__.py')
-rw-r--r--Lib/logging/__init__.py61
1 files changed, 25 insertions, 36 deletions
diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py
index e093982..07a0c0c 100644
--- a/Lib/logging/__init__.py
+++ b/Lib/logging/__init__.py
@@ -231,49 +231,38 @@ def _releaseLock():
# Prevent a held logging lock from blocking a child from logging.
if not hasattr(os, 'register_at_fork'): # Windows and friends.
- def _register_at_fork_acquire_release(instance):
+ def _register_at_fork_reinit_lock(instance):
pass # no-op when os.register_at_fork does not exist.
-else: # The os.register_at_fork API exists
- os.register_at_fork(before=_acquireLock,
- after_in_child=_releaseLock,
- after_in_parent=_releaseLock)
-
- # A collection of instances with acquire and release methods (logging.Handler)
- # to be called before and after fork. The weakref avoids us keeping discarded
- # Handler instances alive forever in case an odd program creates and destroys
- # many over its lifetime.
- _at_fork_acquire_release_weakset = weakref.WeakSet()
-
-
- def _register_at_fork_acquire_release(instance):
- # We put the instance itself in a single WeakSet as we MUST have only
- # one atomic weak ref. used by both before and after atfork calls to
- # guarantee matched pairs of acquire and release calls.
- _at_fork_acquire_release_weakset.add(instance)
-
+else:
+ # A collection of instances with a createLock method (logging.Handler)
+ # to be called in the child after forking. The weakref avoids us keeping
+ # discarded Handler instances alive. A set is used to avoid accumulating
+ # duplicate registrations as createLock() is responsible for registering
+ # a new Handler instance with this set in the first place.
+ _at_fork_reinit_lock_weakset = weakref.WeakSet()
+
+ def _register_at_fork_reinit_lock(instance):
+ _acquireLock()
+ try:
+ _at_fork_reinit_lock_weakset.add(instance)
+ finally:
+ _releaseLock()
- def _at_fork_weak_calls(method_name):
- for instance in _at_fork_acquire_release_weakset:
- method = getattr(instance, method_name)
+ def _after_at_fork_child_reinit_locks():
+ # _acquireLock() was called in the parent before forking.
+ for handler in _at_fork_reinit_lock_weakset:
try:
- method()
+ handler.createLock()
except Exception as err:
# Similar to what PyErr_WriteUnraisable does.
print("Ignoring exception from logging atfork", instance,
- method_name, "method:", err, file=sys.stderr)
-
-
- def _before_at_fork_weak_calls():
- _at_fork_weak_calls('acquire')
+ "._reinit_lock() method:", err, file=sys.stderr)
+ _releaseLock() # Acquired by os.register_at_fork(before=.
- def _after_at_fork_weak_calls():
- _at_fork_weak_calls('release')
-
-
- os.register_at_fork(before=_before_at_fork_weak_calls,
- after_in_child=_after_at_fork_weak_calls,
- after_in_parent=_after_at_fork_weak_calls)
+ os.register_at_fork(before=_acquireLock,
+ after_in_child=_after_at_fork_child_reinit_locks,
+ after_in_parent=_releaseLock)
#---------------------------------------------------------------------------
@@ -900,7 +889,7 @@ class Handler(Filterer):
Acquire a thread lock for serializing access to the underlying I/O.
"""
self.lock = threading.RLock()
- _register_at_fork_acquire_release(self)
+ _register_at_fork_reinit_lock(self)
def acquire(self):
"""