From 65893c6f9c69562526d24fe8265119feeea54cbf Mon Sep 17 00:00:00 2001 From: Alper Date: Mon, 21 Jul 2025 09:24:42 -0700 Subject: [PATCH] gh-116738: Make syslog module thread-safe (#136760) Make the setlogmask() function in the syslog module thread-safe. These changes are relevant for scenarios where the GIL is disabled or when using subinterpreters. --- Lib/test/test_free_threading/test_syslog.py | 44 +++++++++++++++++++ ...-07-18-08-43-35.gh-issue-116738.i0HWtP.rst | 2 + Modules/syslogmodule.c | 8 +++- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 Lib/test/test_free_threading/test_syslog.py create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-07-18-08-43-35.gh-issue-116738.i0HWtP.rst diff --git a/Lib/test/test_free_threading/test_syslog.py b/Lib/test/test_free_threading/test_syslog.py new file mode 100644 index 0000000000..b374a98b96 --- /dev/null +++ b/Lib/test/test_free_threading/test_syslog.py @@ -0,0 +1,44 @@ +import unittest +import threading + +from test.support import import_helper, threading_helper +from test.support.threading_helper import run_concurrently + +syslog = import_helper.import_module("syslog") + +NTHREADS = 32 + +# Similar to Lib/test/test_syslog.py, this test's purpose is to verify that +# the code neither crashes nor leaks. + + +@threading_helper.requires_working_threading() +class TestSyslog(unittest.TestCase): + def test_racing_syslog(self): + def worker(): + """ + The syslog module provides the following functions: + openlog(), syslog(), closelog(), and setlogmask(). + """ + thread_id = threading.get_ident() + syslog.openlog(f"thread-id: {thread_id}") + try: + for _ in range(5): + syslog.syslog("logline") + syslog.setlogmask(syslog.LOG_MASK(syslog.LOG_INFO)) + syslog.syslog(syslog.LOG_INFO, "logline LOG_INFO") + syslog.setlogmask(syslog.LOG_MASK(syslog.LOG_ERR)) + syslog.syslog(syslog.LOG_ERR, "logline LOG_ERR") + syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG)) + finally: + syslog.closelog() + + # Run the worker concurrently to exercise all these syslog functions + run_concurrently( + worker_func=worker, + nthreads=NTHREADS, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-07-18-08-43-35.gh-issue-116738.i0HWtP.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-18-08-43-35.gh-issue-116738.i0HWtP.rst new file mode 100644 index 0000000000..77dca4074b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-18-08-43-35.gh-issue-116738.i0HWtP.rst @@ -0,0 +1,2 @@ +Make functions in :mod:`syslog` thread-safe on the :term:`free threaded +` build. diff --git a/Modules/syslogmodule.c b/Modules/syslogmodule.c index ab20fff150..5d7fd20c4e 100644 --- a/Modules/syslogmodule.c +++ b/Modules/syslogmodule.c @@ -298,7 +298,13 @@ syslog_setlogmask_impl(PyObject *module, long maskpri) return -1; } - return setlogmask(maskpri); + static PyMutex setlogmask_mutex = {0}; + PyMutex_Lock(&setlogmask_mutex); + // Linux man page (3): setlogmask() is MT-Unsafe race:LogMask. + long previous_mask = setlogmask(maskpri); + PyMutex_Unlock(&setlogmask_mutex); + + return previous_mask; } /*[clinic input]