bpo-40091: Fix a hang at fork in the logging module (GH-19416)

Fix a hang at fork in the logging module: the new private
_at_fork_reinit() method is now used to reinitialize locks at fork in
the child process.

The createLock() method is no longer used at fork.
This commit is contained in:
Victor Stinner 2020-04-14 00:25:34 +02:00 committed by GitHub
parent 25a6833f79
commit 4c3da783cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 14 additions and 12 deletions

View File

@ -234,11 +234,9 @@ if not hasattr(os, 'register_at_fork'): # Windows and friends.
def _register_at_fork_reinit_lock(instance): def _register_at_fork_reinit_lock(instance):
pass # no-op when os.register_at_fork does not exist. pass # no-op when os.register_at_fork does not exist.
else: else:
# A collection of instances with a createLock method (logging.Handler) # A collection of instances with a _at_fork_reinit method (logging.Handler)
# to be called in the child after forking. The weakref avoids us keeping # to be called in the child after forking. The weakref avoids us keeping
# discarded Handler instances alive. A set is used to avoid accumulating # discarded Handler instances alive.
# 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() _at_fork_reinit_lock_weakset = weakref.WeakSet()
def _register_at_fork_reinit_lock(instance): def _register_at_fork_reinit_lock(instance):
@ -249,16 +247,12 @@ else:
_releaseLock() _releaseLock()
def _after_at_fork_child_reinit_locks(): def _after_at_fork_child_reinit_locks():
# _acquireLock() was called in the parent before forking.
for handler in _at_fork_reinit_lock_weakset: for handler in _at_fork_reinit_lock_weakset:
try: handler._at_fork_reinit()
handler.createLock()
except Exception as err:
# Similar to what PyErr_WriteUnraisable does.
print("Ignoring exception from logging atfork", instance,
"._reinit_lock() method:", err, file=sys.stderr)
_releaseLock() # Acquired by os.register_at_fork(before=.
# _acquireLock() was called in the parent before forking.
# The lock is reinitialized to unlocked state.
_lock._at_fork_reinit()
os.register_at_fork(before=_acquireLock, os.register_at_fork(before=_acquireLock,
after_in_child=_after_at_fork_child_reinit_locks, after_in_child=_after_at_fork_child_reinit_locks,
@ -891,6 +885,9 @@ class Handler(Filterer):
self.lock = threading.RLock() self.lock = threading.RLock()
_register_at_fork_reinit_lock(self) _register_at_fork_reinit_lock(self)
def _at_fork_reinit(self):
self.lock._at_fork_reinit()
def acquire(self): def acquire(self):
""" """
Acquire the I/O thread lock. Acquire the I/O thread lock.
@ -2168,6 +2165,9 @@ class NullHandler(Handler):
def createLock(self): def createLock(self):
self.lock = None self.lock = None
def _at_fork_reinit(self):
pass
# Warnings integration # Warnings integration
_warnings_showwarning = None _warnings_showwarning = None

View File

@ -0,0 +1,2 @@
Fix a hang at fork in the logging module: the new private _at_fork_reinit()
method is now used to reinitialize locks at fork in the child process.