Skip to content

Commit ebf6d13

Browse files
vstinnercolesbury
andauthored
gh-134745: Change PyThread_allocate_lock() implementation to PyMutex (#134747)
Co-authored-by: Sam Gross <[email protected]>
1 parent 45c6c48 commit ebf6d13

File tree

7 files changed

+86
-491
lines changed

7 files changed

+86
-491
lines changed

Include/internal/pycore_lock.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ typedef enum _PyLockFlags {
4848

4949
// Handle signals if interrupted while waiting on the lock.
5050
_PY_LOCK_HANDLE_SIGNALS = 2,
51+
52+
// Fail if interrupted by a signal while waiting on the lock.
53+
_PY_FAIL_IF_INTERRUPTED = 4,
5154
} _PyLockFlags;
5255

5356
// Lock a mutex with an optional timeout and additional options. See

Lib/test/test_sys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,7 @@ def test_thread_info(self):
729729
info = sys.thread_info
730730
self.assertEqual(len(info), 3)
731731
self.assertIn(info.name, ('nt', 'pthread', 'pthread-stubs', 'solaris', None))
732-
self.assertIn(info.lock, ('semaphore', 'mutex+cond', None))
732+
self.assertIn(info.lock, ('pymutex', None))
733733
if sys.platform.startswith(("linux", "android", "freebsd")):
734734
self.assertEqual(info.name, "pthread")
735735
elif sys.platform == "win32":
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Change :c:func:`!PyThread_allocate_lock` implementation to ``PyMutex``.
2+
On Windows, :c:func:`!PyThread_acquire_lock_timed` now supports the *intr_flag*
3+
parameter: it can be interrupted. Patch by Victor Stinner.

Python/lock.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
119119
return PY_LOCK_INTR;
120120
}
121121
}
122+
else if (ret == Py_PARK_INTR && (flags & _PY_FAIL_IF_INTERRUPTED)) {
123+
return PY_LOCK_INTR;
124+
}
122125
else if (ret == Py_PARK_TIMEOUT) {
123126
assert(timeout >= 0);
124127
return PY_LOCK_FAILURE;

Python/thread.c

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
const long long PY_TIMEOUT_MAX = PY_TIMEOUT_MAX_VALUE;
4040

4141

42-
static void PyThread__init_thread(void); /* Forward */
42+
/* Forward declaration */
43+
static void PyThread__init_thread(void);
4344

4445
#define initialized _PyRuntime.threads.initialized
4546

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

7374

75+
/*
76+
* Lock support.
77+
*/
78+
79+
PyThread_type_lock
80+
PyThread_allocate_lock(void)
81+
{
82+
if (!initialized) {
83+
PyThread_init_thread();
84+
}
85+
86+
PyMutex *lock = (PyMutex *)PyMem_RawMalloc(sizeof(PyMutex));
87+
if (lock) {
88+
*lock = (PyMutex){0};
89+
}
90+
91+
return (PyThread_type_lock)lock;
92+
}
93+
94+
void
95+
PyThread_free_lock(PyThread_type_lock lock)
96+
{
97+
PyMem_RawFree(lock);
98+
}
99+
100+
PyLockStatus
101+
PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds,
102+
int intr_flag)
103+
{
104+
PyTime_t timeout; // relative timeout
105+
if (microseconds >= 0) {
106+
// bpo-41710: PyThread_acquire_lock_timed() cannot report timeout
107+
// overflow to the caller, so clamp the timeout to
108+
// [PyTime_MIN, PyTime_MAX].
109+
//
110+
// PyTime_MAX nanoseconds is around 292.3 years.
111+
//
112+
// _thread.Lock.acquire() and _thread.RLock.acquire() raise an
113+
// OverflowError if microseconds is greater than PY_TIMEOUT_MAX.
114+
timeout = _PyTime_FromMicrosecondsClamp(microseconds);
115+
}
116+
else {
117+
timeout = -1;
118+
}
119+
120+
_PyLockFlags flags = _Py_LOCK_DONT_DETACH;
121+
if (intr_flag) {
122+
flags |= _PY_FAIL_IF_INTERRUPTED;
123+
}
124+
125+
return _PyMutex_LockTimed((PyMutex *)lock, timeout, flags);
126+
}
127+
128+
void
129+
PyThread_release_lock(PyThread_type_lock lock)
130+
{
131+
PyMutex_Unlock((PyMutex *)lock);
132+
}
133+
134+
int
135+
_PyThread_at_fork_reinit(PyThread_type_lock *lock)
136+
{
137+
_PyMutex_at_fork_reinit((PyMutex *)lock);
138+
return 0;
139+
}
140+
141+
int
142+
PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
143+
{
144+
return PyThread_acquire_lock_timed(lock, waitflag ? -1 : 0, /*intr_flag=*/0);
145+
}
146+
147+
74148
/* return the current thread stack size */
75149
size_t
76150
PyThread_get_stacksize(void)
@@ -261,11 +335,7 @@ PyThread_GetInfo(void)
261335
#ifdef HAVE_PTHREAD_STUBS
262336
value = Py_NewRef(Py_None);
263337
#elif defined(_POSIX_THREADS)
264-
#ifdef USE_SEMAPHORES
265-
value = PyUnicode_FromString("semaphore");
266-
#else
267-
value = PyUnicode_FromString("mutex+cond");
268-
#endif
338+
value = PyUnicode_FromString("pymutex");
269339
if (value == NULL) {
270340
Py_DECREF(threadinfo);
271341
return NULL;

Python/thread_nt.h

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -300,98 +300,6 @@ PyThread_hang_thread(void)
300300
}
301301
}
302302

303-
/*
304-
* Lock support. It has to be implemented as semaphores.
305-
* I [Dag] tried to implement it with mutex but I could find a way to
306-
* tell whether a thread already own the lock or not.
307-
*/
308-
PyThread_type_lock
309-
PyThread_allocate_lock(void)
310-
{
311-
PNRMUTEX mutex;
312-
313-
if (!initialized)
314-
PyThread_init_thread();
315-
316-
mutex = AllocNonRecursiveMutex() ;
317-
318-
PyThread_type_lock aLock = (PyThread_type_lock) mutex;
319-
assert(aLock);
320-
321-
return aLock;
322-
}
323-
324-
void
325-
PyThread_free_lock(PyThread_type_lock aLock)
326-
{
327-
FreeNonRecursiveMutex(aLock) ;
328-
}
329-
330-
// WaitForSingleObject() accepts timeout in milliseconds in the range
331-
// [0; 0xFFFFFFFE] (DWORD type). INFINITE value (0xFFFFFFFF) means no
332-
// timeout. 0xFFFFFFFE milliseconds is around 49.7 days.
333-
const DWORD TIMEOUT_MS_MAX = 0xFFFFFFFE;
334-
335-
/*
336-
* Return 1 on success if the lock was acquired
337-
*
338-
* and 0 if the lock was not acquired. This means a 0 is returned
339-
* if the lock has already been acquired by this thread!
340-
*/
341-
PyLockStatus
342-
PyThread_acquire_lock_timed(PyThread_type_lock aLock,
343-
PY_TIMEOUT_T microseconds, int intr_flag)
344-
{
345-
assert(aLock);
346-
347-
/* Fow now, intr_flag does nothing on Windows, and lock acquires are
348-
* uninterruptible. */
349-
PyLockStatus success;
350-
PY_TIMEOUT_T milliseconds;
351-
352-
if (microseconds >= 0) {
353-
milliseconds = microseconds / 1000;
354-
// Round milliseconds away from zero
355-
if (microseconds % 1000 > 0) {
356-
milliseconds++;
357-
}
358-
if (milliseconds > (PY_TIMEOUT_T)TIMEOUT_MS_MAX) {
359-
// bpo-41710: PyThread_acquire_lock_timed() cannot report timeout
360-
// overflow to the caller, so clamp the timeout to
361-
// [0, TIMEOUT_MS_MAX] milliseconds.
362-
//
363-
// _thread.Lock.acquire() and _thread.RLock.acquire() raise an
364-
// OverflowError if microseconds is greater than PY_TIMEOUT_MAX.
365-
milliseconds = TIMEOUT_MS_MAX;
366-
}
367-
assert(milliseconds != INFINITE);
368-
}
369-
else {
370-
milliseconds = INFINITE;
371-
}
372-
373-
if (EnterNonRecursiveMutex((PNRMUTEX)aLock,
374-
(DWORD)milliseconds) == WAIT_OBJECT_0) {
375-
success = PY_LOCK_ACQUIRED;
376-
}
377-
else {
378-
success = PY_LOCK_FAILURE;
379-
}
380-
381-
return success;
382-
}
383-
int
384-
PyThread_acquire_lock(PyThread_type_lock aLock, int waitflag)
385-
{
386-
return PyThread_acquire_lock_timed(aLock, waitflag ? -1 : 0, 0);
387-
}
388-
389-
void
390-
PyThread_release_lock(PyThread_type_lock aLock)
391-
{
392-
assert(aLock);
393-
(void)LeaveNonRecursiveMutex((PNRMUTEX) aLock);
394-
}
395303

396304
/* minimum/maximum thread stack sizes supported */
397305
#define THREAD_MIN_STACKSIZE 0x8000 /* 32 KiB */

0 commit comments

Comments
 (0)