Skip to content

Commit b654a6c

Browse files
author
Jean-Paul Calderone
committed
Merged revisions 81007 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk ........ r81007 | jean-paul.calderone | 2010-05-08 16:06:02 -0400 (Sat, 08 May 2010) | 1 line Skip signal handler re-installation if it is not necessary. Issue 8354. ........
1 parent a555e2e commit b654a6c

4 files changed

Lines changed: 103 additions & 18 deletions

File tree

Lib/test/test_signal.py

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -256,48 +256,121 @@ def tearDown(self):
256256

257257
class SiginterruptTest(unittest.TestCase):
258258
signum = signal.SIGUSR1
259-
def readpipe_interrupted(self, cb):
259+
260+
def setUp(self):
261+
"""Install a no-op signal handler that can be set to allow
262+
interrupts or not, and arrange for the original signal handler to be
263+
re-installed when the test is finished.
264+
"""
265+
self._cleanups = []
266+
oldhandler = signal.signal(self.signum, lambda x,y: None)
267+
self.addCleanup(signal.signal, self.signum, oldhandler)
268+
269+
270+
def tearDown(self):
271+
"""Run any cleanup functions which have been registered.
272+
"""
273+
for (f, a) in self._cleanups:
274+
f(*a)
275+
276+
277+
def addCleanup(self, f, *a):
278+
"""Register a function to be called at the end of the test method
279+
run.
280+
"""
281+
self._cleanups.append((f, a))
282+
283+
284+
def readpipe_interrupted(self):
285+
"""Perform a read during which a signal will arrive. Return True if the
286+
read is interrupted by the signal and raises an exception. Return False
287+
if it returns normally.
288+
"""
289+
# Create a pipe that can be used for the read. Also clean it up
290+
# when the test is over, since nothing else will (but see below for
291+
# the write end).
260292
r, w = os.pipe()
293+
self.addCleanup(os.close, r)
294+
295+
# Create another process which can send a signal to this one to try
296+
# to interrupt the read.
261297
ppid = os.getpid()
262298
pid = os.fork()
263299

264-
oldhandler = signal.signal(self.signum, lambda x,y: None)
265-
cb()
266-
if pid==0:
267-
# child code: sleep, kill, sleep. and then exit,
268-
# which closes the pipe from which the parent process reads
300+
if pid == 0:
301+
# Child code: sleep to give the parent enough time to enter the
302+
# read() call (there's a race here, but it's really tricky to
303+
# eliminate it); then signal the parent process. Also, sleep
304+
# again to make it likely that the signal is delivered to the
305+
# parent process before the child exits. If the child exits
306+
# first, the write end of the pipe will be closed and the test
307+
# is invalid.
269308
try:
270309
time.sleep(0.2)
271310
os.kill(ppid, self.signum)
272311
time.sleep(0.2)
273312
finally:
313+
# No matter what, just exit as fast as possible now.
274314
exit_subprocess()
275-
276-
try:
315+
else:
316+
# Parent code.
317+
# Make sure the child is eventually reaped, else it'll be a
318+
# zombie for the rest of the test suite run.
319+
self.addCleanup(os.waitpid, pid, 0)
320+
321+
# Close the write end of the pipe. The child has a copy, so
322+
# it's not really closed until the child exits. We need it to
323+
# close when the child exits so that in the non-interrupt case
324+
# the read eventually completes, otherwise we could just close
325+
# it *after* the test.
277326
os.close(w)
278327

328+
# Try the read and report whether it is interrupted or not to
329+
# the caller.
279330
try:
280-
d=os.read(r, 1)
331+
d = os.read(r, 1)
281332
return False
282333
except OSError, err:
283334
if err.errno != errno.EINTR:
284335
raise
285336
return True
286-
finally:
287-
signal.signal(self.signum, oldhandler)
288-
os.waitpid(pid, 0)
289337

290338
def test_without_siginterrupt(self):
291-
i=self.readpipe_interrupted(lambda: None)
292-
self.assertEquals(i, True)
339+
"""If a signal handler is installed and siginterrupt is not called
340+
at all, when that signal arrives, it interrupts a syscall that's in
341+
progress.
342+
"""
343+
i = self.readpipe_interrupted()
344+
self.assertTrue(i)
345+
# Arrival of the signal shouldn't have changed anything.
346+
i = self.readpipe_interrupted()
347+
self.assertTrue(i)
293348

294349
def test_siginterrupt_on(self):
295-
i=self.readpipe_interrupted(lambda: signal.siginterrupt(self.signum, 1))
296-
self.assertEquals(i, True)
350+
"""If a signal handler is installed and siginterrupt is called with
351+
a true value for the second argument, when that signal arrives, it
352+
interrupts a syscall that's in progress.
353+
"""
354+
signal.siginterrupt(self.signum, 1)
355+
i = self.readpipe_interrupted()
356+
self.assertTrue(i)
357+
# Arrival of the signal shouldn't have changed anything.
358+
i = self.readpipe_interrupted()
359+
self.assertTrue(i)
297360

298361
def test_siginterrupt_off(self):
299-
i=self.readpipe_interrupted(lambda: signal.siginterrupt(self.signum, 0))
300-
self.assertEquals(i, False)
362+
"""If a signal handler is installed and siginterrupt is called with
363+
a false value for the second argument, when that signal arrives, it
364+
does not interrupt a syscall that's in progress.
365+
"""
366+
signal.siginterrupt(self.signum, 0)
367+
i = self.readpipe_interrupted()
368+
self.assertFalse(i)
369+
# Arrival of the signal shouldn't have changed anything.
370+
i = self.readpipe_interrupted()
371+
self.assertFalse(i)
372+
373+
301374

302375
class ItimerTest(unittest.TestCase):
303376
def setUp(self):

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ Library
7373
- Issue #4687: Fix accuracy of garbage collection runtimes displayed with
7474
gc.DEBUG_STATS.
7575

76+
- Issue #8354: The siginterrupt setting is now preserved for all signals,
77+
not just SIGCHLD.
78+
7679
- Issue #8577: distutils.sysconfig.get_python_inc() now makes a difference
7780
between the build dir and the source dir when looking for "python.h" or
7881
"Include".

Modules/signalmodule.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,12 @@ signal_handler(int sig_num)
192192
return;
193193
}
194194
#endif
195+
#ifndef HAVE_SIGACTION
196+
/* If the handler was not set up with sigaction, reinstall it. See
197+
* Python/pythonrun.c for the implementation of PyOS_setsig which
198+
* makes this true. See also issue8354. */
195199
PyOS_setsig(sig_num, signal_handler);
200+
#endif
196201
}
197202

198203

Python/pythonrun.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,6 +1867,10 @@ PyOS_sighandler_t
18671867
PyOS_setsig(int sig, PyOS_sighandler_t handler)
18681868
{
18691869
#ifdef HAVE_SIGACTION
1870+
/* Some code in Modules/signalmodule.c depends on sigaction() being
1871+
* used here if HAVE_SIGACTION is defined. Fix that if this code
1872+
* changes to invalidate that assumption.
1873+
*/
18701874
struct sigaction context, ocontext;
18711875
context.sa_handler = handler;
18721876
sigemptyset(&context.sa_mask);

0 commit comments

Comments
 (0)