Skip to content

Commit eae50e7

Browse files
miss-islingtonserhiy-storchakaben-spillerclaude
authored
[3.14] gh-79366: Fix a race condition when removing a logging handler (GH-154528) (GH-155077)
removeHandler() mutated the handler list in place, so if a handler was removed while callHandlers() was iterating the same list, the following handlers could be skipped. Replace the list instead of mutating it. (cherry picked from commit 083e038) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Ben Spiller <11992588+ben-spiller@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c30cf5c commit eae50e7

3 files changed

Lines changed: 25 additions & 1 deletion

File tree

Lib/logging/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1694,7 +1694,11 @@ def removeHandler(self, hdlr):
16941694
"""
16951695
with _lock:
16961696
if hdlr in self.handlers:
1697-
self.handlers.remove(hdlr)
1697+
# Replace the list instead of mutating it in place, so that
1698+
# callHandlers() can iterate it without a lock (gh-79366).
1699+
handlers = self.handlers.copy()
1700+
handlers.remove(hdlr)
1701+
self.handlers = handlers
16981702

16991703
def hasHandlers(self):
17001704
"""

Lib/test/test_logging.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,23 @@ def lock_holder_thread_fn():
799799

800800
support.wait_process(pid, exitcode=0)
801801

802+
def test_remove_handler_while_emitting(self):
803+
# Removing a handler while callHandlers() iterates over the handlers
804+
# should not cause the following handlers to be skipped (gh-79366).
805+
logger = logging.Logger('test_remove_handler_while_emitting')
806+
calls = []
807+
class RemovingHandler(logging.Handler):
808+
def emit(self, record):
809+
calls.append('removing')
810+
logger.removeHandler(self)
811+
class CountingHandler(logging.Handler):
812+
def emit(self, record):
813+
calls.append('counting')
814+
logger.addHandler(RemovingHandler())
815+
logger.addHandler(CountingHandler())
816+
logger.error('spam')
817+
self.assertEqual(calls, ['removing', 'counting'])
818+
802819

803820
class BadStream(object):
804821
def write(self, data):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed a race condition in :mod:`logging`:
2+
if a handler was removed while a record was being emitted,
3+
the following handlers of the same logger could be skipped.

0 commit comments

Comments
 (0)