Skip to content

Commit 22af062

Browse files
serhiy-storchakaben-spillerclaude
committed
gh-79366: Fix a race condition when removing a logging handler
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. 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 206788a commit 22af062

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
@@ -1709,7 +1709,11 @@ def removeHandler(self, hdlr):
17091709
"""
17101710
with _lock:
17111711
if hdlr in self.handlers:
1712-
self.handlers.remove(hdlr)
1712+
# Replace the list instead of mutating it in place, so that
1713+
# callHandlers() can iterate it without a lock (gh-79366).
1714+
handlers = self.handlers.copy()
1715+
handlers.remove(hdlr)
1716+
self.handlers = handlers
17131717

17141718
def hasHandlers(self):
17151719
"""

Lib/test/test_logging.py

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

815815
support.wait_process(pid, exitcode=0)
816816

817+
def test_remove_handler_while_emitting(self):
818+
# Removing a handler while callHandlers() iterates over the handlers
819+
# should not cause the following handlers to be skipped (gh-79366).
820+
logger = logging.Logger('test_remove_handler_while_emitting')
821+
calls = []
822+
class RemovingHandler(logging.Handler):
823+
def emit(self, record):
824+
calls.append('removing')
825+
logger.removeHandler(self)
826+
class CountingHandler(logging.Handler):
827+
def emit(self, record):
828+
calls.append('counting')
829+
logger.addHandler(RemovingHandler())
830+
logger.addHandler(CountingHandler())
831+
logger.error('spam')
832+
self.assertEqual(calls, ['removing', 'counting'])
833+
817834

818835
class BadStream(object):
819836
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)