Skip to content

Commit 30ea2f2

Browse files
author
Michael W. Hudson
committed
This closes patch:
[ 960406 ] unblock signals in threads although the changes do not correspond exactly to any patch attached to that report. Non-main threads no longer have all signals masked. A different interface to readline is used. The handling of signals inside calls to PyOS_Readline is now rather different. These changes are all a bit scary! Review and cross-platform testing much appreciated.
1 parent e3c330b commit 30ea2f2

File tree

10 files changed

+1558
-3077
lines changed

10 files changed

+1558
-3077
lines changed

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ Hannu Krosing
327327
Andrew Kuchling
328328
Vladimir Kushnir
329329
Cameron Laird
330+
Andrew Langmead
330331
Detlef Lannert
331332
Soren Larsen
332333
Piers Lauder

Modules/readline.c

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,66 @@ setup_readline(void)
656656
#endif
657657
}
658658

659+
/* Wrapper around GNU readline that handles signals differently. */
660+
661+
662+
#if defined(HAVE_RL_CALLBACK) && defined(HAVE_SELECT)
663+
664+
static char *completed_input_string;
665+
static void
666+
rlhandler(char *text)
667+
{
668+
completed_input_string = text;
669+
rl_callback_handler_remove();
670+
}
671+
672+
extern PyThreadState* _PyOS_ReadlineTState;
673+
674+
static char *
675+
readline_until_enter_or_signal(char *prompt, int *signal)
676+
{
677+
char * not_done_reading = "";
678+
fd_set selectset;
679+
680+
*signal = 0;
681+
#ifdef HAVE_RL_CATCH_SIGNAL
682+
rl_catch_signals = 0;
683+
#endif
684+
685+
rl_callback_handler_install (prompt, rlhandler);
686+
FD_ZERO(&selectset);
687+
FD_SET(fileno(rl_instream), &selectset);
688+
689+
completed_input_string = not_done_reading;
690+
691+
while(completed_input_string == not_done_reading) {
692+
int has_input;
693+
694+
has_input = select(fileno(rl_instream) + 1, &selectset,
695+
NULL, NULL, NULL);
696+
if(has_input > 0) {
697+
rl_callback_read_char();
698+
}
699+
else if (errno == EINTR) {
700+
int s;
701+
PyEval_RestoreThread(_PyOS_ReadlineTState);
702+
s = PyErr_CheckSignals();
703+
PyThreadState_Swap(NULL);
704+
if (s < 0) {
705+
rl_free_line_state();
706+
rl_cleanup_after_signal();
707+
rl_callback_handler_remove();
708+
*signal = 1;
709+
completed_input_string = NULL;
710+
}
711+
}
712+
}
713+
714+
return completed_input_string;
715+
}
716+
717+
718+
#else
659719

660720
/* Interrupt handler */
661721

@@ -669,14 +729,13 @@ onintr(int sig)
669729
}
670730

671731

672-
/* Wrapper around GNU readline that handles signals differently. */
673-
674732
static char *
675-
call_readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
733+
readline_until_enter_or_signal(char *prompt, int *signal)
676734
{
677-
size_t n;
678-
char *p, *q;
679735
PyOS_sighandler_t old_inthandler;
736+
char *p;
737+
738+
*signal = 0;
680739

681740
old_inthandler = PyOS_setsig(SIGINT, onintr);
682741
if (setjmp(jbuf)) {
@@ -685,8 +744,24 @@ call_readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
685744
sigrelse(SIGINT);
686745
#endif
687746
PyOS_setsig(SIGINT, old_inthandler);
747+
*signal = 1;
688748
return NULL;
689749
}
750+
p = readline(prompt);
751+
PyOS_setsig(SIGINT, old_inthandler);
752+
753+
return p;
754+
}
755+
#endif /*defined(HAVE_RL_CALLBACK) && defined(HAVE_SELECT) */
756+
757+
758+
static char *
759+
call_readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
760+
{
761+
size_t n;
762+
char *p, *q;
763+
int signal;
764+
690765
rl_event_hook = PyOS_InputHook;
691766

692767
if (sys_stdin != rl_instream || sys_stdout != rl_outstream) {
@@ -697,16 +772,22 @@ call_readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
697772
#endif
698773
}
699774

700-
p = readline(prompt);
701-
PyOS_setsig(SIGINT, old_inthandler);
775+
p = readline_until_enter_or_signal(prompt, &signal);
776+
777+
/* we got an interrupt signal */
778+
if(signal) {
779+
return NULL;
780+
}
702781

703-
/* We must return a buffer allocated with PyMem_Malloc. */
782+
/* We got an EOF, return a empty string. */
704783
if (p == NULL) {
705784
p = PyMem_Malloc(1);
706785
if (p != NULL)
707786
*p = '\0';
708787
return p;
709788
}
789+
790+
/* we have a valid line */
710791
n = strlen(p);
711792
if (n > 0) {
712793
char *line;

Parser/myreadline.c

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@
1919
extern char* vms__StdioReadline(FILE *sys_stdin, FILE *sys_stdout, char *prompt);
2020
#endif
2121

22+
23+
PyThreadState* _PyOS_ReadlineTState;
24+
25+
#if WITH_THREAD
26+
#include "pythread.h"
27+
static PyThread_type_lock _PyOS_ReadlineLock = NULL;
28+
#endif
29+
2230
int (*PyOS_InputHook)(void) = NULL;
2331

2432
#ifdef RISCOS
@@ -73,10 +81,13 @@ my_fgets(char *buf, int len, FILE *fp)
7381
}
7482
#ifdef EINTR
7583
if (errno == EINTR) {
76-
if (PyOS_InterruptOccurred()) {
77-
return 1; /* Interrupt */
84+
int s;
85+
PyEval_RestoreThread(_PyOS_ReadlineTState);
86+
s = PyErr_CheckSignals();
87+
PyThreadState_Swap(NULL);
88+
if (s < 0) {
89+
return 1;
7890
}
79-
continue;
8091
}
8192
#endif
8293
if (PyOS_InterruptOccurred()) {
@@ -155,15 +166,32 @@ PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
155166
{
156167
char *rv;
157168

169+
if (_PyOS_ReadlineTState == PyThreadState_GET()) {
170+
PyErr_SetString(PyExc_RuntimeError,
171+
"can't re-enter readline");
172+
return NULL;
173+
}
174+
175+
158176
if (PyOS_ReadlineFunctionPointer == NULL) {
159177
#ifdef __VMS
160178
PyOS_ReadlineFunctionPointer = vms__StdioReadline;
161179
#else
162180
PyOS_ReadlineFunctionPointer = PyOS_StdioReadline;
163181
#endif
164182
}
183+
184+
#if WITH_THREAD
185+
if (_PyOS_ReadlineLock == NULL) {
186+
_PyOS_ReadlineLock = PyThread_allocate_lock();
187+
}
188+
#endif
165189

190+
_PyOS_ReadlineTState = PyThreadState_GET();
166191
Py_BEGIN_ALLOW_THREADS
192+
#if WITH_THREAD
193+
PyThread_acquire_lock(_PyOS_ReadlineLock, 1);
194+
#endif
167195

168196
/* This is needed to handle the unlikely case that the
169197
* interpreter is in interactive mode *and* stdin/out are not
@@ -176,5 +204,12 @@ PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
176204
rv = (*PyOS_ReadlineFunctionPointer)(sys_stdin, sys_stdout,
177205
prompt);
178206
Py_END_ALLOW_THREADS
207+
208+
#if WITH_THREAD
209+
PyThread_release_lock(_PyOS_ReadlineLock);
210+
#endif
211+
212+
_PyOS_ReadlineTState = NULL;
213+
179214
return rv;
180215
}

Python/bltinmodule.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1589,7 +1589,8 @@ builtin_raw_input(PyObject *self, PyObject *args)
15891589
prompt);
15901590
Py_XDECREF(po);
15911591
if (s == NULL) {
1592-
PyErr_SetNone(PyExc_KeyboardInterrupt);
1592+
if (!PyErr_Occurred())
1593+
PyErr_SetNone(PyExc_KeyboardInterrupt);
15931594
return NULL;
15941595
}
15951596
if (*s == '\0') {

Python/ceval.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ static volatile int things_to_do = 0;
318318
int
319319
Py_AddPendingCall(int (*func)(void *), void *arg)
320320
{
321-
static int busy = 0;
321+
static volatile int busy = 0;
322322
int i, j;
323323
/* XXX Begin critical section */
324324
/* XXX If you want this to be safe against nested

Python/pythonrun.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1435,7 +1435,8 @@ err_input(perrdetail *err)
14351435
msg = "EOL while scanning single-quoted string";
14361436
break;
14371437
case E_INTR:
1438-
PyErr_SetNone(PyExc_KeyboardInterrupt);
1438+
if (!PyErr_Occurred())
1439+
PyErr_SetNone(PyExc_KeyboardInterrupt);
14391440
Py_XDECREF(v);
14401441
return;
14411442
case E_NOMEM:

Python/thread_pthread.h

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ PyThread_start_new_thread(void (*func)(void *), void *arg)
119119
{
120120
pthread_t th;
121121
int status;
122-
sigset_t oldmask, newmask;
123122
#if defined(THREAD_STACK_SIZE) || defined(PTHREAD_SYSTEM_SCHED_SUPPORTED)
124123
pthread_attr_t attrs;
125124
#endif
@@ -137,13 +136,6 @@ PyThread_start_new_thread(void (*func)(void *), void *arg)
137136
pthread_attr_setscope(&attrs, PTHREAD_SCOPE_SYSTEM);
138137
#endif
139138

140-
/* Mask all signals in the current thread before creating the new
141-
* thread. This causes the new thread to start with all signals
142-
* blocked.
143-
*/
144-
sigfillset(&newmask);
145-
SET_THREAD_SIGMASK(SIG_BLOCK, &newmask, &oldmask);
146-
147139
status = pthread_create(&th,
148140
#if defined(THREAD_STACK_SIZE) || defined(PTHREAD_SYSTEM_SCHED_SUPPORTED)
149141
&attrs,
@@ -154,9 +146,6 @@ PyThread_start_new_thread(void (*func)(void *), void *arg)
154146
(void *)arg
155147
);
156148

157-
/* Restore signal mask for original thread */
158-
SET_THREAD_SIGMASK(SIG_SETMASK, &oldmask, NULL);
159-
160149
#if defined(THREAD_STACK_SIZE) || defined(PTHREAD_SYSTEM_SCHED_SUPPORTED)
161150
pthread_attr_destroy(&attrs);
162151
#endif

0 commit comments

Comments
 (0)