From 0f7e1b011024b19b5ec755cc3302032bc5a36008 Mon Sep 17 00:00:00 2001 From: Jib Date: Mon, 5 Feb 2024 08:46:32 -0500 Subject: [PATCH] PYTHON-4147: Silence noisy thread.start() RuntimeError at shutdown (#1486) --- doc/changelog.rst | 3 +++ pymongo/periodic_executor.py | 11 ++++++++++- test/test_monitor.py | 12 ++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index c5fd8758dd..3feae4c2a4 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -29,6 +29,9 @@ PyMongo 4.7 brings a number of improvements including: .. _orjson: https://github.com/ijl/orjson +- Fixed a bug appearing in Python 3.12 where "RuntimeError: can't create new thread at interpreter shutdown" + could be written to stderr when a MongoClient's thread starts as the python interpreter is shutting down. + Changes in Version 4.6.1 ------------------------ diff --git a/pymongo/periodic_executor.py b/pymongo/periodic_executor.py index 25d519187f..9e9ead61fc 100644 --- a/pymongo/periodic_executor.py +++ b/pymongo/periodic_executor.py @@ -16,6 +16,7 @@ from __future__ import annotations +import sys import threading import time import weakref @@ -91,7 +92,15 @@ def open(self) -> None: thread.daemon = True self._thread = weakref.proxy(thread) _register_executor(self) - thread.start() + # Mitigation to RuntimeError firing when thread starts on shutdown + # https://github.com/python/cpython/issues/114570 + try: + thread.start() + except RuntimeError as e: + if "interpreter shutdown" in str(e) or sys.is_finalizing(): + self._thread = None + return + raise def close(self, dummy: Any = None) -> None: """Stop. To restart, call open(). diff --git a/test/test_monitor.py b/test/test_monitor.py index 0495a8cbc7..8ccec7fd0b 100644 --- a/test/test_monitor.py +++ b/test/test_monitor.py @@ -16,6 +16,7 @@ from __future__ import annotations import gc +import subprocess import sys from functools import partial @@ -79,6 +80,17 @@ def test_cleanup_executors_on_client_close(self): for executor in executors: wait_until(lambda: executor._stopped, f"closed executor: {executor._name}", timeout=5) + def test_no_thread_start_runtime_err_on_shutdown(self): + """Test we silence noisy runtime errors fired when the MongoClient spawns a new thread + on process shutdown.""" + command = [sys.executable, "-c", "'from pymongo import MongoClient; c = MongoClient()'"] + completed_process: subprocess.CompletedProcess = subprocess.run( + " ".join(command), shell=True, capture_output=True + ) + + self.assertFalse(completed_process.stderr) + self.assertFalse(completed_process.stdout) + if __name__ == "__main__": unittest.main()