From 3cf26a3bc945e35ab8b8cad6799b574f46056ea2 Mon Sep 17 00:00:00 2001 From: alperyoney Date: Mon, 13 Oct 2025 13:19:54 -0700 Subject: [PATCH 1/4] gh-116738: Make uuid module thread-safe --- Lib/test/test_free_threading/test_uuid.py | 60 +++++++++++++++++++ ...-10-13-13-17-06.gh-issue-116738.BY-HOW.rst | 2 + Modules/_uuidmodule.c | 6 ++ 3 files changed, 68 insertions(+) create mode 100755 Lib/test/test_free_threading/test_uuid.py create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-13-13-17-06.gh-issue-116738.BY-HOW.rst diff --git a/Lib/test/test_free_threading/test_uuid.py b/Lib/test/test_free_threading/test_uuid.py new file mode 100755 index 00000000000000..bdeec489f28ef4 --- /dev/null +++ b/Lib/test/test_free_threading/test_uuid.py @@ -0,0 +1,60 @@ +import unittest +import os + +from test.support import import_helper, threading_helper +from test.support.threading_helper import run_concurrently + +c_uuid = import_helper.import_module("_uuid") + +NTHREADS = 10 +UUID_PER_THREAD = 1000 + + +@threading_helper.requires_working_threading() +@unittest.skipUnless(c_uuid, "requires the C _uuid module") +class UuidTests(unittest.TestCase): + @unittest.skipUnless(os.name == "posix", "POSIX only") + def test_generate_time_safe(self): + uuids = [] + + def worker(): + local_uuids = [] + for _ in range(UUID_PER_THREAD): + uuid, is_safe = c_uuid.generate_time_safe() + self.assertIs(type(uuid), bytes) + self.assertEqual(len(uuid), 16) + # Collect the UUID only if it is safe. If not, we cannot ensure + # UUID uniqueness. According to uuid_generate_time_safe() man + # page, it is theoretically possible for two concurrently + # running processes to generate the same UUID(s) if the return + # value is not 0. + if is_safe == 0: + local_uuids.append(uuid) + + # Merge all safe uuids + uuids.extend(local_uuids) + + run_concurrently(worker_func=worker, nthreads=NTHREADS) + self.assertEqual(len(uuids), len(set(uuids))) + + @unittest.skipUnless(os.name == "nt", "Windows only") + def test_UuidCreate(self): + uuids = [] + + def worker(): + local_uuids = [] + for _ in range(UUID_PER_THREAD): + uuid = c_uuid.UuidCreate() + self.assertIs(type(uuid), bytes) + self.assertEqual(len(uuid), 16) + local_uuids.append(uuid) + + # Merge all uuids + uuids.extend(local_uuids) + + run_concurrently(worker_func=worker, nthreads=NTHREADS) + self.assertEqual(len(uuids), len(set(uuids))) + + +if __name__ == "__main__": + unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-13-13-17-06.gh-issue-116738.BY-HOW.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-13-13-17-06.gh-issue-116738.BY-HOW.rst new file mode 100644 index 00000000000000..02c6c184e461c0 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-13-13-17-06.gh-issue-116738.BY-HOW.rst @@ -0,0 +1,2 @@ +Make :mod:`uuid` thread-safe on the :term:`free threaded ` +build. diff --git a/Modules/_uuidmodule.c b/Modules/_uuidmodule.c index c31a7e8fea5608..d34763eb747667 100644 --- a/Modules/_uuidmodule.c +++ b/Modules/_uuidmodule.c @@ -32,11 +32,15 @@ py_uuid_generate_time_safe(PyObject *Py_UNUSED(context), #ifdef HAVE_UUID_GENERATE_TIME_SAFE int res; + Py_BEGIN_ALLOW_THREADS res = uuid_generate_time_safe(uuid); + Py_END_ALLOW_THREADS return Py_BuildValue("y#i", (const char *) uuid, sizeof(uuid), res); #elif defined(HAVE_UUID_CREATE) uint32_t status; + Py_BEGIN_ALLOW_THREADS uuid_create(&uuid, &status); + Py_END_ALLOW_THREADS # if defined(HAVE_UUID_ENC_BE) unsigned char buf[sizeof(uuid)]; uuid_enc_be(buf, &uuid); @@ -45,7 +49,9 @@ py_uuid_generate_time_safe(PyObject *Py_UNUSED(context), return Py_BuildValue("y#i", (const char *) &uuid, sizeof(uuid), (int) status); # endif /* HAVE_UUID_CREATE */ #else /* HAVE_UUID_GENERATE_TIME_SAFE */ + Py_BEGIN_ALLOW_THREADS uuid_generate_time(uuid); + Py_END_ALLOW_THREADS return Py_BuildValue("y#O", (const char *) uuid, sizeof(uuid), Py_None); #endif /* HAVE_UUID_GENERATE_TIME_SAFE */ } From 5dab3ed6049ce4a55581a68aafb6648ab869ac81 Mon Sep 17 00:00:00 2001 From: alperyoney Date: Tue, 14 Oct 2025 20:17:03 -0700 Subject: [PATCH 2/4] gh-116738: Remove Py_{BEGIN,END}_ALLOW_THREADS --- .../2025-10-13-13-17-06.gh-issue-116738.BY-HOW.rst | 2 -- Modules/_uuidmodule.c | 6 ------ 2 files changed, 8 deletions(-) delete mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-13-13-17-06.gh-issue-116738.BY-HOW.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-13-13-17-06.gh-issue-116738.BY-HOW.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-13-13-17-06.gh-issue-116738.BY-HOW.rst deleted file mode 100644 index 02c6c184e461c0..00000000000000 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-13-13-17-06.gh-issue-116738.BY-HOW.rst +++ /dev/null @@ -1,2 +0,0 @@ -Make :mod:`uuid` thread-safe on the :term:`free threaded ` -build. diff --git a/Modules/_uuidmodule.c b/Modules/_uuidmodule.c index d34763eb747667..c31a7e8fea5608 100644 --- a/Modules/_uuidmodule.c +++ b/Modules/_uuidmodule.c @@ -32,15 +32,11 @@ py_uuid_generate_time_safe(PyObject *Py_UNUSED(context), #ifdef HAVE_UUID_GENERATE_TIME_SAFE int res; - Py_BEGIN_ALLOW_THREADS res = uuid_generate_time_safe(uuid); - Py_END_ALLOW_THREADS return Py_BuildValue("y#i", (const char *) uuid, sizeof(uuid), res); #elif defined(HAVE_UUID_CREATE) uint32_t status; - Py_BEGIN_ALLOW_THREADS uuid_create(&uuid, &status); - Py_END_ALLOW_THREADS # if defined(HAVE_UUID_ENC_BE) unsigned char buf[sizeof(uuid)]; uuid_enc_be(buf, &uuid); @@ -49,9 +45,7 @@ py_uuid_generate_time_safe(PyObject *Py_UNUSED(context), return Py_BuildValue("y#i", (const char *) &uuid, sizeof(uuid), (int) status); # endif /* HAVE_UUID_CREATE */ #else /* HAVE_UUID_GENERATE_TIME_SAFE */ - Py_BEGIN_ALLOW_THREADS uuid_generate_time(uuid); - Py_END_ALLOW_THREADS return Py_BuildValue("y#O", (const char *) uuid, sizeof(uuid), Py_None); #endif /* HAVE_UUID_GENERATE_TIME_SAFE */ } From 442c0829142cb15e78c985621968f5e767862475 Mon Sep 17 00:00:00 2001 From: alperyoney Date: Tue, 14 Oct 2025 20:44:07 -0700 Subject: [PATCH 3/4] gh-116738: Remove redundant skipUnless --- Lib/test/test_free_threading/test_uuid.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_free_threading/test_uuid.py b/Lib/test/test_free_threading/test_uuid.py index bdeec489f28ef4..48a005d3d13437 100755 --- a/Lib/test/test_free_threading/test_uuid.py +++ b/Lib/test/test_free_threading/test_uuid.py @@ -11,8 +11,7 @@ @threading_helper.requires_working_threading() -@unittest.skipUnless(c_uuid, "requires the C _uuid module") -class UuidTests(unittest.TestCase): +class UUIDTests(unittest.TestCase): @unittest.skipUnless(os.name == "posix", "POSIX only") def test_generate_time_safe(self): uuids = [] From e73b23d99e713b55d304c39c035d25236cbfe748 Mon Sep 17 00:00:00 2001 From: alperyoney Date: Wed, 15 Oct 2025 09:15:19 -0700 Subject: [PATCH 4/4] gh-116738: Improve readability --- Lib/test/test_free_threading/test_uuid.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_free_threading/test_uuid.py b/Lib/test/test_free_threading/test_uuid.py index 48a005d3d13437..d794afc552a652 100755 --- a/Lib/test/test_free_threading/test_uuid.py +++ b/Lib/test/test_free_threading/test_uuid.py @@ -1,8 +1,9 @@ -import unittest import os +import unittest from test.support import import_helper, threading_helper from test.support.threading_helper import run_concurrently +from uuid import SafeUUID c_uuid = import_helper.import_module("_uuid") @@ -27,7 +28,7 @@ def worker(): # page, it is theoretically possible for two concurrently # running processes to generate the same UUID(s) if the return # value is not 0. - if is_safe == 0: + if is_safe == SafeUUID.safe: local_uuids.append(uuid) # Merge all safe uuids