Skip to content

Commit 6dd381e

Browse files
committed
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.
1 parent ce4a9da commit 6dd381e

File tree

8 files changed

+84
-98
lines changed

8 files changed

+84
-98
lines changed

Include/intrcheck.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ extern "C" {
88
PyAPI_FUNC(int) PyOS_InterruptOccurred(void);
99
PyAPI_FUNC(void) PyOS_InitInterrupts(void);
1010
PyAPI_FUNC(void) PyOS_AfterFork(void);
11+
PyAPI_FUNC(int) _PyOS_IsMainThread(void);
12+
13+
#ifdef MS_WINDOWS
14+
/* windows.h is not included by Python.h so use void* instead of HANDLE */
15+
PyAPI_FUNC(void*) _PyOS_SigintEvent(void);
16+
#endif
1117

1218
#ifdef __cplusplus
1319
}

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,10 @@ Core and Builtins
387387
Library
388388
-------
389389

390+
- Issue #12328: Under Windows, refactor handling of Ctrl-C events and
391+
make _multiprocessing.win32.WaitForMultipleObjects interruptible when
392+
the wait_flag parameter is false. Patch by sbt.
393+
390394
- Issue #13322: Fix BufferedWriter.write() to ensure that BlockingIOError is
391395
raised when the wrapped raw file is non-blocking and the write would block.
392396
Previous code assumed that the raw write() would raise BlockingIOError, but

Modules/_multiprocessing/multiprocessing.c

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -53,30 +53,6 @@ mp_SetError(PyObject *Type, int num)
5353
}
5454

5555

56-
/*
57-
* Windows only
58-
*/
59-
60-
#ifdef MS_WINDOWS
61-
62-
/* On Windows we set an event to signal Ctrl-C; compare with timemodule.c */
63-
64-
HANDLE sigint_event = NULL;
65-
66-
static BOOL WINAPI
67-
ProcessingCtrlHandler(DWORD dwCtrlType)
68-
{
69-
SetEvent(sigint_event);
70-
return FALSE;
71-
}
72-
73-
#endif /* MS_WINDOWS */
74-
75-
76-
/*
77-
* All platforms
78-
*/
79-
8056
static PyObject*
8157
multiprocessing_address_of_buffer(PyObject *self, PyObject *obj)
8258
{
@@ -165,17 +141,6 @@ PyInit__multiprocessing(void)
165141
if (!temp)
166142
return NULL;
167143
PyModule_AddObject(module, "win32", temp);
168-
169-
/* Initialize the event handle used to signal Ctrl-C */
170-
sigint_event = CreateEvent(NULL, TRUE, FALSE, NULL);
171-
if (!sigint_event) {
172-
PyErr_SetFromWindowsErr(0);
173-
return NULL;
174-
}
175-
if (!SetConsoleCtrlHandler(ProcessingCtrlHandler, TRUE)) {
176-
PyErr_SetFromWindowsErr(0);
177-
return NULL;
178-
}
179144
#endif
180145

181146
/* Add configuration macros */

Modules/_multiprocessing/multiprocessing.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ PyObject *mp_SetError(PyObject *Type, int num);
100100
extern PyObject *BufferTooShort;
101101
extern PyTypeObject SemLockType;
102102
extern PyTypeObject PipeConnectionType;
103-
extern HANDLE sigint_event;
104103

105104
/*
106105
* Miscellaneous

Modules/_multiprocessing/semaphore.c

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ semlock_acquire(SemLockObject *self, PyObject *args, PyObject *kwds)
6262
int blocking = 1;
6363
double timeout;
6464
PyObject *timeout_obj = Py_None;
65-
DWORD res, full_msecs, msecs, start, ticks;
65+
DWORD res, full_msecs, nhandles;
66+
HANDLE handles[2], sigint_event;
6667

6768
static char *kwlist[] = {"block", "timeout", NULL};
6869

@@ -96,53 +97,40 @@ semlock_acquire(SemLockObject *self, PyObject *args, PyObject *kwds)
9697
Py_RETURN_TRUE;
9798
}
9899

99-
/* check whether we can acquire without blocking */
100+
/* check whether we can acquire without releasing the GIL and blocking */
100101
if (WaitForSingleObject(self->handle, 0) == WAIT_OBJECT_0) {
101102
self->last_tid = GetCurrentThreadId();
102103
++self->count;
103104
Py_RETURN_TRUE;
104105
}
105106

106-
msecs = full_msecs;
107-
start = GetTickCount();
108-
109-
for ( ; ; ) {
110-
HANDLE handles[2] = {self->handle, sigint_event};
107+
/* prepare list of handles */
108+
nhandles = 0;
109+
handles[nhandles++] = self->handle;
110+
if (_PyOS_IsMainThread()) {
111+
sigint_event = _PyOS_SigintEvent();
112+
assert(sigint_event != NULL);
113+
handles[nhandles++] = sigint_event;
114+
}
111115

112-
/* do the wait */
113-
Py_BEGIN_ALLOW_THREADS
116+
/* do the wait */
117+
Py_BEGIN_ALLOW_THREADS
118+
if (sigint_event != NULL)
114119
ResetEvent(sigint_event);
115-
res = WaitForMultipleObjects(2, handles, FALSE, msecs);
116-
Py_END_ALLOW_THREADS
117-
118-
/* handle result */
119-
if (res != WAIT_OBJECT_0 + 1)
120-
break;
121-
122-
/* got SIGINT so give signal handler a chance to run */
123-
Sleep(1);
124-
125-
/* if this is main thread let KeyboardInterrupt be raised */
126-
if (PyErr_CheckSignals())
127-
return NULL;
128-
129-
/* recalculate timeout */
130-
if (msecs != INFINITE) {
131-
ticks = GetTickCount();
132-
if ((DWORD)(ticks - start) >= full_msecs)
133-
Py_RETURN_FALSE;
134-
msecs = full_msecs - (ticks - start);
135-
}
136-
}
120+
res = WaitForMultipleObjects(nhandles, handles, FALSE, full_msecs);
121+
Py_END_ALLOW_THREADS
137122

138123
/* handle result */
139124
switch (res) {
140125
case WAIT_TIMEOUT:
141126
Py_RETURN_FALSE;
142-
case WAIT_OBJECT_0:
127+
case WAIT_OBJECT_0 + 0:
143128
self->last_tid = GetCurrentThreadId();
144129
++self->count;
145130
Py_RETURN_TRUE;
131+
case WAIT_OBJECT_0 + 1:
132+
errno = EINTR;
133+
return PyErr_SetFromErrno(PyExc_IOError);
146134
case WAIT_FAILED:
147135
return PyErr_SetFromWindowsErr(0);
148136
default:

Modules/_multiprocessing/win32_functions.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,7 @@ win32_WaitForMultipleObjects(PyObject* self, PyObject* args)
679679
DWORD result;
680680
PyObject *handle_seq;
681681
HANDLE handles[MAXIMUM_WAIT_OBJECTS];
682+
HANDLE sigint_event = NULL;
682683
Py_ssize_t nhandles, i;
683684
int wait_flag;
684685
int milliseconds = INFINITE;
@@ -696,10 +697,10 @@ win32_WaitForMultipleObjects(PyObject* self, PyObject* args)
696697
nhandles = PySequence_Length(handle_seq);
697698
if (nhandles == -1)
698699
return NULL;
699-
if (nhandles < 0 || nhandles >= MAXIMUM_WAIT_OBJECTS) {
700+
if (nhandles < 0 || nhandles >= MAXIMUM_WAIT_OBJECTS - 1) {
700701
PyErr_Format(PyExc_ValueError,
701702
"need at most %zd handles, got a sequence of length %zd",
702-
MAXIMUM_WAIT_OBJECTS, nhandles);
703+
MAXIMUM_WAIT_OBJECTS - 1, nhandles);
703704
return NULL;
704705
}
705706
for (i = 0; i < nhandles; i++) {
@@ -711,14 +712,27 @@ win32_WaitForMultipleObjects(PyObject* self, PyObject* args)
711712
return NULL;
712713
handles[i] = h;
713714
}
715+
/* If this is the main thread then make the wait interruptible
716+
by Ctrl-C unless we are waiting for *all* handles */
717+
if (!wait_flag && _PyOS_IsMainThread()) {
718+
sigint_event = _PyOS_SigintEvent();
719+
assert(sigint_event != NULL);
720+
handles[nhandles++] = sigint_event;
721+
}
714722

715723
Py_BEGIN_ALLOW_THREADS
724+
if (sigint_event != NULL)
725+
ResetEvent(sigint_event);
716726
result = WaitForMultipleObjects((DWORD) nhandles, handles,
717727
(BOOL) wait_flag, (DWORD) milliseconds);
718728
Py_END_ALLOW_THREADS
719729

720730
if (result == WAIT_FAILED)
721731
return PyErr_SetExcFromWindowsErr(PyExc_IOError, 0);
732+
else if (sigint_event != NULL && result == WAIT_OBJECT_0 + nhandles - 1) {
733+
errno = EINTR;
734+
return PyErr_SetFromErrno(PyExc_IOError);
735+
}
722736

723737
return PyLong_FromLong((int) result);
724738
}

Modules/signalmodule.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ static PyObject *IntHandler;
109109

110110
static PyOS_sighandler_t old_siginthandler = SIG_DFL;
111111

112+
#ifdef MS_WINDOWS
113+
static HANDLE sigint_event = NULL;
114+
#endif
115+
112116
#ifdef HAVE_GETITIMER
113117
static PyObject *ItimerError;
114118

@@ -229,6 +233,11 @@ signal_handler(int sig_num)
229233
/* Issue #10311: asynchronously executing signal handlers should not
230234
mutate errno under the feet of unsuspecting C code. */
231235
errno = save_errno;
236+
237+
#ifdef MS_WINDOWS
238+
if (sig_num == SIGINT)
239+
SetEvent(sigint_event);
240+
#endif
232241
}
233242

234243

@@ -1253,6 +1262,11 @@ PyInit_signal(void)
12531262
Py_DECREF(x);
12541263
#endif
12551264

1265+
#ifdef MS_WINDOWS
1266+
/* Create manual-reset event, initially unset */
1267+
sigint_event = CreateEvent(NULL, TRUE, FALSE, FALSE);
1268+
#endif
1269+
12561270
if (PyErr_Occurred()) {
12571271
Py_DECREF(m);
12581272
m = NULL;
@@ -1397,3 +1411,25 @@ PyOS_AfterFork(void)
13971411
PyThread_ReInitTLS();
13981412
#endif
13991413
}
1414+
1415+
int
1416+
_PyOS_IsMainThread(void)
1417+
{
1418+
#ifdef WITH_THREAD
1419+
return PyThread_get_thread_ident() == main_thread;
1420+
#else
1421+
return 1;
1422+
#endif
1423+
}
1424+
1425+
#ifdef MS_WINDOWS
1426+
void *_PyOS_SigintEvent(void)
1427+
{
1428+
/* Returns a manual-reset event which gets tripped whenever
1429+
SIGINT is received.
1430+
1431+
Python.h does not include windows.h so we do cannot use HANDLE
1432+
as the return type of this function. We use void* instead. */
1433+
return sigint_event;
1434+
}
1435+
#endif

Modules/timemodule.c

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,6 @@
2121
#include <windows.h>
2222
#include "pythread.h"
2323

24-
/* helper to allow us to interrupt sleep() on Windows*/
25-
static HANDLE hInterruptEvent = NULL;
26-
static BOOL WINAPI PyCtrlHandler(DWORD dwCtrlType)
27-
{
28-
SetEvent(hInterruptEvent);
29-
/* allow other default handlers to be called.
30-
Default Python handler will setup the
31-
KeyboardInterrupt exception.
32-
*/
33-
return FALSE;
34-
}
35-
static long main_thread;
36-
3724
#if defined(__BORLANDC__)
3825
/* These overrides not needed for Win32 */
3926
#define timezone _timezone
@@ -955,15 +942,6 @@ PyInit_time(void)
955942
/* Set, or reset, module variables like time.timezone */
956943
PyInit_timezone(m);
957944

958-
#ifdef MS_WINDOWS
959-
/* Helper to allow interrupts for Windows.
960-
If Ctrl+C event delivered while not sleeping
961-
it will be ignored.
962-
*/
963-
main_thread = PyThread_get_thread_ident();
964-
hInterruptEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
965-
SetConsoleCtrlHandler( PyCtrlHandler, TRUE);
966-
#endif /* MS_WINDOWS */
967945
if (!initialized) {
968946
PyStructSequence_InitType(&StructTimeType,
969947
&struct_time_type_desc);
@@ -1036,18 +1014,14 @@ floatsleep(double secs)
10361014
* by Guido, only the main thread can be interrupted.
10371015
*/
10381016
ul_millis = (unsigned long)millisecs;
1039-
if (ul_millis == 0 ||
1040-
main_thread != PyThread_get_thread_ident())
1017+
if (ul_millis == 0 || !_PyOS_IsMainThread())
10411018
Sleep(ul_millis);
10421019
else {
10431020
DWORD rc;
1021+
HANDLE hInterruptEvent = _PyOS_SigintEvent();
10441022
ResetEvent(hInterruptEvent);
10451023
rc = WaitForSingleObject(hInterruptEvent, ul_millis);
10461024
if (rc == WAIT_OBJECT_0) {
1047-
/* Yield to make sure real Python signal
1048-
* handler called.
1049-
*/
1050-
Sleep(1);
10511025
Py_BLOCK_THREADS
10521026
errno = EINTR;
10531027
PyErr_SetFromErrno(PyExc_IOError);

0 commit comments

Comments
 (0)