Commit 6dd381eb authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #12328: Under Windows, refactor handling of Ctrl-C events and

make _multiprocessing.win32.WaitForMultipleObjects interruptible when
the wait_flag parameter is false.  Patch by sbt.
parent ce4a9da7
...@@ -8,6 +8,12 @@ extern "C" { ...@@ -8,6 +8,12 @@ extern "C" {
PyAPI_FUNC(int) PyOS_InterruptOccurred(void); PyAPI_FUNC(int) PyOS_InterruptOccurred(void);
PyAPI_FUNC(void) PyOS_InitInterrupts(void); PyAPI_FUNC(void) PyOS_InitInterrupts(void);
PyAPI_FUNC(void) PyOS_AfterFork(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 #ifdef __cplusplus
} }
......
...@@ -387,6 +387,10 @@ Core and Builtins ...@@ -387,6 +387,10 @@ Core and Builtins
Library Library
------- -------
- Issue #12328: Under Windows, refactor handling of Ctrl-C events and
make _multiprocessing.win32.WaitForMultipleObjects interruptible when
the wait_flag parameter is false. Patch by sbt.
- Issue #13322: Fix BufferedWriter.write() to ensure that BlockingIOError is - Issue #13322: Fix BufferedWriter.write() to ensure that BlockingIOError is
raised when the wrapped raw file is non-blocking and the write would block. raised when the wrapped raw file is non-blocking and the write would block.
Previous code assumed that the raw write() would raise BlockingIOError, but Previous code assumed that the raw write() would raise BlockingIOError, but
......
...@@ -53,30 +53,6 @@ mp_SetError(PyObject *Type, int num) ...@@ -53,30 +53,6 @@ mp_SetError(PyObject *Type, int num)
} }
/*
* 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;
}
#endif /* MS_WINDOWS */
/*
* All platforms
*/
static PyObject* static PyObject*
multiprocessing_address_of_buffer(PyObject *self, PyObject *obj) multiprocessing_address_of_buffer(PyObject *self, PyObject *obj)
{ {
...@@ -165,17 +141,6 @@ PyInit__multiprocessing(void) ...@@ -165,17 +141,6 @@ PyInit__multiprocessing(void)
if (!temp) if (!temp)
return NULL; return NULL;
PyModule_AddObject(module, "win32", temp); 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 #endif
/* Add configuration macros */ /* Add configuration macros */
......
...@@ -100,7 +100,6 @@ PyObject *mp_SetError(PyObject *Type, int num); ...@@ -100,7 +100,6 @@ PyObject *mp_SetError(PyObject *Type, int num);
extern PyObject *BufferTooShort; extern PyObject *BufferTooShort;
extern PyTypeObject SemLockType; extern PyTypeObject SemLockType;
extern PyTypeObject PipeConnectionType; extern PyTypeObject PipeConnectionType;
extern HANDLE sigint_event;
/* /*
* Miscellaneous * Miscellaneous
......
...@@ -62,7 +62,8 @@ semlock_acquire(SemLockObject *self, PyObject *args, PyObject *kwds) ...@@ -62,7 +62,8 @@ semlock_acquire(SemLockObject *self, PyObject *args, PyObject *kwds)
int blocking = 1; int blocking = 1;
double timeout; double timeout;
PyObject *timeout_obj = Py_None; 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}; static char *kwlist[] = {"block", "timeout", NULL};
...@@ -96,53 +97,40 @@ semlock_acquire(SemLockObject *self, PyObject *args, PyObject *kwds) ...@@ -96,53 +97,40 @@ semlock_acquire(SemLockObject *self, PyObject *args, PyObject *kwds)
Py_RETURN_TRUE; Py_RETURN_TRUE;
} }
/* check whether we can acquire without blocking */ /* check whether we can acquire without releasing the GIL and blocking */
if (WaitForSingleObject(self->handle, 0) == WAIT_OBJECT_0) { if (WaitForSingleObject(self->handle, 0) == WAIT_OBJECT_0) {
self->last_tid = GetCurrentThreadId(); self->last_tid = GetCurrentThreadId();
++self->count; ++self->count;
Py_RETURN_TRUE; Py_RETURN_TRUE;
} }
msecs = full_msecs; /* prepare list of handles */
start = GetTickCount(); nhandles = 0;
handles[nhandles++] = self->handle;
for ( ; ; ) { if (_PyOS_IsMainThread()) {
HANDLE handles[2] = {self->handle, sigint_event}; sigint_event = _PyOS_SigintEvent();
assert(sigint_event != NULL);
handles[nhandles++] = sigint_event;
}
/* do the wait */ /* do the wait */
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
if (sigint_event != NULL)
ResetEvent(sigint_event); ResetEvent(sigint_event);
res = WaitForMultipleObjects(2, handles, FALSE, msecs); res = WaitForMultipleObjects(nhandles, handles, FALSE, full_msecs);
Py_END_ALLOW_THREADS 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);
}
}
/* handle result */ /* handle result */
switch (res) { switch (res) {
case WAIT_TIMEOUT: case WAIT_TIMEOUT:
Py_RETURN_FALSE; Py_RETURN_FALSE;
case WAIT_OBJECT_0: case WAIT_OBJECT_0 + 0:
self->last_tid = GetCurrentThreadId(); self->last_tid = GetCurrentThreadId();
++self->count; ++self->count;
Py_RETURN_TRUE; Py_RETURN_TRUE;
case WAIT_OBJECT_0 + 1:
errno = EINTR;
return PyErr_SetFromErrno(PyExc_IOError);
case WAIT_FAILED: case WAIT_FAILED:
return PyErr_SetFromWindowsErr(0); return PyErr_SetFromWindowsErr(0);
default: default:
......
...@@ -679,6 +679,7 @@ win32_WaitForMultipleObjects(PyObject* self, PyObject* args) ...@@ -679,6 +679,7 @@ win32_WaitForMultipleObjects(PyObject* self, PyObject* args)
DWORD result; DWORD result;
PyObject *handle_seq; PyObject *handle_seq;
HANDLE handles[MAXIMUM_WAIT_OBJECTS]; HANDLE handles[MAXIMUM_WAIT_OBJECTS];
HANDLE sigint_event = NULL;
Py_ssize_t nhandles, i; Py_ssize_t nhandles, i;
int wait_flag; int wait_flag;
int milliseconds = INFINITE; int milliseconds = INFINITE;
...@@ -696,10 +697,10 @@ win32_WaitForMultipleObjects(PyObject* self, PyObject* args) ...@@ -696,10 +697,10 @@ win32_WaitForMultipleObjects(PyObject* self, PyObject* args)
nhandles = PySequence_Length(handle_seq); nhandles = PySequence_Length(handle_seq);
if (nhandles == -1) if (nhandles == -1)
return NULL; return NULL;
if (nhandles < 0 || nhandles >= MAXIMUM_WAIT_OBJECTS) { if (nhandles < 0 || nhandles >= MAXIMUM_WAIT_OBJECTS - 1) {
PyErr_Format(PyExc_ValueError, PyErr_Format(PyExc_ValueError,
"need at most %zd handles, got a sequence of length %zd", "need at most %zd handles, got a sequence of length %zd",
MAXIMUM_WAIT_OBJECTS, nhandles); MAXIMUM_WAIT_OBJECTS - 1, nhandles);
return NULL; return NULL;
} }
for (i = 0; i < nhandles; i++) { for (i = 0; i < nhandles; i++) {
...@@ -711,14 +712,27 @@ win32_WaitForMultipleObjects(PyObject* self, PyObject* args) ...@@ -711,14 +712,27 @@ win32_WaitForMultipleObjects(PyObject* self, PyObject* args)
return NULL; return NULL;
handles[i] = h; 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 Py_BEGIN_ALLOW_THREADS
if (sigint_event != NULL)
ResetEvent(sigint_event);
result = WaitForMultipleObjects((DWORD) nhandles, handles, result = WaitForMultipleObjects((DWORD) nhandles, handles,
(BOOL) wait_flag, (DWORD) milliseconds); (BOOL) wait_flag, (DWORD) milliseconds);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
if (result == WAIT_FAILED) if (result == WAIT_FAILED)
return PyErr_SetExcFromWindowsErr(PyExc_IOError, 0); 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); return PyLong_FromLong((int) result);
} }
......
...@@ -109,6 +109,10 @@ static PyObject *IntHandler; ...@@ -109,6 +109,10 @@ static PyObject *IntHandler;
static PyOS_sighandler_t old_siginthandler = SIG_DFL; static PyOS_sighandler_t old_siginthandler = SIG_DFL;
#ifdef MS_WINDOWS
static HANDLE sigint_event = NULL;
#endif
#ifdef HAVE_GETITIMER #ifdef HAVE_GETITIMER
static PyObject *ItimerError; static PyObject *ItimerError;
...@@ -229,6 +233,11 @@ signal_handler(int sig_num) ...@@ -229,6 +233,11 @@ signal_handler(int sig_num)
/* Issue #10311: asynchronously executing signal handlers should not /* Issue #10311: asynchronously executing signal handlers should not
mutate errno under the feet of unsuspecting C code. */ mutate errno under the feet of unsuspecting C code. */
errno = save_errno; errno = save_errno;
#ifdef MS_WINDOWS
if (sig_num == SIGINT)
SetEvent(sigint_event);
#endif
} }
...@@ -1253,6 +1262,11 @@ PyInit_signal(void) ...@@ -1253,6 +1262,11 @@ PyInit_signal(void)
Py_DECREF(x); Py_DECREF(x);
#endif #endif
#ifdef MS_WINDOWS
/* Create manual-reset event, initially unset */
sigint_event = CreateEvent(NULL, TRUE, FALSE, FALSE);
#endif
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
Py_DECREF(m); Py_DECREF(m);
m = NULL; m = NULL;
...@@ -1397,3 +1411,25 @@ PyOS_AfterFork(void) ...@@ -1397,3 +1411,25 @@ PyOS_AfterFork(void)
PyThread_ReInitTLS(); PyThread_ReInitTLS();
#endif #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
...@@ -21,19 +21,6 @@ ...@@ -21,19 +21,6 @@
#include <windows.h> #include <windows.h>
#include "pythread.h" #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__) #if defined(__BORLANDC__)
/* These overrides not needed for Win32 */ /* These overrides not needed for Win32 */
#define timezone _timezone #define timezone _timezone
...@@ -955,15 +942,6 @@ PyInit_time(void) ...@@ -955,15 +942,6 @@ PyInit_time(void)
/* Set, or reset, module variables like time.timezone */ /* Set, or reset, module variables like time.timezone */
PyInit_timezone(m); 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) { if (!initialized) {
PyStructSequence_InitType(&StructTimeType, PyStructSequence_InitType(&StructTimeType,
&struct_time_type_desc); &struct_time_type_desc);
...@@ -1036,18 +1014,14 @@ floatsleep(double secs) ...@@ -1036,18 +1014,14 @@ floatsleep(double secs)
* by Guido, only the main thread can be interrupted. * by Guido, only the main thread can be interrupted.
*/ */
ul_millis = (unsigned long)millisecs; ul_millis = (unsigned long)millisecs;
if (ul_millis == 0 || if (ul_millis == 0 || !_PyOS_IsMainThread())
main_thread != PyThread_get_thread_ident())
Sleep(ul_millis); Sleep(ul_millis);
else { else {
DWORD rc; DWORD rc;
HANDLE hInterruptEvent = _PyOS_SigintEvent();
ResetEvent(hInterruptEvent); ResetEvent(hInterruptEvent);
rc = WaitForSingleObject(hInterruptEvent, ul_millis); rc = WaitForSingleObject(hInterruptEvent, ul_millis);
if (rc == WAIT_OBJECT_0) { if (rc == WAIT_OBJECT_0) {
/* Yield to make sure real Python signal
* handler called.
*/
Sleep(1);
Py_BLOCK_THREADS Py_BLOCK_THREADS
errno = EINTR; errno = EINTR;
PyErr_SetFromErrno(PyExc_IOError); PyErr_SetFromErrno(PyExc_IOError);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment