diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index c9b2c7d76b6746..2cfc2f4962979f 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -504,16 +504,31 @@ Data Types .. class:: StrEnum - ``StrEnum`` is the same as :class:`Enum`, but its members are also strings and can be used - in most of the same places that a string can be used. The result of any string - operation performed on or with a *StrEnum* member is not part of the enumeration. + *StrEnum* is the same as :class:`Enum`, but its members are also strings and + can be used in most of the same places that a string can be used. The result + of any string operation performed on or with a *StrEnum* member is not part + of the enumeration. + + >>> from enum import StrEnum, auto + >>> class Color(StrEnum): + ... RED = 'r' + ... GREEN = 'g' + ... BLUE = 'b' + ... UNKNOWN = auto() + ... + >>> Color.RED + + >>> Color.UNKNOWN + + >>> str(Color.UNKNOWN) + 'unknown' .. note:: There are places in the stdlib that check for an exact :class:`str` instead of a :class:`str` subclass (i.e. ``type(unknown) == str`` instead of ``isinstance(unknown, str)``), and in those locations you - will need to use ``str(StrEnum.member)``. + will need to use ``str(MyStrEnum.MY_MEMBER)``. .. note:: diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 05bc7cfb9dc089..52f0af31c68726 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -2191,8 +2191,11 @@ always available. Unless explicitly noted otherwise, all variables are read-only .. data:: api_version - The C API version for this interpreter. Programmers may find this useful when - debugging version conflicts between Python and extension modules. + The C API version, equivalent to the C macro :c:macro:`PYTHON_API_VERSION`. + Defined for backwards compatibility. + + Currently, this constant is not updated in new Python versions, and is not + useful for versioning. This may change in the future. .. data:: version_info diff --git a/Doc/tutorial/modules.rst b/Doc/tutorial/modules.rst index 47bf7547b4ae1d..f8105cd5441fec 100644 --- a/Doc/tutorial/modules.rst +++ b/Doc/tutorial/modules.rst @@ -579,8 +579,8 @@ module for example, you might use:: from .. import formats from ..filters import equalizer -Note that relative imports are based on the name of the current module. Since -the name of the main module is always ``"__main__"``, modules intended for use +Note that relative imports are based on the name of the current module's package. +Since the main module does not have a package, modules intended for use as the main module of a Python application must always use absolute imports. 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 00000000000000..b374a98b96e6de --- /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/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 81d4e39f5be1ee..ec44a0f9ce3fb3 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2037,7 +2037,7 @@ def test_missing_override_failure(self): """ output = """ """ - with self.assertRaisesRegex(AssertionError, "All abstract uops"): + with self.assertRaisesRegex(ValueError, "All abstract uops"): self.run_cases_test(input, input2, output) def test_validate_uop_input_length_mismatch(self): 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 00000000000000..77dca4074b742f --- /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 ab20fff1509dfe..5d7fd20c4e0999 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] diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 81ae534bddae5c..ea9dd836d98e22 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -398,9 +398,9 @@ def generate_abstract_interpreter( out.emit("\n") base_uop_names = set([uop.name for uop in base.uops.values()]) for abstract_uop_name in abstract.uops: - assert ( - abstract_uop_name in base_uop_names - ), f"All abstract uops should override base uops, but {abstract_uop_name} is not." + if abstract_uop_name not in base_uop_names: + raise ValueError(f"All abstract uops should override base uops, " + "but {abstract_uop_name} is not.") for uop in base.uops.values(): override: Uop | None = None