Index: Python/thread_pthread.h =================================================================== --- Python/thread_pthread.h (revision 82754) +++ Python/thread_pthread.h (working copy) @@ -315,16 +315,17 @@ return (status == -1) ? errno : status; } -int -PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds) +PyLockStatus +PyThread_acquire_lock_timed_intr(PyThread_type_lock lock, + PY_TIMEOUT_T microseconds, int intr_flag) { - int success; + PyLockStatus success; sem_t *thelock = (sem_t *)lock; int status, error = 0; struct timespec ts; - dprintf(("PyThread_acquire_lock_timed(%p, %lld) called\n", - lock, microseconds)); + dprintf(("PyThread_acquire_lock_timed_intr(%p, %lld, %d) called\n", + lock, microseconds, intr_flag)); if (microseconds > 0) MICROSECONDS_TO_TIMESPEC(microseconds, ts); @@ -335,33 +336,38 @@ status = fix_status(sem_trywait(thelock)); else status = fix_status(sem_wait(thelock)); - } while (status == EINTR); /* Retry if interrupted by a signal */ + /* Retry if interrupted by a signal, unless the caller wants to be + notified. */ + } while (!intr_flag && status == EINTR); - if (microseconds > 0) { - if (status != ETIMEDOUT) - CHECK_STATUS("sem_timedwait"); + /* Don't check the status if we're stopping because of an interrupt. */ + if (!(intr_flag && status == EINTR)) { + 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 (microseconds == 0) { - if (status != EAGAIN) - CHECK_STATUS("sem_trywait"); + + if (status == 0) { + success = PY_LOCK_ACQUIRED; + } else if (intr_flag && status == EINTR) { + success = PY_LOCK_INTR; + } else { + success = PY_LOCK_FAILURE; } - else { - CHECK_STATUS("sem_wait"); - } - success = (status == 0) ? 1 : 0; - - dprintf(("PyThread_acquire_lock_timed(%p, %lld) -> %d\n", - lock, microseconds, success)); + dprintf(("PyThread_acquire_lock_timed_intr(%p, %lld, %d) -> %d\n", + lock, microseconds, intr_flag, 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) { @@ -435,21 +441,25 @@ free((void *)thelock); } -int -PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds) +PyLockStatus +PyThread_acquire_lock_timed_intr(PyThread_type_lock lock, + PY_TIMEOUT_T microseconds, int intr_flag) { - int success; + PyLockStatus success; pthread_lock *thelock = (pthread_lock *)lock; int status, error = 0; - dprintf(("PyThread_acquire_lock_timed(%p, %lld) called\n", - lock, microseconds)); + dprintf(("PyThread_acquire_lock_timed_intr(%p, %lld, %d) called\n", + lock, microseconds, intr_flag)); status = pthread_mutex_lock( &thelock->mut ); CHECK_STATUS("pthread_mutex_lock[1]"); - success = thelock->locked == 0; - if (!success && microseconds != 0) { + if (thelock->locked == 0) { + success = PY_LOCK_ACQUIRED; + } else if (microseconds == 0) { + success = PY_LOCK_FAILURE; + } else { struct timespec ts; if (microseconds > 0) MICROSECONDS_TO_TIMESPEC(microseconds, ts); @@ -457,7 +467,8 @@ /* mut must be locked by me -- part of the condition * protocol */ - while (thelock->locked) { + success = PY_LOCK_FAILURE; + while (success == PY_LOCK_FAILURE) { if (microseconds > 0) { status = pthread_cond_timedwait( &thelock->lock_released, @@ -472,25 +483,29 @@ &thelock->mut); CHECK_STATUS("pthread_cond_wait"); } + + if (intr_flag && status == 0 && thelock->locked) { + /* We were woken up, but didn't get the lock. We probably received + * a signal. Return -1 to allow the caller to handle it and retry. */ + success = PY_LOCK_INTR; + break; + } else if (status == 0 && !thelock->locked) { + success = PY_LOCK_ACQUIRED; + } else { + success = PY_LOCK_FAILURE; + } } - success = (status == 0); } - if (success) thelock->locked = 1; + if (success == PY_LOCK_ACQUIRED) thelock->locked = 1; status = pthread_mutex_unlock( &thelock->mut ); CHECK_STATUS("pthread_mutex_unlock[1]"); - if (error) success = 0; - dprintf(("PyThread_acquire_lock_timed(%p, %lld) -> %d\n", - lock, microseconds, success)); + if (error) success = PY_LOCK_FAILURE; + dprintf(("PyThread_acquire_lock_timed_intr(%p, %lld, %d) -> %d\n", + lock, microseconds, intr_flag, 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) { @@ -514,6 +529,18 @@ #endif /* USE_SEMAPHORES */ +int +PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds) +{ + return PyThread_acquire_lock_timed_intr(lock, microseconds, /*intr_flag=*/0); +} + +int +PyThread_acquire_lock(PyThread_type_lock lock, int waitflag) +{ + return PyThread_acquire_lock_timed_intr(lock, waitflag ? -1 : 0, /*intr_flag=*/0); +} + /* set the thread stack size. * Return 0 if size is valid, -1 if size is invalid, * -2 if setting stack size is not supported. Index: Python/thread_nt.h =================================================================== --- Python/thread_nt.h (revision 82754) +++ Python/thread_nt.h (working copy) @@ -271,6 +271,15 @@ return PyThread_acquire_lock_timed(aLock, waitflag ? -1 : 0); } +/* Fow now, intr_flag does nothing on Windows, and lock acquires are + * uninterruptible. */ +PyLockStatus +PyThread_acquire_lock_timed_intr(PyThread_type_lock aLock, + PY_TIMEOUT_T microseconds, int intr_flag) +{ + return PyThread_acquire_lock_timed(aLock, microseconds); +} + void PyThread_release_lock(PyThread_type_lock aLock) { Index: Include/pythread.h =================================================================== --- Include/pythread.h (revision 82754) +++ Include/pythread.h (working copy) @@ -9,6 +9,14 @@ extern "C" { #endif +/* Return status codes for Python lock acquisition. Chosen for maximum + * backwards compatibility, ie failure -> 0, success -> 1. */ +typedef enum PyLockStatus { + PY_LOCK_FAILURE = 0, + PY_LOCK_ACQUIRED = 1, + PY_LOCK_INTR +} PyLockStatus; + PyAPI_FUNC(void) PyThread_init_thread(void); PyAPI_FUNC(long) PyThread_start_new_thread(void (*)(void *), void *); PyAPI_FUNC(void) PyThread_exit_thread(void); @@ -49,11 +57,22 @@ 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, - PY_TIMEOUT_T microseconds); + PY_TIMEOUT_T microseconds); + + +/* New interruptible lock acquisition function. In addition to 0 for failure + and 1 for success, it can return PY_LOCK_INTR to indicate that a signal was + received. + + Introduced in 3.2. +*/ +PyAPI_FUNC(PyLockStatus) PyThread_acquire_lock_timed_intr( + PyThread_type_lock, PY_TIMEOUT_T microseconds, int intr_flag); + PyAPI_FUNC(void) PyThread_release_lock(PyThread_type_lock); PyAPI_FUNC(size_t) PyThread_get_stacksize(void); Index: Lib/test/test_threadsignals.py =================================================================== --- Lib/test/test_threadsignals.py (revision 82754) +++ Lib/test/test_threadsignals.py (working copy) @@ -66,7 +66,21 @@ def spawnSignallingThread(self): thread.start_new_thread(send_signals, ()) + def test_lock_acquire_interruption(self): + # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck + # in a deadlock. + def alarm_interrupt(sig, frame): + raise KeyboardInterrupt + oldalrm = signal.signal(signal.SIGALRM, alarm_interrupt) + try: + lock = thread.allocate_lock() + lock.acquire() + signal.alarm(1) + self.assertRaises(KeyboardInterrupt, lock.acquire) + finally: + signal.signal(signal.SIGALRM, oldalrm) + def test_main(): global signal_blackboard Index: Modules/_threadmodule.c =================================================================== --- Modules/_threadmodule.c (revision 82754) +++ Modules/_threadmodule.c (working copy) @@ -46,7 +46,7 @@ int blocking = 1; double timeout = -1; PY_TIMEOUT_T microseconds; - int r; + PyLockStatus r; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|id:acquire", kwlist, &blocking, &timeout)) @@ -76,10 +76,19 @@ microseconds = (PY_TIMEOUT_T) timeout; } - Py_BEGIN_ALLOW_THREADS - r = PyThread_acquire_lock_timed(self->lock_lock, microseconds); - Py_END_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS + r = PyThread_acquire_lock_timed_intr(self->lock_lock, microseconds, + /*intr_flag=*/1); + Py_END_ALLOW_THREADS + /* Run signal handlers if we were interrupted. Propagate exceptions from + * signal handlers, such as KeyboardInterrupt. */ + if (r == PY_LOCK_INTR && Py_MakePendingCalls() < 0) { + return NULL; + } + } while (r == PY_LOCK_INTR); /* Retry if we were interrupted. */ + return PyBool_FromLong(r); }