Skip to content

Commit 3d70247

Browse files
miss-islingtonserhiy-storchakaben-spillerclaude
authored
[3.13] gh-79366: Fix a race condition when removing a logging handler (GH-154528) (GH-155078)
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 7c28dcd commit 3d70247

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
@@ -802,6 +802,23 @@ def lock_holder_thread_fn():
802802

803803
support.wait_process(pid, exitcode=0)
804804

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

806823
class BadStream(object):
807824
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)