From 582b59307b1b7a17bd1391008554e84c97657639 Mon Sep 17 00:00:00 2001 From: Charles Machalow Date: Sun, 13 Apr 2025 00:53:13 -0700 Subject: [PATCH 1/2] [3.13] gh-132106: Ensure that running `logging.handlers.QueueListener` cannot be started again (GH-132444) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevents a thread leak Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> (cherry picked from commit 5863cd70b8782313b52bb8c71a4127d7ea4c50e9) Co-authored-by: Charles Machalow --- Doc/library/logging.handlers.rst | 4 +++ Lib/logging/handlers.py | 3 ++ Lib/test/test_logging.py | 36 +++++++++++++++++-- ...-04-12-09-30-24.gh-issue-132106.OxUds3.rst | 2 ++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-04-12-09-30-24.gh-issue-132106.OxUds3.rst diff --git a/Doc/library/logging.handlers.rst b/Doc/library/logging.handlers.rst index 5a081f9e7add99..e63c253e123552 100644 --- a/Doc/library/logging.handlers.rst +++ b/Doc/library/logging.handlers.rst @@ -1172,6 +1172,10 @@ possible, while any potentially slow operations (such as sending an email via This starts up a background thread to monitor the queue for LogRecords to process. + .. versionchanged:: next + Raises :exc:`RuntimeError` if called and the listener is already + running. + .. method:: stop() Stops the listener. diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py index da9c59c119f3db..d3ea06c731ef89 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -1545,6 +1545,9 @@ def start(self): This starts up a background thread to monitor the queue for LogRecords to process. """ + if self._thread is not None: + raise RuntimeError("Listener already started") + self._thread = t = threading.Thread(target=self._monitor) t.daemon = True t.start() diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 678c23dad67faa..c52243896908db 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -4336,8 +4336,40 @@ def test_queue_listener(self): self.assertTrue(handler.matches(levelno=logging.CRITICAL, message='6')) handler.close() - @unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'), - 'logging.handlers.QueueListener required for this test') + def test_queue_listener_context_manager(self): + handler = TestHandler(support.Matcher()) + with logging.handlers.QueueListener(self.queue, handler) as listener: + self.assertIsInstance(listener, logging.handlers.QueueListener) + self.assertIsNotNone(listener._thread) + self.assertIsNone(listener._thread) + + # doesn't hurt to call stop() more than once. + listener.stop() + self.assertIsNone(listener._thread) + + def test_queue_listener_context_manager(self): + handler = TestHandler(support.Matcher()) + with logging.handlers.QueueListener(self.queue, handler) as listener: + self.assertIsInstance(listener, logging.handlers.QueueListener) + self.assertIsNotNone(listener._thread) + self.assertIsNone(listener._thread) + + # doesn't hurt to call stop() more than once. + listener.stop() + self.assertIsNone(listener._thread) + + def test_queue_listener_multi_start(self): + handler = TestHandler(support.Matcher()) + with logging.handlers.QueueListener(self.queue, handler) as listener: + self.assertRaises(RuntimeError, listener.start) + + with listener: + self.assertRaises(RuntimeError, listener.start) + + listener.start() + listener.stop() + +>>>>>>> 5863cd70b87... gh-132106: Ensure that running `logging.handlers.QueueListener` cannot be started again (GH-132444) def test_queue_listener_with_StreamHandler(self): # Test that traceback and stack-info only appends once (bpo-34334, bpo-46755). listener = logging.handlers.QueueListener(self.queue, self.root_hdlr) diff --git a/Misc/NEWS.d/next/Library/2025-04-12-09-30-24.gh-issue-132106.OxUds3.rst b/Misc/NEWS.d/next/Library/2025-04-12-09-30-24.gh-issue-132106.OxUds3.rst new file mode 100644 index 00000000000000..b6d58a29f9b42f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-12-09-30-24.gh-issue-132106.OxUds3.rst @@ -0,0 +1,2 @@ +:meth:`QueueListener.start ` now +raises a :exc:`RuntimeError` if the listener is already started. From 87c7e6752db47fd88d18fd508c80b79e19cb14d4 Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Sun, 13 Apr 2025 12:30:53 +0100 Subject: [PATCH 2/2] Tidy up tests. --- Lib/test/test_logging.py | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index c52243896908db..58e0381c4aa934 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -4300,8 +4300,6 @@ def test_formatting(self): self.assertEqual(formatted_msg, log_record.msg) self.assertEqual(formatted_msg, log_record.message) - @unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'), - 'logging.handlers.QueueListener required for this test') def test_queue_listener(self): handler = TestHandler(support.Matcher()) listener = logging.handlers.QueueListener(self.queue, handler) @@ -4336,40 +4334,18 @@ def test_queue_listener(self): self.assertTrue(handler.matches(levelno=logging.CRITICAL, message='6')) handler.close() - def test_queue_listener_context_manager(self): - handler = TestHandler(support.Matcher()) - with logging.handlers.QueueListener(self.queue, handler) as listener: - self.assertIsInstance(listener, logging.handlers.QueueListener) - self.assertIsNotNone(listener._thread) - self.assertIsNone(listener._thread) - - # doesn't hurt to call stop() more than once. - listener.stop() - self.assertIsNone(listener._thread) - - def test_queue_listener_context_manager(self): - handler = TestHandler(support.Matcher()) - with logging.handlers.QueueListener(self.queue, handler) as listener: - self.assertIsInstance(listener, logging.handlers.QueueListener) - self.assertIsNotNone(listener._thread) - self.assertIsNone(listener._thread) - # doesn't hurt to call stop() more than once. listener.stop() self.assertIsNone(listener._thread) def test_queue_listener_multi_start(self): handler = TestHandler(support.Matcher()) - with logging.handlers.QueueListener(self.queue, handler) as listener: - self.assertRaises(RuntimeError, listener.start) - - with listener: - self.assertRaises(RuntimeError, listener.start) - + listener = logging.handlers.QueueListener(self.queue, handler) listener.start() + self.assertRaises(RuntimeError, listener.start) listener.stop() + self.assertIsNone(listener._thread) ->>>>>>> 5863cd70b87... gh-132106: Ensure that running `logging.handlers.QueueListener` cannot be started again (GH-132444) def test_queue_listener_with_StreamHandler(self): # Test that traceback and stack-info only appends once (bpo-34334, bpo-46755). listener = logging.handlers.QueueListener(self.queue, self.root_hdlr) @@ -4384,8 +4360,6 @@ def test_queue_listener_with_StreamHandler(self): self.assertEqual(self.stream.getvalue().strip().count('Traceback'), 1) self.assertEqual(self.stream.getvalue().strip().count('Stack'), 1) - @unittest.skipUnless(hasattr(logging.handlers, 'QueueListener'), - 'logging.handlers.QueueListener required for this test') def test_queue_listener_with_multiple_handlers(self): # Test that queue handler format doesn't affect other handler formats (bpo-35726). self.que_hdlr.setFormatter(self.root_formatter)