cpython: 01d1fd775d16 (original) (raw)
Mercurial > cpython
changeset 60598:01d1fd775d16
Issue #7316: the acquire() method of lock objects in the :mod:`threading` module now takes an optional timeout argument in seconds. Timeout support relies on the system threading library, so as to avoid a semi-busy wait loop. [#7316]
Antoine Pitrou solipsis@pitrou.net | |
---|---|
date | Wed, 14 Apr 2010 15:44:10 +0000 |
parents | 62d11785204f |
children | ca11c830f5cc |
files | Doc/library/_thread.rst Doc/library/threading.rst Include/pythread.h Lib/_dummy_thread.py Lib/multiprocessing/pool.py Lib/test/lock_tests.py Lib/threading.py Misc/NEWS Modules/_threadmodule.c Python/thread_nt.h Python/thread_pthread.h |
diffstat | 11 files changed, 327 insertions(+), 78 deletions(-)[+] [-] Doc/library/_thread.rst 31 Doc/library/threading.rst 28 Include/pythread.h 35 Lib/_dummy_thread.py 8 Lib/multiprocessing/pool.py 4 Lib/test/lock_tests.py 44 Lib/threading.py 25 Misc/NEWS 5 Modules/_threadmodule.c 89 Python/thread_nt.h 35 Python/thread_pthread.h 101 |
line wrap: on
line diff
--- a/Doc/library/_thread.rst
+++ b/Doc/library/_thread.rst
@@ -28,7 +28,7 @@ implementation. For systems lacking the
:mod:_dummy_thread
module is available. It duplicates this module's interface
and can be used as a drop-in replacement.
-It defines the following constant and functions:
+It defines the following constants and functions:
.. exception:: error
@@ -103,19 +103,34 @@ It defines the following constant and fu
Availability: Windows, systems with POSIX threads.
+.. data:: TIMEOUT_MAX
+
- The maximum value allowed for the timeout parameter of
- :meth:
Lock.acquire
. Specifiying a timeout greater than this value will - raise an :exc:
OverflowError
. +
+ Lock objects have the following methods: -.. method:: lock.acquire([waitflag]) +.. method:: lock.acquire(waitflag=1, timeout=-1)
- Without any optional argument, this method acquires the lock unconditionally, if necessary waiting until it is released by another thread (only one thread at a
- time can acquire a lock --- that's their reason for existence). If the integer
- waitflag argument is present, the action depends on its value: if it is zero,
- the lock is only acquired if it can be acquired immediately without waiting,
- while if it is nonzero, the lock is acquired unconditionally as before. The
- return value is
True
if the lock is acquired successfully,False
if not.
- time can acquire a lock --- that's their reason for existence). +
- If the integer waitflag argument is present, the action depends on its
- value: if it is zero, the lock is only acquired if it can be acquired
- immediately without waiting, while if it is nonzero, the lock is acquired
- unconditionally as above.
- If the floating-point timeout argument is present and positive, it
- specifies the maximum wait time in seconds before returning. A negative
- timeout argument specifies an unbounded wait. You cannot specify
- a timeout if waitflag is zero. +
- The return value is
True
if the lock is acquired successfully, False
if not.
--- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -155,6 +155,16 @@ This module defines the following functi Availability: Windows, systems with POSIX threads. +This module also defines the following constant: + +.. data:: TIMEOUT_MAX +
- The maximum value allowed for the timeout parameter of blocking functions
- (:meth:
Lock.acquire
, :meth:RLock.acquire
, :meth:Condition.wait
, etc.). - Specifiying a timeout greater than this value will raise an
- :exc:
OverflowError
. +
+ Detailed interfaces for the objects are documented below. The design of this module is loosely based on Java's threading model. However, @@ -349,7 +359,7 @@ and may vary across implementations. All methods are executed atomically. -.. method:: Lock.acquire(blocking=True) +.. method:: Lock.acquire(blocking=True, timeout=-1) Acquire a lock, blocking or non-blocking. @@ -363,6 +373,15 @@ All methods are executed atomically. without an argument would block, return false immediately; otherwise, do the same thing as when called without arguments, and return true.
- When invoked with the floating-point timeout argument set to a positive
- value, block for at most the number of seconds specified by timeout
- and as long as the lock cannot be acquired. A negative timeout argument
- specifies an unbounded wait. It is forbidden to specify a timeout
- when blocking is false. +
- The return value is
True
if the lock is acquired successfully, False
if not (for example if the timeout expired). +
.. method:: Lock.release()
@@ -396,7 +415,7 @@ pair) resets the lock to unlocked and al
:meth:acquire
to proceed.
-.. method:: RLock.acquire(blocking=True)
+.. method:: RLock.acquire(blocking=True, timeout=-1)
Acquire a lock, blocking or non-blocking.
@@ -415,6 +434,11 @@ pair) resets the lock to unlocked and al
without an argument would block, return false immediately; otherwise, do the
same thing as when called without arguments, and return true.
- When invoked with the floating-point timeout argument set to a positive
- value, block for at most the number of seconds specified by timeout
- and as long as the lock cannot be acquired. Return true if the lock has
- been acquired, false if the timeout has elapsed. +
--- a/Include/pythread.h +++ b/Include/pythread.h @@ -19,6 +19,41 @@ PyAPI_FUNC(void) PyThread_free_lock(PyTh PyAPI_FUNC(int) PyThread_acquire_lock(PyThread_type_lock, int); #define WAIT_LOCK 1 #define NOWAIT_LOCK 0 + +/* PY_TIMEOUT_T is the integral type used to specify timeouts when waiting
- on a lock (see PyThread_acquire_lock_timed() below).
- PY_TIMEOUT_MAX is the highest usable value (in microseconds) of that
- type, and depends on the system threading API.
- NOTE: this isn't the same value as
_thread.TIMEOUT_MAX
. The _thread - module exposes a higher-level API, with timeouts expressed in seconds
- and floating-point numbers allowed. +*/ +#if defined(HAVE_LONG_LONG) +#define PY_TIMEOUT_T PY_LONG_LONG +#define PY_TIMEOUT_MAX PY_LLONG_MAX +#else +#define PY_TIMEOUT_T long +#define PY_TIMEOUT_MAX LONG_MAX +#endif +
+/* In the NT API, the timeout is a DWORD and is expressed in milliseconds / +#if defined (NT_THREADS) +#if (0xFFFFFFFFLL * 1000 < PY_TIMEOUT_MAX) +#undef PY_TIMEOUT_MAX +#define PY_TIMEOUT_MAX (0xFFFFFFFFLL * 1000) +#endif +#endif + +/ If microseconds == 0, the call is non-blocking: it returns immediately
- even when the lock can't be acquired.
- If microseconds > 0, the call waits up to the specified duration.
- If microseconds < 0, the call waits until success (or abnormal failure)
- microseconds must be less than PY_TIMEOUT_MAX. Behaviour otherwise is
- undefined. */ +PyAPI_FUNC(int) PyThread_acquire_lock_timed(PyThread_type_lock,
PyAPI_FUNC(void) PyThread_release_lock(PyThread_type_lock); PyAPI_FUNC(size_t) PyThread_get_stacksize(void);PY_TIMEOUT_T microseconds);[](#l3.41)
--- a/Lib/_dummy_thread.py +++ b/Lib/_dummy_thread.py @@ -17,6 +17,10 @@ Suggested usage is:: 'interrupt_main', 'LockType'] import traceback as _traceback +import time + +# A dummy value +TIMEOUT_MAX = 2**31 class error(Exception): """Dummy implementation of _thread.error.""" @@ -92,7 +96,7 @@ class LockType(object): def init(self): self.locked_status = False
For blocking calls, self.locked_status is automatically set to @@ -111,6 +115,8 @@ class LockType(object): self.locked_status = True return True else:
if timeout > 0:[](#l4.27)
time.sleep(timeout)[](#l4.28) return False[](#l4.29)
--- a/Lib/multiprocessing/pool.py +++ b/Lib/multiprocessing/pool.py @@ -440,10 +440,10 @@ class Pool(object): p.terminate() debug('joining task handler')
task_handler.join(1e100)[](#l5.7)
task_handler.join()[](#l5.8)
debug('joining result handler')
result_handler.join(1e100)[](#l5.11)
task_handler.join()[](#l5.12)
if pool and hasattr(pool[0], 'terminate'): debug('joining pool workers')
--- a/Lib/test/lock_tests.py +++ b/Lib/test/lock_tests.py @@ -4,7 +4,7 @@ Various tests for synchronization primit import sys import time -from _thread import start_new_thread, get_ident +from _thread import start_new_thread, get_ident, TIMEOUT_MAX import threading import unittest @@ -62,6 +62,14 @@ class BaseTestCase(unittest.TestCase): support.threading_cleanup(*self._threads) support.reap_children()
- def assertTimeout(self, actual, expected):
# The waiting and/or time.time() can be imprecise, which[](#l6.17)
# is why comparing to the expected value would sometimes fail[](#l6.18)
# (especially under Windows).[](#l6.19)
self.assertGreaterEqual(actual, expected * 0.6)[](#l6.20)
# Test nothing insane happened[](#l6.21)
self.assertLess(actual, expected * 10.0)[](#l6.22)
+ class BaseLockTests(BaseTestCase): """ @@ -143,6 +151,32 @@ class BaseLockTests(BaseTestCase): Bunch(f, 15).wait_for_finished() self.assertEqual(n, len(threading.enumerate()))
- def test_timeout(self):
lock = self.locktype()[](#l6.32)
# Can't set timeout if not blocking[](#l6.33)
self.assertRaises(ValueError, lock.acquire, 0, 1)[](#l6.34)
# Invalid timeout values[](#l6.35)
self.assertRaises(ValueError, lock.acquire, timeout=-100)[](#l6.36)
self.assertRaises(OverflowError, lock.acquire, timeout=1e100)[](#l6.37)
self.assertRaises(OverflowError, lock.acquire, timeout=TIMEOUT_MAX + 1)[](#l6.38)
# TIMEOUT_MAX is ok[](#l6.39)
lock.acquire(timeout=TIMEOUT_MAX)[](#l6.40)
lock.release()[](#l6.41)
t1 = time.time()[](#l6.42)
self.assertTrue(lock.acquire(timeout=5))[](#l6.43)
t2 = time.time()[](#l6.44)
# Just a sanity test that it didn't actually wait for the timeout.[](#l6.45)
self.assertLess(t2 - t1, 5)[](#l6.46)
results = [][](#l6.47)
def f():[](#l6.48)
t1 = time.time()[](#l6.49)
results.append(lock.acquire(timeout=0.5))[](#l6.50)
t2 = time.time()[](#l6.51)
results.append(t2 - t1)[](#l6.52)
Bunch(f, 1).wait_for_finished()[](#l6.53)
self.assertFalse(results[0])[](#l6.54)
self.assertTimeout(results[1], 0.5)[](#l6.55)
+ class LockTests(BaseLockTests): """ @@ -284,14 +318,14 @@ class EventTests(BaseTestCase): def f(): results1.append(evt.wait(0.0)) t1 = time.time()
r = evt.wait(0.2)[](#l6.64)
r = evt.wait(0.5)[](#l6.65) t2 = time.time()[](#l6.66) results2.append((r, t2 - t1))[](#l6.67) Bunch(f, N).wait_for_finished()[](#l6.68) self.assertEqual(results1, [False] * N)[](#l6.69) for r, dt in results2:[](#l6.70) self.assertFalse(r)[](#l6.71)
self.assertTrue(dt >= 0.2, dt)[](#l6.72)
self.assertTimeout(dt, 0.5)[](#l6.73) # The event is set[](#l6.74) results1 = [][](#l6.75) results2 = [][](#l6.76)
@@ -397,14 +431,14 @@ class ConditionTests(BaseTestCase): def f(): cond.acquire() t1 = time.time()
cond.wait(0.2)[](#l6.81)
cond.wait(0.5)[](#l6.82) t2 = time.time()[](#l6.83) cond.release()[](#l6.84) results.append(t2 - t1)[](#l6.85) Bunch(f, N).wait_for_finished()[](#l6.86) self.assertEqual(len(results), 5)[](#l6.87) for dt in results:[](#l6.88)
self.assertTrue(dt >= 0.2, dt)[](#l6.89)
self.assertTimeout(dt, 0.5)[](#l6.90)
class BaseSemaphoreTests(BaseTestCase):
--- a/Lib/threading.py +++ b/Lib/threading.py @@ -31,6 +31,7 @@ try: _CRLock = _thread.RLock except AttributeError: _CRLock = None +TIMEOUT_MAX = _thread.TIMEOUT_MAX del _thread @@ -107,14 +108,14 @@ class _RLock(_Verbose): return "<%s owner=%r count=%d>" % ( self.class.name, owner, self._count)
- def acquire(self, blocking=True, timeout=-1): me = _get_ident() if self._owner == me: self._count = self._count + 1 if debug: self._note("%s.acquire(%s): recursive success", self, blocking) return 1
rc = self._block.acquire(blocking)[](#l7.23)
rc = self._block.acquire(blocking, timeout)[](#l7.24) if rc:[](#l7.25) self._owner = me[](#l7.26) self._count = 1[](#l7.27)
@@ -234,22 +235,10 @@ class _Condition(_Verbose): if debug: self._note("%s.wait(): got it", self) else:
# Balancing act: We can't afford a pure busy loop, so we[](#l7.32)
# have to sleep; but if we sleep the whole timeout time,[](#l7.33)
# we'll be unresponsive. The scheme here sleeps very[](#l7.34)
# little at first, longer as time goes on, but never longer[](#l7.35)
# than 20 times per second (or the timeout time remaining).[](#l7.36)
endtime = _time() + timeout[](#l7.37)
delay = 0.0005 # 500 us -> initial delay of 1 ms[](#l7.38)
while True:[](#l7.39)
gotit = waiter.acquire(0)[](#l7.40)
if gotit:[](#l7.41)
break[](#l7.42)
remaining = endtime - _time()[](#l7.43)
if remaining <= 0:[](#l7.44)
break[](#l7.45)
delay = min(delay * 2, remaining, .05)[](#l7.46)
_sleep(delay)[](#l7.47)
if timeout > 0:[](#l7.48)
gotit = waiter.acquire(True, timeout)[](#l7.49)
else:[](#l7.50)
gotit = waiter.acquire(False)[](#l7.51) if not gotit:[](#l7.52) if __debug__:[](#l7.53) self._note("%s.wait(%s): timed out", self, timeout)[](#l7.54)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -312,6 +312,11 @@ C-API
Library
-------
+- Issue #7316: the acquire() method of lock objects in the :mod:threading
- module now takes an optional timeout argument in seconds. Timeout support
- relies on the system threading library, so as to avoid a semi-busy wait
- loop. +
- Issue #8383: pickle and pickletools use surrogatepass error handler when encoding unicode as utf8 to support lone surrogates and stay compatible with Python 2.x and 3.0
--- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -40,18 +40,47 @@ lock_dealloc(lockobject *self) } static PyObject * -lock_PyThread_acquire_lock(lockobject *self, PyObject *args) +lock_PyThread_acquire_lock(lockobject *self, PyObject *args, PyObject *kwds) { - int i = 1; + char kwlist[] = {"blocking", "timeout", NULL}; + int blocking = 1; + double timeout = -1; + PY_TIMEOUT_T microseconds; + int r; - if (!PyArg_ParseTuple(args, "|i:acquire", &i)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|id:acquire", kwlist, + &blocking, &timeout)) return NULL; + if (!blocking && timeout != -1) { + PyErr_SetString(PyExc_ValueError, "can't specify a timeout " + "for a non-blocking call"); + return NULL; + } + if (timeout < 0 && timeout != -1) { + PyErr_SetString(PyExc_ValueError, "timeout value must be " + "strictly positive"); + return NULL; + } + if (!blocking) + microseconds = 0; + else if (timeout == -1) + microseconds = -1; + else { + timeout *= 1e6; + if (timeout >= (double) PY_TIMEOUT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "timeout value is too large"); + return NULL; + } + microseconds = (PY_TIMEOUT_T) timeout; + } + Py_BEGIN_ALLOW_THREADS - i = PyThread_acquire_lock(self->lock_lock, i); + r = PyThread_acquire_lock_timed(self->lock_lock, microseconds); Py_END_ALLOW_THREADS - return PyBool_FromLong((long)i); + return PyBool_FromLong(r); } PyDoc_STRVAR(acquire_doc, @@ -106,9 +135,9 @@ Return whether the lock is in the locked static PyMethodDef lock_methods[] = { {"acquire_lock", (PyCFunction)lock_PyThread_acquire_lock, - METH_VARARGS, acquire_doc}, + METH_VARARGS | METH_KEYWORDS, acquire_doc}, {"acquire", (PyCFunction)lock_PyThread_acquire_lock, - METH_VARARGS, acquire_doc}, + METH_VARARGS | METH_KEYWORDS, acquire_doc}, {"release_lock", (PyCFunction)lock_PyThread_release_lock, METH_NOARGS, release_doc}, {"release", (PyCFunction)lock_PyThread_release_lock, @@ -118,7 +147,7 @@ static PyMethodDef lock_methods[] = { {"locked", (PyCFunction)lock_locked_lock, METH_NOARGS, locked_doc}, {"enter", (PyCFunction)lock_PyThread_acquire_lock, - METH_VARARGS, acquire_doc}, + METH_VARARGS | METH_KEYWORDS, acquire_doc}, {"exit", (PyCFunction)lock_PyThread_release_lock, METH_VARARGS, release_doc}, {NULL, NULL} / sentinel */ @@ -183,15 +212,41 @@ rlock_dealloc(rlockobject *self) static PyObject * rlock_acquire(rlockobject *self, PyObject *args, PyObject *kwds) { - char *kwlist[] = {"blocking", NULL}; + char *kwlist[] = {"blocking", "timeout", NULL}; int blocking = 1; + double timeout = -1; + PY_TIMEOUT_T microseconds; long tid; int r = 1; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i:acquire", kwlist, - &blocking)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|id:acquire", kwlist, + &blocking, &timeout)) return NULL; + if (!blocking && timeout != -1) { + PyErr_SetString(PyExc_ValueError, "can't specify a timeout " + "for a non-blocking call"); + return NULL; + } + if (timeout < 0 && timeout != -1) { + PyErr_SetString(PyExc_ValueError, "timeout value must be " + "strictly positive"); + return NULL; + } + if (!blocking) + microseconds = 0; + else if (timeout == -1) + microseconds = -1; + else { + timeout *= 1e6; + if (timeout >= (double) PY_TIMEOUT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "timeout value is too large"); + return NULL; + } + microseconds = (PY_TIMEOUT_T) timeout; + } + tid = PyThread_get_thread_ident(); if (self->rlock_count > 0 && tid == self->rlock_owner) { unsigned long count = self->rlock_count + 1; @@ -206,11 +261,11 @@ rlock_acquire(rlockobject *self, PyObjec if (self->rlock_count > 0 || !PyThread_acquire_lock(self->rlock_lock, 0)) { - if (!blocking) { + if (microseconds == 0) { Py_RETURN_FALSE; } Py_BEGIN_ALLOW_THREADS - r = PyThread_acquire_lock(self->rlock_lock, blocking); + r = PyThread_acquire_lock_timed(self->rlock_lock, microseconds); Py_END_ALLOW_THREADS } if (r) { @@ -1005,7 +1060,7 @@ static struct PyModuleDef threadmodule = PyMODINIT_FUNC PyInit__thread(void) { - PyObject *m, *d; + PyObject *m, *d, timeout_max; / Initialize types: / if (PyType_Ready(&localtype) < 0) @@ -1020,6 +1075,12 @@ PyInit__thread(void) if (m == NULL) return NULL; + timeout_max = PyFloat_FromDouble(PY_TIMEOUT_MAX / 1000000); + if (!timeout_max) + return NULL; + if (PyModule_AddObject(m, "TIMEOUT_MAX", timeout_max) < 0) + return NULL; + / Add a symbolic constant */ d = PyModule_GetDict(m); ThreadError = PyErr_NewException("_thread.error", NULL, NULL);
--- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -34,13 +34,13 @@ DeleteNonRecursiveMutex(PNRMUTEX mutex) } DWORD -EnterNonRecursiveMutex(PNRMUTEX mutex, BOOL wait) +EnterNonRecursiveMutex(PNRMUTEX mutex, DWORD milliseconds) { /* Assume that the thread waits successfully / DWORD ret ; / InterlockedIncrement(&mutex->owned) == 0 means that no thread currently owns the mutex / - if (!wait) + if (milliseconds == 0) { if (InterlockedCompareExchange(&mutex->owned, 0, -1) != -1) return WAIT_TIMEOUT ; @@ -49,7 +49,7 @@ EnterNonRecursiveMutex(PNRMUTEX mutex, B else ret = InterlockedIncrement(&mutex->owned) ? / Some thread owns the mutex, let's wait... / - WaitForSingleObject(mutex->hevent, INFINITE) : WAIT_OBJECT_0 ; + WaitForSingleObject(mutex->hevent, milliseconds) : WAIT_OBJECT_0 ; mutex->thread_id = GetCurrentThreadId() ; / We own it */ return ret ; @@ -239,18 +239,37 @@ PyThread_free_lock(PyThread_type_lock aL
- if the lock has already been acquired by this thread
*/ int -PyThread_acquire_lock(PyThread_type_lock aLock, int waitflag) +PyThread_acquire_lock_timed(PyThread_type_lock aLock, PY_TIMEOUT_T microseconds) { int success ; -
- dprintf(("%ld: PyThread_acquire_lock(%p, %d) called\n", PyThread_get_thread_ident(),aLock, waitflag));
- success = aLock && EnterNonRecursiveMutex((PNRMUTEX) aLock, (waitflag ? INFINITE : 0)) == WAIT_OBJECT_0 ;
- if (microseconds >= 0) {
milliseconds = microseconds / 1000;[](#l10.42)
if (microseconds % 1000 > 0)[](#l10.43)
++milliseconds;[](#l10.44)
if ((DWORD) milliseconds != milliseconds)[](#l10.45)
Py_FatalError("Timeout too large for a DWORD, "[](#l10.46)
"please check PY_TIMEOUT_MAX");[](#l10.47)
- }
- else
milliseconds = INFINITE;[](#l10.50)
- dprintf(("%ld: PyThread_acquire_lock(%p, %d) -> %d\n", PyThread_get_thread_ident(),aLock, waitflag, success));
- dprintf(("%ld: PyThread_acquire_lock_timed(%p, %lld) called\n",
PyThread_get_thread_ident(), aLock, microseconds));[](#l10.54)
+ + success = aLock && EnterNonRecursiveMutex((PNRMUTEX) aLock, (DWORD) milliseconds) == WAIT_OBJECT_0 ; + + dprintf(("%ld: PyThread_acquire_lock(%p, %lld) -> %d\n", + PyThread_get_thread_ident(), aLock, microseconds, success)); return success; } +int +PyThread_acquire_lock(PyThread_type_lock aLock, int waitflag) +{ + return PyThread_acquire_lock_timed(aLock, waitflag ? -1 : 0); +} void PyThread_release_lock(PyThread_type_lock aLock)
--- a/Python/thread_pthread.h +++ b/Python/thread_pthread.h @@ -83,6 +83,26 @@ #endif +/* We assume all modern POSIX systems have gettimeofday() */ +#ifdef GETTIMEOFDAY_NO_TZ +#define GETTIMEOFDAY(ptv) gettimeofday(ptv) +#else +#define GETTIMEOFDAY(ptv) gettimeofday(ptv, (struct timezone )NULL) +#endif + +#define MICROSECONDS_TO_TIMESPEC(microseconds, ts) [](#l11.14) +do { [](#l11.15) + struct timeval tv; [](#l11.16) + GETTIMEOFDAY(&tv); [](#l11.17) + tv.tv_usec += microseconds % 1000000; [](#l11.18) + tv.tv_sec += microseconds / 1000000; [](#l11.19) + tv.tv_sec += tv.tv_usec / 1000000; [](#l11.20) + tv.tv_usec %= 1000000; [](#l11.21) + ts.tv_sec = tv.tv_sec; [](#l11.22) + ts.tv_nsec = tv.tv_usec * 1000; [](#l11.23) +} while(0) + + / A pthread mutex isn't sufficient to model the Python lock type
- because, according to Draft 5 of the docs (P1003.4a/D5), both of the
- following are undefined: @@ -295,34 +315,53 @@ fix_status(int status) return (status == -1) ? errno : status; }
-int -PyThread_acquire_lock(PyThread_type_lock lock, int waitflag) +int +PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds) { int success; sem_t *thelock = (sem_t )lock; int status, error = 0; + struct timespec ts; - dprintf(("PyThread_acquire_lock(%p, %d) called\n", lock, waitflag)); + dprintf(("PyThread_acquire_lock_timed(%p, %lld) called\n", + lock, microseconds)); + if (microseconds > 0) + MICROSECONDS_TO_TIMESPEC(microseconds, ts); do { - if (waitflag) + if (microseconds > 0) + status = fix_status(sem_timedwait(thelock, &ts)); + else if (microseconds == 0) + status = fix_status(sem_trywait(thelock)); + else status = fix_status(sem_wait(thelock)); - else - status = fix_status(sem_trywait(thelock)); } while (status == EINTR); / Retry if interrupted by a signal */ - if (waitflag) { + if (microseconds > 0) { + if (status != ETIMEDOUT) + CHECK_STATUS("sem_timedwait"); + } + else if (microseconds == 0) { + if (status != EAGAIN) + CHECK_STATUS("sem_trywait"); + } + else { CHECK_STATUS("sem_wait"); - } else if (status != EAGAIN) { - CHECK_STATUS("sem_trywait"); } success = (status == 0) ? 1 : 0; - dprintf(("PyThread_acquire_lock(%p, %d) -> %d\n", lock, waitflag, success)); + dprintf(("PyThread_acquire_lock_timed(%p, %lld) -> %d\n", + lock, microseconds, success)); return success; } +int +PyThread_acquire_lock(PyThread_type_lock lock, int waitflag) +{ + return PyThread_acquire_lock_timed(lock, waitflag ? -1 : 0); +} + void PyThread_release_lock(PyThread_type_lock lock) { @@ -390,40 +429,62 @@ PyThread_free_lock(PyThread_type_lock lo free((void *)thelock); } -int -PyThread_acquire_lock(PyThread_type_lock lock, int waitflag) +int +PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds) { int success; pthread_lock *thelock = (pthread_lock )lock; int status, error = 0; - dprintf(("PyThread_acquire_lock(%p, %d) called\n", lock, waitflag)); + dprintf(("PyThread_acquire_lock_timed(%p, %lld) called\n", + lock, microseconds)); status = pthread_mutex_lock( &thelock->mut ); CHECK_STATUS("pthread_mutex_lock[1]"); success = thelock->locked == 0; - if ( !success && waitflag ) { + if (!success && microseconds != 0) { + struct timespec ts; + if (microseconds > 0) + MICROSECONDS_TO_TIMESPEC(microseconds, ts); / continue trying until we get the lock / / mut must be locked by me -- part of the condition * protocol */ - while ( thelock->locked ) { - status = pthread_cond_wait(&thelock->lock_released, - &thelock->mut); - CHECK_STATUS("pthread_cond_wait"); + while (thelock->locked) { + if (microseconds > 0) { + status = pthread_cond_timedwait( + &thelock->lock_released, + &thelock->mut, &ts); + if (status == ETIMEDOUT) + break; + CHECK_STATUS("pthread_cond_timed_wait"); + } + else { + status = pthread_cond_wait( + &thelock->lock_released, + &thelock->mut); + CHECK_STATUS("pthread_cond_wait"); + } } - success = 1; + success = (status == 0); } if (success) thelock->locked = 1; status = pthread_mutex_unlock( &thelock->mut ); CHECK_STATUS("pthread_mutex_unlock[1]"); if (error) success = 0; - dprintf(("PyThread_acquire_lock(%p, %d) -> %d\n", lock, waitflag, success)); + dprintf(("PyThread_acquire_lock_timed(%p, %lld) -> %d\n", + lock, microseconds, success)); return success; } +int +PyThread_acquire_lock(PyThread_type_lock lock, int waitflag) +{ + return PyThread_acquire_lock_timed(lock, waitflag ? -1 : 0); +} + void PyThread_release_lock(PyThread_type_lock lock) {