mirror of https://github.com/python/cpython
Skip signal handler re-installation if it is not necessary. Issue 8354.
This commit is contained in:
parent
0712b5651a
commit
e54ddf1ed2
|
@ -255,48 +255,105 @@ class WakeupSignalTests(unittest.TestCase):
|
||||||
|
|
||||||
class SiginterruptTest(unittest.TestCase):
|
class SiginterruptTest(unittest.TestCase):
|
||||||
signum = signal.SIGUSR1
|
signum = signal.SIGUSR1
|
||||||
def readpipe_interrupted(self, cb):
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Install a no-op signal handler that can be set to allow
|
||||||
|
interrupts or not, and arrange for the original signal handler to be
|
||||||
|
re-installed when the test is finished.
|
||||||
|
"""
|
||||||
|
oldhandler = signal.signal(self.signum, lambda x,y: None)
|
||||||
|
self.addCleanup(signal.signal, self.signum, oldhandler)
|
||||||
|
|
||||||
|
def readpipe_interrupted(self):
|
||||||
|
"""Perform a read during which a signal will arrive. Return True if the
|
||||||
|
read is interrupted by the signal and raises an exception. Return False
|
||||||
|
if it returns normally.
|
||||||
|
"""
|
||||||
|
# Create a pipe that can be used for the read. Also clean it up
|
||||||
|
# when the test is over, since nothing else will (but see below for
|
||||||
|
# the write end).
|
||||||
r, w = os.pipe()
|
r, w = os.pipe()
|
||||||
|
self.addCleanup(os.close, r)
|
||||||
|
|
||||||
|
# Create another process which can send a signal to this one to try
|
||||||
|
# to interrupt the read.
|
||||||
ppid = os.getpid()
|
ppid = os.getpid()
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
|
|
||||||
oldhandler = signal.signal(self.signum, lambda x,y: None)
|
if pid == 0:
|
||||||
cb()
|
# Child code: sleep to give the parent enough time to enter the
|
||||||
if pid==0:
|
# read() call (there's a race here, but it's really tricky to
|
||||||
# child code: sleep, kill, sleep. and then exit,
|
# eliminate it); then signal the parent process. Also, sleep
|
||||||
# which closes the pipe from which the parent process reads
|
# again to make it likely that the signal is delivered to the
|
||||||
|
# parent process before the child exits. If the child exits
|
||||||
|
# first, the write end of the pipe will be closed and the test
|
||||||
|
# is invalid.
|
||||||
try:
|
try:
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
os.kill(ppid, self.signum)
|
os.kill(ppid, self.signum)
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
finally:
|
finally:
|
||||||
|
# No matter what, just exit as fast as possible now.
|
||||||
exit_subprocess()
|
exit_subprocess()
|
||||||
|
else:
|
||||||
|
# Parent code.
|
||||||
|
# Make sure the child is eventually reaped, else it'll be a
|
||||||
|
# zombie for the rest of the test suite run.
|
||||||
|
self.addCleanup(os.waitpid, pid, 0)
|
||||||
|
|
||||||
try:
|
# Close the write end of the pipe. The child has a copy, so
|
||||||
|
# it's not really closed until the child exits. We need it to
|
||||||
|
# close when the child exits so that in the non-interrupt case
|
||||||
|
# the read eventually completes, otherwise we could just close
|
||||||
|
# it *after* the test.
|
||||||
os.close(w)
|
os.close(w)
|
||||||
|
|
||||||
|
# Try the read and report whether it is interrupted or not to
|
||||||
|
# the caller.
|
||||||
try:
|
try:
|
||||||
d=os.read(r, 1)
|
d = os.read(r, 1)
|
||||||
return False
|
return False
|
||||||
except OSError, err:
|
except OSError, err:
|
||||||
if err.errno != errno.EINTR:
|
if err.errno != errno.EINTR:
|
||||||
raise
|
raise
|
||||||
return True
|
return True
|
||||||
finally:
|
|
||||||
signal.signal(self.signum, oldhandler)
|
|
||||||
os.waitpid(pid, 0)
|
|
||||||
|
|
||||||
def test_without_siginterrupt(self):
|
def test_without_siginterrupt(self):
|
||||||
i=self.readpipe_interrupted(lambda: None)
|
"""If a signal handler is installed and siginterrupt is not called
|
||||||
self.assertEquals(i, True)
|
at all, when that signal arrives, it interrupts a syscall that's in
|
||||||
|
progress.
|
||||||
|
"""
|
||||||
|
i = self.readpipe_interrupted()
|
||||||
|
self.assertTrue(i)
|
||||||
|
# Arrival of the signal shouldn't have changed anything.
|
||||||
|
i = self.readpipe_interrupted()
|
||||||
|
self.assertTrue(i)
|
||||||
|
|
||||||
def test_siginterrupt_on(self):
|
def test_siginterrupt_on(self):
|
||||||
i=self.readpipe_interrupted(lambda: signal.siginterrupt(self.signum, 1))
|
"""If a signal handler is installed and siginterrupt is called with
|
||||||
self.assertEquals(i, True)
|
a true value for the second argument, when that signal arrives, it
|
||||||
|
interrupts a syscall that's in progress.
|
||||||
|
"""
|
||||||
|
signal.siginterrupt(self.signum, 1)
|
||||||
|
i = self.readpipe_interrupted()
|
||||||
|
self.assertTrue(i)
|
||||||
|
# Arrival of the signal shouldn't have changed anything.
|
||||||
|
i = self.readpipe_interrupted()
|
||||||
|
self.assertTrue(i)
|
||||||
|
|
||||||
def test_siginterrupt_off(self):
|
def test_siginterrupt_off(self):
|
||||||
i=self.readpipe_interrupted(lambda: signal.siginterrupt(self.signum, 0))
|
"""If a signal handler is installed and siginterrupt is called with
|
||||||
self.assertEquals(i, False)
|
a false value for the second argument, when that signal arrives, it
|
||||||
|
does not interrupt a syscall that's in progress.
|
||||||
|
"""
|
||||||
|
signal.siginterrupt(self.signum, 0)
|
||||||
|
i = self.readpipe_interrupted()
|
||||||
|
self.assertFalse(i)
|
||||||
|
# Arrival of the signal shouldn't have changed anything.
|
||||||
|
i = self.readpipe_interrupted()
|
||||||
|
self.assertFalse(i)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ItimerTest(unittest.TestCase):
|
class ItimerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -96,6 +96,9 @@ Library
|
||||||
- Issue #4687: Fix accuracy of garbage collection runtimes displayed with
|
- Issue #4687: Fix accuracy of garbage collection runtimes displayed with
|
||||||
gc.DEBUG_STATS.
|
gc.DEBUG_STATS.
|
||||||
|
|
||||||
|
- Issue #8354: The siginterrupt setting is now preserved for all signals,
|
||||||
|
not just SIGCHLD.
|
||||||
|
|
||||||
- Issue #7192: webbrowser.get("firefox") now wors on Mac OS X, as does
|
- Issue #7192: webbrowser.get("firefox") now wors on Mac OS X, as does
|
||||||
webbrowser.get("safari").
|
webbrowser.get("safari").
|
||||||
|
|
||||||
|
|
|
@ -198,7 +198,12 @@ signal_handler(int sig_num)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef HAVE_SIGACTION
|
||||||
|
/* If the handler was not set up with sigaction, reinstall it. See
|
||||||
|
* Python/pythonrun.c for the implementation of PyOS_setsig which
|
||||||
|
* makes this true. See also issue8354. */
|
||||||
PyOS_setsig(sig_num, signal_handler);
|
PyOS_setsig(sig_num, signal_handler);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1862,6 +1862,10 @@ PyOS_sighandler_t
|
||||||
PyOS_setsig(int sig, PyOS_sighandler_t handler)
|
PyOS_setsig(int sig, PyOS_sighandler_t handler)
|
||||||
{
|
{
|
||||||
#ifdef HAVE_SIGACTION
|
#ifdef HAVE_SIGACTION
|
||||||
|
/* Some code in Modules/signalmodule.c depends on sigaction() being
|
||||||
|
* used here if HAVE_SIGACTION is defined. Fix that if this code
|
||||||
|
* changes to invalidate that assumption.
|
||||||
|
*/
|
||||||
struct sigaction context, ocontext;
|
struct sigaction context, ocontext;
|
||||||
context.sa_handler = handler;
|
context.sa_handler = handler;
|
||||||
sigemptyset(&context.sa_mask);
|
sigemptyset(&context.sa_mask);
|
||||||
|
|
Loading…
Reference in New Issue