Skip to content

Commit 0951442

Browse files
author
guido.van.rossum
committed
Patch #1583 by Adam Olsen.
This adds signal.set_wakeup_fd(fd) which sets a file descriptor to which a zero byte will be written whenever a C exception handler runs. I added a simple C API as well, PySignal_SetWakeupFd(fd). git-svn-id: http://svn.python.org/projects/python/trunk@59574 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent 071df5d commit 0951442

File tree

5 files changed

+127
-2
lines changed

5 files changed

+127
-2
lines changed

Doc/c-api/exceptions.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,16 @@ is a separate error indicator for each thread.
375375
.. % thread.interrupt_main() (used from IDLE), so it's still needed.
376376
377377
378+
.. cfunction:: int PySignal_SetWakeupFd(int fd)
379+
380+
This utility function specifies a file descriptor to which a ``'\0'`` byte will
381+
be written whenever a signal is received. It returns the previous such file
382+
descriptor. The value ``-1`` disables the feature; this is the initial state.
383+
This is equivalent to :func:`signal.set_wakeup_fd` in Python, but without any
384+
error checking. *fd* should be a valid file descriptor. The function should
385+
only be called from the main thread.
386+
387+
378388
.. cfunction:: PyObject* PyErr_NewException(char *name, PyObject *base, PyObject *dict)
379389

380390
This utility function creates and returns a new exception object. The *name*

Doc/library/signal.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,20 @@ The :mod:`signal` module defines the following functions:
110110
:manpage:`signal(2)`.)
111111

112112

113+
.. function:: set_wakeup_fd(fd)
114+
115+
Set the wakeup fd to *fd*. When a signal is received, a ``'\0'`` byte is
116+
written to the fd. This can be used by a library to wakeup a poll or select
117+
call, allowing the signal to be fully processed.
118+
119+
The old wakeup fd is returned. *fd* must be non-blocking. It is up to the
120+
library to remove any bytes before calling poll or select again.
121+
122+
When threads are enabled, this function can only be called from the main thread;
123+
attempting to call it from other threads will cause a :exc:`ValueError`
124+
exception to be raised.
125+
126+
113127
.. function:: signal(signalnum, handler)
114128

115129
Set the handler for signal *signalnum* to the function *handler*. *handler* can

Include/pyerrors.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ PyAPI_FUNC(int) PyErr_WarnExplicit(PyObject *, const char *,
238238
PyAPI_FUNC(int) PyErr_CheckSignals(void);
239239
PyAPI_FUNC(void) PyErr_SetInterrupt(void);
240240

241+
/* In signalmodule.c */
242+
int PySignal_SetWakeupFd(int fd);
243+
241244
/* Support for adding program text to SyntaxErrors */
242245
PyAPI_FUNC(void) PyErr_SyntaxLocation(const char *, int);
243246
PyAPI_FUNC(PyObject *) PyErr_ProgramText(const char *, int);

Lib/test/test_signal.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,59 @@ def test_setting_signal_handler_to_none_raises_error(self):
165165
self.assertRaises(TypeError, signal.signal,
166166
signal.SIGUSR1, None)
167167

168+
class WakeupSignalTests(unittest.TestCase):
169+
TIMEOUT_FULL = 10
170+
TIMEOUT_HALF = 5
171+
172+
def test_wakeup_fd_early(self):
173+
import select
174+
175+
signal.alarm(1)
176+
before_time = time.time()
177+
# We attempt to get a signal during the sleep,
178+
# before select is called
179+
time.sleep(self.TIMEOUT_FULL)
180+
mid_time = time.time()
181+
self.assert_(mid_time - before_time < self.TIMEOUT_HALF)
182+
select.select([self.read], [], [], self.TIMEOUT_FULL)
183+
after_time = time.time()
184+
self.assert_(after_time - mid_time < self.TIMEOUT_HALF)
185+
186+
def test_wakeup_fd_during(self):
187+
import select
188+
189+
signal.alarm(1)
190+
before_time = time.time()
191+
# We attempt to get a signal during the select call
192+
self.assertRaises(select.error, select.select,
193+
[self.read], [], [], self.TIMEOUT_FULL)
194+
after_time = time.time()
195+
self.assert_(after_time - before_time < self.TIMEOUT_HALF)
196+
197+
def setUp(self):
198+
import fcntl
199+
200+
self.alrm = signal.signal(signal.SIGALRM, lambda x,y:None)
201+
self.read, self.write = os.pipe()
202+
flags = fcntl.fcntl(self.write, fcntl.F_GETFL, 0)
203+
flags = flags | os.O_NONBLOCK
204+
fcntl.fcntl(self.write, fcntl.F_SETFL, flags)
205+
self.old_wakeup = signal.set_wakeup_fd(self.write)
206+
207+
def tearDown(self):
208+
signal.set_wakeup_fd(self.old_wakeup)
209+
os.close(self.read)
210+
os.close(self.write)
211+
signal.signal(signal.SIGALRM, self.alrm)
212+
213+
168214
def test_main():
169215
if sys.platform[:3] in ('win', 'os2') or sys.platform == 'riscos':
170216
raise test_support.TestSkipped("Can't test signal on %s" % \
171217
sys.platform)
172218

173-
test_support.run_unittest(BasicSignalTests, InterProcessSignalTests)
219+
test_support.run_unittest(BasicSignalTests, InterProcessSignalTests,
220+
WakeupSignalTests)
174221

175222

176223
if __name__ == "__main__":

Modules/signalmodule.c

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
#include <signal.h>
1414

15+
#include <sys/stat.h>
16+
1517
#ifndef SIG_ERR
1618
#define SIG_ERR ((PyOS_sighandler_t)(-1))
1719
#endif
@@ -75,6 +77,8 @@ static struct {
7577
PyObject *func;
7678
} Handlers[NSIG];
7779

80+
static sig_atomic_t wakeup_fd = -1;
81+
7882
/* Speed up sigcheck() when none tripped */
7983
static volatile sig_atomic_t is_tripped = 0;
8084

@@ -128,6 +132,8 @@ signal_handler(int sig_num)
128132
cleared in PyErr_CheckSignals() before .tripped. */
129133
is_tripped = 1;
130134
Py_AddPendingCall(checksignals_witharg, NULL);
135+
if (wakeup_fd != -1)
136+
write(wakeup_fd, "\0", 1);
131137
#ifdef WITH_THREAD
132138
}
133139
#endif
@@ -267,18 +273,63 @@ None -- if an unknown handler is in effect\n\
267273
anything else -- the callable Python object used as a handler");
268274

269275

276+
static PyObject *
277+
signal_set_wakeup_fd(PyObject *self, PyObject *args)
278+
{
279+
struct stat buf;
280+
int fd, old_fd;
281+
if (!PyArg_ParseTuple(args, "i:set_wakeup_fd", &fd))
282+
return NULL;
283+
#ifdef WITH_THREAD
284+
if (PyThread_get_thread_ident() != main_thread) {
285+
PyErr_SetString(PyExc_ValueError,
286+
"set_wakeup_fd only works in main thread");
287+
return NULL;
288+
}
289+
#endif
290+
if (fd != -1 && fstat(fd, &buf) != 0) {
291+
PyErr_SetString(PyExc_ValueError, "invalid fd");
292+
return NULL;
293+
}
294+
old_fd = wakeup_fd;
295+
wakeup_fd = fd;
296+
return PyLong_FromLong(old_fd);
297+
}
298+
299+
PyDoc_STRVAR(set_wakeup_fd_doc,
300+
"set_wakeup_fd(fd) -> fd\n\
301+
\n\
302+
Sets the fd to be written to (with '\\0') when a signal\n\
303+
comes in. A library can use this to wakeup select or poll.\n\
304+
The previous fd is returned.\n\
305+
\n\
306+
The fd must be non-blocking.");
307+
308+
/* C API for the same, without all the error checking */
309+
int
310+
PySignal_SetWakeupFd(int fd)
311+
{
312+
int old_fd = wakeup_fd;
313+
if (fd < 0)
314+
fd = -1;
315+
wakeup_fd = fd;
316+
return old_fd;
317+
}
318+
319+
270320
/* List of functions defined in the module */
271321
static PyMethodDef signal_methods[] = {
272322
#ifdef HAVE_ALARM
273323
{"alarm", signal_alarm, METH_VARARGS, alarm_doc},
274324
#endif
275325
{"signal", signal_signal, METH_VARARGS, signal_doc},
276326
{"getsignal", signal_getsignal, METH_VARARGS, getsignal_doc},
327+
{"set_wakeup_fd", signal_set_wakeup_fd, METH_VARARGS, set_wakeup_fd_doc},
277328
#ifdef HAVE_PAUSE
278329
{"pause", (PyCFunction)signal_pause,
279330
METH_NOARGS,pause_doc},
280331
#endif
281-
{"default_int_handler", signal_default_int_handler,
332+
{"default_int_handler", signal_default_int_handler,
282333
METH_VARARGS, default_int_handler_doc},
283334
{NULL, NULL} /* sentinel */
284335
};

0 commit comments

Comments
 (0)