]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-79366: Fix a race condition when removing a logging handler (GH-154528)
authorSerhiy Storchaka <storchaka@gmail.com>
Sun, 2 Aug 2026 10:15:56 +0000 (13:15 +0300)
committerGitHub <noreply@github.com>
Sun, 2 Aug 2026 10:15:56 +0000 (13:15 +0300)
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>
Lib/logging/__init__.py
Lib/test/test_logging.py
Misc/NEWS.d/next/Library/2026-07-23-06-53-01.gh-issue-79366.3gMT7I.rst [new file with mode: 0644]

index b4a5f5cc2f598f5b313a2a6d855aa9d0ec21c939..53becca4e3a696972dcecfa4046363f7571544a6 100644 (file)
@@ -1709,7 +1709,11 @@ class Logger(Filterer):
         """
         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):
         """
index ccc7cce86883c89e1f49d671b7128284b413a2c7..d74670609ec0199de3a722dcb5c83627c8ac861e 100644 (file)
@@ -814,6 +814,23 @@ class HandlerTest(BaseTest):
 
             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 (file)
index 0000000..ecb3c3a
--- /dev/null
@@ -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.