Skip to content

gh-134745: Change PyThread_allocate_lock() implementation to PyMutex #134747

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 30, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Include/internal/pycore_lock.h
Original file line number Diff line number Diff line change
@@ -48,6 +48,9 @@ typedef enum _PyLockFlags {

/ Handle signals if interrupted while waiting on the lock.
_PY_LOCK_HANDLE_SIGNALS = 2,

/ Fail if interrupted by a signal while waiting on the lock.
_PY_FAIL_IF_INTERRUPTED = 4,
} _PyLockFlags;

/ Lock a mutex with an optional timeout and additional options. See
2 changes: 1 addition & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
@@ -729,7 +729,7 @@ def test_thread_info(self):
info = sys.thread_info
self.assertEqual(len(info), 3)
self.assertIn(info.name, ('nt', 'pthread', 'pthread-stubs', 'solaris', None))
self.assertIn(info.lock, ('semaphore', 'mutex+cond', None))
self.assertIn(info.lock, ('pymutex', None))
if sys.platform.startswith(("linux", "android", "freebsd")):
self.assertEqual(info.name, "pthread")
elif sys.platform == "win32":
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Change :c:func:`!PyThread_allocate_lock` implementation to ``PyMutex``.
On Windows, :c:func:`!PyThread_acquire_lock_timed` now supports the *intr_flag*
parameter: it can be interrupted. Patch by Victor Stinner.
3 changes: 3 additions & 0 deletions Python/lock.c
Original file line number Diff line number Diff line change
@@ -119,6 +119,9 @@ _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
return PY_LOCK_INTR;
}
}
else if (ret == Py_PARK_INTR && (flags & _PY_FAIL_IF_INTERRUPTED)) {
return PY_LOCK_INTR;
}
else if (ret == Py_PARK_TIMEOUT) {
assert(timeout >= 0);
return PY_LOCK_FAILURE;
82 changes: 76 additions & 6 deletions Python/thread.c
Original file line number Diff line number Diff line change
@@ -39,7 +39,8 @@
const long long PY_TIMEOUT_MAX = PY_TIMEOUT_MAX_VALUE;


static void PyThread__init_thread(void); /* Forward */
/* Forward declaration */
static void PyThread__init_thread(void);

#define initialized _PyRuntime.threads.initialized

@@ -71,6 +72,79 @@ PyThread_init_thread(void)
#endif


/*
* Lock support.
*/

PyThread_type_lock
PyThread_allocate_lock(void)
{
if (!initialized) {
PyThread_init_thread();
}

PyMutex *lock = (PyMutex *)PyMem_RawMalloc(sizeof(PyMutex));
if (lock) {
*lock = (PyMutex){0};
}

return (PyThread_type_lock)lock;
}

void
PyThread_free_lock(PyThread_type_lock lock)
{
PyMem_RawFree(lock);
}

PyLockStatus
PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
int intr_flag)
{
PyTime_t timeout; / relative timeout
if (microseconds >= 0) {
/ bpo-41710: PyThread_acquire_lock_timed() cannot report timeout
/ overflow to the caller, so clamp the timeout to
/ [PyTime_MIN, PyTime_MAX].
/
/ PyTime_MAX nanoseconds is around 292.3 years.
/
/ _thread.Lock.acquire() and _thread.RLock.acquire() raise an
/ OverflowError if microseconds is greater than PY_TIMEOUT_MAX.
timeout = _PyTime_FromMicrosecondsClamp(microseconds);
}
else {
timeout = -1;
}

_PyLockFlags flags = _Py_LOCK_DONT_DETACH;
if (intr_flag) {
flags |= _PY_FAIL_IF_INTERRUPTED;
}

return _PyMutex_LockTimed((PyMutex *)lock, timeout, flags);
}

void
PyThread_release_lock(PyThread_type_lock lock)
{
PyMutex_Unlock((PyMutex *)lock);
}

int
_PyThread_at_fork_reinit(PyThread_type_lock *lock)
{
_PyMutex_at_fork_reinit((PyMutex *)lock);
return 0;
}

int
PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
{
return PyThread_acquire_lock_timed(lock, waitflag ? -1 : 0, /*intr_flag=*/0);
}


/* return the current thread stack size */
size_t
PyThread_get_stacksize(void)
@@ -261,11 +335,7 @@ PyThread_GetInfo(void)
#ifdef HAVE_PTHREAD_STUBS
value = Py_NewRef(Py_None);
#elif defined(_POSIX_THREADS)
#ifdef USE_SEMAPHORES
value = PyUnicode_FromString("semaphore");
#else
value = PyUnicode_FromString("mutex+cond");
#endif
value = PyUnicode_FromString("pymutex");
if (value == NULL) {
Py_DECREF(threadinfo);
return NULL;
92 changes: 0 additions & 92 deletions Python/thread_nt.h
Original file line number Diff line number Diff line change
@@ -300,98 +300,6 @@ PyThread_hang_thread(void)
}
}

/*
* Lock support. It has to be implemented as semaphores.
* I [Dag] tried to implement it with mutex but I could find a way to
* tell whether a thread already own the lock or not.
*/
PyThread_type_lock
PyThread_allocate_lock(void)
{
PNRMUTEX mutex;

if (!initialized)
PyThread_init_thread();

mutex = AllocNonRecursiveMutex() ;

PyThread_type_lock aLock = (PyThread_type_lock) mutex;
assert(aLock);

return aLock;
}

void
PyThread_free_lock(PyThread_type_lock aLock)
{
FreeNonRecursiveMutex(aLock) ;
}

/ WaitForSingleObject() accepts timeout in milliseconds in the range
/ [0; 0xFFFFFFFE] (DWORD type). INFINITE value (0xFFFFFFFF) means no
/ timeout. 0xFFFFFFFE milliseconds is around 49.7 days.
const DWORD TIMEOUT_MS_MAX = 0xFFFFFFFE;

/*
* Return 1 on success if the lock was acquired
*
* and 0 if the lock was not acquired. This means a 0 is returned
* if the lock has already been acquired by this thread!
*/
PyLockStatus
PyThread_acquire_lock_timed(PyThread_type_lock aLock,
PY_TIMEOUT_T microseconds, int intr_flag)
{
assert(aLock);

/* Fow now, intr_flag does nothing on Windows, and lock acquires are
* uninterruptible. */
PyLockStatus success;
PY_TIMEOUT_T milliseconds;

if (microseconds >= 0) {
milliseconds = microseconds / 1000;
/ Round milliseconds away from zero
if (microseconds % 1000 > 0) {
milliseconds++;
}
if (milliseconds > (PY_TIMEOUT_T)TIMEOUT_MS_MAX) {
/ bpo-41710: PyThread_acquire_lock_timed() cannot report timeout
/ overflow to the caller, so clamp the timeout to
/ [0, TIMEOUT_MS_MAX] milliseconds.
/
/ _thread.Lock.acquire() and _thread.RLock.acquire() raise an
/ OverflowError if microseconds is greater than PY_TIMEOUT_MAX.
milliseconds = TIMEOUT_MS_MAX;
}
assert(milliseconds != INFINITE);
}
else {
milliseconds = INFINITE;
}

if (EnterNonRecursiveMutex((PNRMUTEX)aLock,
(DWORD)milliseconds) == WAIT_OBJECT_0) {
success = PY_LOCK_ACQUIRED;
}
else {
success = PY_LOCK_FAILURE;
}

return success;
}
int
PyThread_acquire_lock(PyThread_type_lock aLock, int waitflag)
{
return PyThread_acquire_lock_timed(aLock, waitflag ? -1 : 0, 0);
}

void
PyThread_release_lock(PyThread_type_lock aLock)
{
assert(aLock);
(void)LeaveNonRecursiveMutex((PNRMUTEX) aLock);
}

/* minimum/maximum thread stack sizes supported */
#define THREAD_MIN_STACKSIZE 0x8000 /* 32 KiB */
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.

Follow Lee on X/Twitter - Father, Husband, Serial builder creating AI, crypto, games & web tools. We are friends :) AI Will Come To Life!

Check out: eBank.nz (Art Generator) | Netwrck.com (AI Tools) | Text-Generator.io (AI API) | BitBank.nz (Crypto AI) | ReadingTime (Kids Reading) | RewordGame | BigMultiplayerChess | WebFiddle | How.nz | Helix AI Assistant