[3.14] gh-79366: Fix a race condition when removing a logging handler (GH-154528) (GH-155077)

serhiy-storchaka <[email protected]> Sun, 02 Aug 2026 06:43:11 -0400 (EDT)
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/eae50e7a04f3a0cf219adc97c625f17b6e2e0d56
commit: eae50e7a04f3a0cf219adc97c625f17b6e2e0d56
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-02T10:42:57Z
summary:

[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 083e0388b0a39d379ab0e7370e8dce7574a87171)

Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: Ben Spiller <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-07-23-06-53-01.gh-issue-79366.3gMT7I.rst
M Lib/logging/__init__.py
M Lib/test/test_logging.py

diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py
index 9005f1ef865c909..6e7606269fb7133 100644
--- a/Lib/logging/__init__.py
+++ b/Lib/logging/__init__.py
@@ -1694,7 +1694,11 @@ def removeHandler(self, hdlr):
         """
         with _lock:
             if hdlr in self.handlers:
-                self.handlers.remove(hdlr)
+                # Replace the list instead of mutating it in place, so that
+                # callHandlers() can iterate it without a lock (gh-79366).
+                handlers = self.handlers.copy()
+                handlers.remove(hdlr)
+                self.handlers = handlers
 
     def hasHandlers(self):
         """
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index 0c27c03ac850610..74b740510f3c791 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -799,6 +799,23 @@ def lock_holder_thread_fn():
 
             support.wait_process(pid, exitcode=0)
 
+    def test_remove_handler_while_emitting(self):
+        # Removing a handler while callHandlers() iterates over the handlers
+        # should not cause the following handlers to be skipped (gh-79366).
+        logger = logging.Logger('test_remove_handler_while_emitting')
+        calls = []
+        class RemovingHandler(logging.Handler):
+            def emit(self, record):
+                calls.append('removing')
+                logger.removeHandler(self)
+        class CountingHandler(logging.Handler):
+            def emit(self, record):
+                calls.append('counting')
+        logger.addHandler(RemovingHandler())
+        logger.addHandler(CountingHandler())
+        logger.error('spam')
+        self.assertEqual(calls, ['removing', 'counting'])
+
 
 class BadStream(object):
     def write(self, data):
diff --git a/Misc/NEWS.d/next/Library/2026-07-23-06-53-01.gh-issue-79366.3gMT7I.rst b/Misc/NEWS.d/next/Library/2026-07-23-06-53-01.gh-issue-79366.3gMT7I.rst
new file mode 100644
index 000000000000000..ecb3c3ad92cb2be
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-23-06-53-01.gh-issue-79366.3gMT7I.rst
@@ -0,0 +1,3 @@
+Fixed a race condition in :mod:`logging`:
+if a handler was removed while a record was being emitted,
+the following handlers of the same logger could be skipped.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]