# HG changeset patch # Parent 52744a5a9260215992fedd29ba98432bbad69391 Make _multiprocessing.win32.WaitForMultipleObjects interruptible. The patch also adds functions _PyOS_SigintEvent and _PyOS_IsMainThread which are implemented in signalmodule.c and declared in intrcheck.c. _PyOS_SigintEvent returns a manual reset event (cast to void*) which is set whenever SIGINT is received. It is Windows only. _PyOS_IsMainThread returns 0 or 1 according to whether the current thread is the main thread. The time and _multiprocessing modules have been updated to use these functions. Note that WaitForMultipleObjects has a bWaitAll parameter. When this is true, all handles in the array are waited for, and WaitForMultipleObjects is not interruptible. diff -r 52744a5a9260 Include/intrcheck.h --- a/Include/intrcheck.h Fri Jun 24 09:37:26 2011 -0500 +++ b/Include/intrcheck.h Sun Jun 26 21:19:45 2011 +0100 @@ -8,6 +8,12 @@ PyAPI_FUNC(int) PyOS_InterruptOccurred(void); PyAPI_FUNC(void) PyOS_InitInterrupts(void); PyAPI_FUNC(void) PyOS_AfterFork(void); +PyAPI_FUNC(int) _PyOS_IsMainThread(void); + +#ifdef MS_WINDOWS +/* windows.h is not included by Python.h so use void* instead of HANDLE */ +PyAPI_FUNC(void*) _PyOS_SigintEvent(void); +#endif #ifdef __cplusplus } diff -r 52744a5a9260 Modules/_multiprocessing/multiprocessing.c --- a/Modules/_multiprocessing/multiprocessing.c Fri Jun 24 09:37:26 2011 -0500 +++ b/Modules/_multiprocessing/multiprocessing.c Sun Jun 26 21:19:45 2011 +0100 @@ -59,27 +59,10 @@ /* - * Windows only - */ - -#ifdef MS_WINDOWS - -/* On Windows we set an event to signal Ctrl-C; compare with timemodule.c */ - -HANDLE sigint_event = NULL; - -static BOOL WINAPI -ProcessingCtrlHandler(DWORD dwCtrlType) -{ - SetEvent(sigint_event); - return FALSE; -} - -/* * Unix only */ -#else /* !MS_WINDOWS */ +#ifndef MS_WINDOWS /* !MS_WINDOWS */ #if HAVE_FD_TRANSFER @@ -265,17 +248,6 @@ if (!temp) return NULL; PyModule_AddObject(module, "win32", temp); - - /* Initialize the event handle used to signal Ctrl-C */ - sigint_event = CreateEvent(NULL, TRUE, FALSE, NULL); - if (!sigint_event) { - PyErr_SetFromWindowsErr(0); - return NULL; - } - if (!SetConsoleCtrlHandler(ProcessingCtrlHandler, TRUE)) { - PyErr_SetFromWindowsErr(0); - return NULL; - } #endif /* Add configuration macros */ diff -r 52744a5a9260 Modules/_multiprocessing/multiprocessing.h --- a/Modules/_multiprocessing/multiprocessing.h Fri Jun 24 09:37:26 2011 -0500 +++ b/Modules/_multiprocessing/multiprocessing.h Sun Jun 26 21:19:45 2011 +0100 @@ -110,7 +110,6 @@ extern PyObject *BufferTooShort; extern PyTypeObject SemLockType; extern PyTypeObject PipeConnectionType; -extern HANDLE sigint_event; /* * Miscellaneous diff -r 52744a5a9260 Modules/_multiprocessing/semaphore.c --- a/Modules/_multiprocessing/semaphore.c Fri Jun 24 09:37:26 2011 -0500 +++ b/Modules/_multiprocessing/semaphore.c Sun Jun 26 21:19:45 2011 +0100 @@ -62,7 +62,8 @@ int blocking = 1; double timeout; PyObject *timeout_obj = Py_None; - DWORD res, full_msecs, msecs, start, ticks; + DWORD res, full_msecs, nhandles; + HANDLE handles[2], sigint_event; static char *kwlist[] = {"block", "timeout", NULL}; @@ -96,53 +97,33 @@ Py_RETURN_TRUE; } - /* check whether we can acquire without blocking */ - if (WaitForSingleObject(self->handle, 0) == WAIT_OBJECT_0) { - self->last_tid = GetCurrentThreadId(); - ++self->count; - Py_RETURN_TRUE; + /* prepare list of handles */ + nhandles = 0; + handles[nhandles++] = self->handle; + if (_PyOS_IsMainThread()) { + sigint_event = _PyOS_SigintEvent(); + assert(sigint_event != NULL); + handles[nhandles++] = sigint_event; } - msecs = full_msecs; - start = GetTickCount(); - - for ( ; ; ) { - HANDLE handles[2] = {self->handle, sigint_event}; - - /* do the wait */ - Py_BEGIN_ALLOW_THREADS + /* do the wait */ + Py_BEGIN_ALLOW_THREADS + if (sigint_event != NULL) ResetEvent(sigint_event); - res = WaitForMultipleObjects(2, handles, FALSE, msecs); - Py_END_ALLOW_THREADS - - /* handle result */ - if (res != WAIT_OBJECT_0 + 1) - break; - - /* got SIGINT so give signal handler a chance to run */ - Sleep(1); - - /* if this is main thread let KeyboardInterrupt be raised */ - if (PyErr_CheckSignals()) - return NULL; - - /* recalculate timeout */ - if (msecs != INFINITE) { - ticks = GetTickCount(); - if ((DWORD)(ticks - start) >= full_msecs) - Py_RETURN_FALSE; - msecs = full_msecs - (ticks - start); - } - } + res = WaitForMultipleObjects(nhandles, handles, FALSE, full_msecs); + Py_END_ALLOW_THREADS /* handle result */ switch (res) { case WAIT_TIMEOUT: Py_RETURN_FALSE; - case WAIT_OBJECT_0: + case WAIT_OBJECT_0 + 0: self->last_tid = GetCurrentThreadId(); ++self->count; Py_RETURN_TRUE; + case WAIT_OBJECT_0 + 1: + errno = EINTR; + return PyErr_SetFromErrno(PyExc_IOError); case WAIT_FAILED: return PyErr_SetFromWindowsErr(0); default: diff -r 52744a5a9260 Modules/_multiprocessing/win32_functions.c --- a/Modules/_multiprocessing/win32_functions.c Fri Jun 24 09:37:26 2011 -0500 +++ b/Modules/_multiprocessing/win32_functions.c Sun Jun 26 21:19:45 2011 +0100 @@ -679,6 +679,7 @@ DWORD result; PyObject *handle_seq; HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + HANDLE sigint_event = NULL; Py_ssize_t nhandles, i; int wait_flag; int milliseconds = INFINITE; @@ -696,10 +697,10 @@ nhandles = PySequence_Length(handle_seq); if (nhandles == -1) return NULL; - if (nhandles < 0 || nhandles >= MAXIMUM_WAIT_OBJECTS) { + if (nhandles < 0 || nhandles >= MAXIMUM_WAIT_OBJECTS - 1) { PyErr_Format(PyExc_ValueError, "need at most %zd handles, got a sequence of length %zd", - MAXIMUM_WAIT_OBJECTS, nhandles); + MAXIMUM_WAIT_OBJECTS - 1, nhandles); return NULL; } for (i = 0; i < nhandles; i++) { @@ -711,14 +712,27 @@ return NULL; handles[i] = h; } + /* If this is the main thread then make the wait interruptible + by Ctrl-C unless we are waiting for *all* handles */ + if (!wait_flag && _PyOS_IsMainThread()) { + sigint_event = _PyOS_SigintEvent(); + assert(sigint_event != NULL); + handles[nhandles++] = sigint_event; + } Py_BEGIN_ALLOW_THREADS + if (sigint_event != NULL) + ResetEvent(sigint_event); result = WaitForMultipleObjects((DWORD) nhandles, handles, (BOOL) wait_flag, (DWORD) milliseconds); Py_END_ALLOW_THREADS if (result == WAIT_FAILED) return PyErr_SetExcFromWindowsErr(PyExc_IOError, 0); + else if (sigint_event != NULL && result == WAIT_OBJECT_0 + nhandles - 1) { + errno = EINTR; + return PyErr_SetFromErrno(PyExc_IOError); + } return PyLong_FromLong((int) result); } diff -r 52744a5a9260 Modules/signalmodule.c --- a/Modules/signalmodule.c Fri Jun 24 09:37:26 2011 -0500 +++ b/Modules/signalmodule.c Sun Jun 26 21:19:45 2011 +0100 @@ -109,6 +109,10 @@ static PyOS_sighandler_t old_siginthandler = SIG_DFL; +#ifdef MS_WINDOWS +static HANDLE sigint_event = NULL; +#endif + #ifdef HAVE_GETITIMER static PyObject *ItimerError; @@ -229,6 +233,11 @@ /* Issue #10311: asynchronously executing signal handlers should not mutate errno under the feet of unsuspecting C code. */ errno = save_errno; + +#ifdef MS_WINDOWS + if (sig_num == SIGINT) + SetEvent(sigint_event); +#endif } @@ -1101,6 +1110,11 @@ Py_DECREF(x); #endif +#ifdef MS_WINDOWS + /* Create manual-reset event, initially unset */ + sigint_event = CreateEvent(NULL, TRUE, FALSE, FALSE); +#endif + if (PyErr_Occurred()) { Py_DECREF(m); m = NULL; @@ -1245,3 +1259,25 @@ PyThread_ReInitTLS(); #endif } + +int +_PyOS_IsMainThread(void) +{ +#ifdef WITH_THREAD + return PyThread_get_thread_ident() == main_thread; +#else + return 1; +#endif +} + +#ifdef MS_WINDOWS +void *_PyOS_SigintEvent(void) +{ + /* Returns a manual-reset event which gets tripped whenever + SIGINT is received. + + Python.h does not include windows.h so we do cannot use HANDLE + as the return type of this function. We use void* instead. */ + return sigint_event; +} +#endif diff -r 52744a5a9260 Modules/timemodule.c --- a/Modules/timemodule.c Fri Jun 24 09:37:26 2011 -0500 +++ b/Modules/timemodule.c Sun Jun 26 21:19:45 2011 +0100 @@ -23,19 +23,6 @@ #include #include "pythread.h" -/* helper to allow us to interrupt sleep() on Windows*/ -static HANDLE hInterruptEvent = NULL; -static BOOL WINAPI PyCtrlHandler(DWORD dwCtrlType) -{ - SetEvent(hInterruptEvent); - /* allow other default handlers to be called. - Default Python handler will setup the - KeyboardInterrupt exception. - */ - return FALSE; -} -static long main_thread; - #if defined(__BORLANDC__) /* These overrides not needed for Win32 */ #define timezone _timezone @@ -876,15 +863,6 @@ /* Set, or reset, module variables like time.timezone */ PyInit_timezone(m); -#ifdef MS_WINDOWS - /* Helper to allow interrupts for Windows. - If Ctrl+C event delivered while not sleeping - it will be ignored. - */ - main_thread = PyThread_get_thread_ident(); - hInterruptEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - SetConsoleCtrlHandler( PyCtrlHandler, TRUE); -#endif /* MS_WINDOWS */ if (!initialized) { PyStructSequence_InitType(&StructTimeType, &struct_time_type_desc); @@ -952,18 +930,14 @@ * by Guido, only the main thread can be interrupted. */ ul_millis = (unsigned long)millisecs; - if (ul_millis == 0 || - main_thread != PyThread_get_thread_ident()) + if (ul_millis == 0 || !_PyOS_IsMainThread()) Sleep(ul_millis); else { DWORD rc; + HANDLE hInterruptEvent = _PyOS_SigintEvent(); ResetEvent(hInterruptEvent); rc = WaitForSingleObject(hInterruptEvent, ul_millis); if (rc == WAIT_OBJECT_0) { - /* Yield to make sure real Python signal - * handler called. - */ - Sleep(1); Py_BLOCK_THREADS errno = EINTR; PyErr_SetFromErrno(PyExc_IOError);