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
Show file tree
Hide file tree
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
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
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
Expand Up @@ -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;
Expand Down
82 changes: 76 additions & 6 deletions Python/thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
92 changes: 0 additions & 92 deletions Python/thread_nt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
Loading
Loading