Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-35568: add 'raise_signal' function #11335

Merged
merged 11 commits into from Jan 8, 2019
7 changes: 7 additions & 0 deletions Doc/library/signal.rst
Expand Up @@ -237,6 +237,13 @@ The :mod:`signal` module defines the following functions:
:func:`sigpending`.


.. function:: raise_signal(signum)

Sends a signal to the calling process. Returns nothing.

.. versionadded:: 3.8


asvetlov marked this conversation as resolved.
Show resolved Hide resolved
.. function:: pthread_kill(thread_id, signalnum)

Send the signal *signalnum* to the thread *thread_id*, another thread in the
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_asyncio/test_windows_events.py
Expand Up @@ -45,7 +45,7 @@ def test_ctrl_c(self):

def SIGINT_after_delay():
time.sleep(1)
_testcapi.raise_signal(signal.SIGINT)
signal.raise_signal(signal.SIGINT)

asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
l = asyncio.get_event_loop()
Expand Down
10 changes: 4 additions & 6 deletions Lib/test/test_faulthandler.py
Expand Up @@ -198,29 +198,27 @@ def test_sigfpe(self):
@skip_segfault_on_android
def test_sigbus(self):
self.check_fatal_error("""
import _testcapi
import faulthandler
import signal

faulthandler.enable()
_testcapi.raise_signal(signal.SIGBUS)
signal.raise_signal(signal.SIGBUS)
""",
6,
5,
'Bus error')

@unittest.skipIf(_testcapi is None, 'need _testcapi')
@unittest.skipUnless(hasattr(signal, 'SIGILL'), 'need signal.SIGILL')
@skip_segfault_on_android
def test_sigill(self):
self.check_fatal_error("""
import _testcapi
import faulthandler
import signal

faulthandler.enable()
_testcapi.raise_signal(signal.SIGILL)
signal.raise_signal(signal.SIGILL)
""",
6,
5,
'Illegal instruction')

def test_fatal_error(self):
Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_posix.py
Expand Up @@ -1596,8 +1596,8 @@ def test_setpgroup_wrong_type(self):
'need signal.pthread_sigmask()')
def test_setsigmask(self):
code = textwrap.dedent("""\
import _testcapi, signal
_testcapi.raise_signal(signal.SIGUSR1)""")
import signal
signal.raise_signal(signal.SIGUSR1)""")

pid = posix.posix_spawn(
sys.executable,
Expand Down Expand Up @@ -1627,8 +1627,8 @@ def test_setsigmask_wrong_type(self):
def test_setsigdef(self):
original_handler = signal.signal(signal.SIGUSR1, signal.SIG_IGN)
code = textwrap.dedent("""\
import _testcapi, signal
_testcapi.raise_signal(signal.SIGUSR1)""")
import signal
signal.raise_signal(signal.SIGUSR1)""")
try:
pid = posix.posix_spawn(
sys.executable,
Expand Down
3 changes: 1 addition & 2 deletions Lib/test/test_regrtest.py
Expand Up @@ -26,9 +26,8 @@
ROOT_DIR = os.path.abspath(os.path.normpath(ROOT_DIR))

TEST_INTERRUPTED = textwrap.dedent("""
from signal import SIGINT
from signal import SIGINT, raise_signal
try:
from _testcapi import raise_signal
raise_signal(SIGINT)
except ImportError:
import os
Expand Down
56 changes: 44 additions & 12 deletions Lib/test/test_signal.py
@@ -1,3 +1,4 @@
import errno
import os
import random
import signal
Expand Down Expand Up @@ -254,7 +255,7 @@ def handler(signum, frame):
signal.set_wakeup_fd(r)
try:
with captured_stderr() as err:
_testcapi.raise_signal(signal.SIGALRM)
signal.raise_signal(signal.SIGALRM)
except ZeroDivisionError:
# An ignored exception should have been printed out on stderr
err = err.getvalue()
Expand Down Expand Up @@ -348,10 +349,9 @@ def handler(signum, frame):

def test_signum(self):
self.check_wakeup("""def test():
import _testcapi
signal.signal(signal.SIGUSR1, handler)
_testcapi.raise_signal(signal.SIGUSR1)
_testcapi.raise_signal(signal.SIGALRM)
signal.raise_signal(signal.SIGUSR1)
signal.raise_signal(signal.SIGALRM)
""", signal.SIGUSR1, signal.SIGALRM)

@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
Expand All @@ -365,8 +365,8 @@ def test_pending(self):
signal.signal(signum2, handler)

signal.pthread_sigmask(signal.SIG_BLOCK, (signum1, signum2))
_testcapi.raise_signal(signum1)
_testcapi.raise_signal(signum2)
signal.raise_signal(signum1)
signal.raise_signal(signum2)
# Unblocking the 2 signals calls the C signal handler twice
signal.pthread_sigmask(signal.SIG_UNBLOCK, (signum1, signum2))
""", signal.SIGUSR1, signal.SIGUSR2, ordered=False)
Expand Down Expand Up @@ -396,7 +396,7 @@ def handler(signum, frame):
write.setblocking(False)
signal.set_wakeup_fd(write.fileno())

_testcapi.raise_signal(signum)
signal.raise_signal(signum)

data = read.recv(1)
if not data:
Expand Down Expand Up @@ -445,7 +445,7 @@ def handler(signum, frame):
write.close()

with captured_stderr() as err:
_testcapi.raise_signal(signum)
signal.raise_signal(signum)

err = err.getvalue()
if ('Exception ignored when trying to {action} to the signal wakeup fd'
Expand Down Expand Up @@ -519,7 +519,7 @@ def handler(signum, frame):
signal.set_wakeup_fd(write.fileno())

with captured_stderr() as err:
_testcapi.raise_signal(signum)
signal.raise_signal(signum)

err = err.getvalue()
if msg not in err:
Expand All @@ -530,7 +530,7 @@ def handler(signum, frame):
signal.set_wakeup_fd(write.fileno(), warn_on_full_buffer=True)

with captured_stderr() as err:
_testcapi.raise_signal(signum)
signal.raise_signal(signum)

err = err.getvalue()
if msg not in err:
Expand All @@ -541,7 +541,7 @@ def handler(signum, frame):
signal.set_wakeup_fd(write.fileno(), warn_on_full_buffer=False)

with captured_stderr() as err:
_testcapi.raise_signal(signum)
signal.raise_signal(signum)

err = err.getvalue()
if err != "":
Expand All @@ -553,7 +553,7 @@ def handler(signum, frame):
signal.set_wakeup_fd(write.fileno())

with captured_stderr() as err:
_testcapi.raise_signal(signum)
signal.raise_signal(signum)

err = err.getvalue()
if msg not in err:
Expand Down Expand Up @@ -1214,6 +1214,38 @@ def handler(signum, frame):
# Python handler
self.assertEqual(len(sigs), N, "Some signals were lost")

class RaiseSignalTest(unittest.TestCase):

def test_sigint(self):
try:
signal.raise_signal(signal.SIGINT)
self.fail("Expected KeyInterrupt")
except KeyboardInterrupt:
pass

@unittest.skipIf(sys.platform != "win32", "Windows specific test")
def test_invalid_argument(self):
try:
SIGHUP = 1 # not supported on win32
signal.raise_signal(SIGHUP)
self.fail("OSError (Invalid argument) expected")
except OSError as e:
if e.errno == errno.EINVAL:
pass
else:
raise

def test_handler(self):
is_ok = False
def handler(a, b):
nonlocal is_ok
is_ok = True
old_signal = signal.signal(signal.SIGINT, handler)
self.addCleanup(signal.signal, signal.SIGINT, old_signal)

signal.raise_signal(signal.SIGINT)
self.assertTrue(is_ok)


def tearDownModule():
support.reap_children()
Expand Down
@@ -0,0 +1 @@
Expose ``raise(signum)`` as `raise_signal`
21 changes: 0 additions & 21 deletions Modules/_testcapimodule.c
Expand Up @@ -3859,25 +3859,6 @@ call_in_temporary_c_thread(PyObject *self, PyObject *callback)
return res;
}

static PyObject*
test_raise_signal(PyObject* self, PyObject *args)
{
int signum, err;

if (!PyArg_ParseTuple(args, "i:raise_signal", &signum)) {
return NULL;
}

err = raise(signum);
if (err)
return PyErr_SetFromErrno(PyExc_OSError);

if (PyErr_CheckSignals() < 0)
return NULL;

Py_RETURN_NONE;
}

/* marshal */

static PyObject*
Expand Down Expand Up @@ -4908,8 +4889,6 @@ static PyMethodDef TestMethods[] = {
{"docstring_with_signature_with_defaults",
(PyCFunction)test_with_docstring, METH_NOARGS,
docstring_with_signature_with_defaults},
{"raise_signal",
(PyCFunction)test_raise_signal, METH_VARARGS},
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_O,
PyDoc_STR("set_error_class(error_class) -> None")},
{"pymarshal_write_long_to_file",
Expand Down
35 changes: 34 additions & 1 deletion Modules/clinic/signalmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions Modules/signalmodule.c
Expand Up @@ -390,6 +390,31 @@ signal_pause_impl(PyObject *module)

#endif

/*[clinic input]
signal.raise_signal

signalnum: int
/

Send a signal to the executing process.
[clinic start generated code]*/

static PyObject *
signal_raise_signal_impl(PyObject *module, int signalnum)
/*[clinic end generated code: output=e2b014220aa6111d input=e90c0f9a42358de6]*/
{
int err;
Py_BEGIN_ALLOW_THREADS
vladima marked this conversation as resolved.
Show resolved Hide resolved
_Py_BEGIN_SUPPRESS_IPH
err = raise(signalnum);
_Py_END_SUPPRESS_IPH
Py_END_ALLOW_THREADS

if (err) {
return PyErr_SetFromErrno(PyExc_OSError);
}
Py_RETURN_NONE;
}

/*[clinic input]
signal.signal
Expand Down Expand Up @@ -1208,6 +1233,7 @@ static PyMethodDef signal_methods[] = {
SIGNAL_SETITIMER_METHODDEF
SIGNAL_GETITIMER_METHODDEF
SIGNAL_SIGNAL_METHODDEF
SIGNAL_RAISE_SIGNAL_METHODDEF
SIGNAL_STRSIGNAL_METHODDEF
SIGNAL_GETSIGNAL_METHODDEF
{"set_wakeup_fd", (PyCFunction)(void(*)(void))signal_set_wakeup_fd, METH_VARARGS | METH_KEYWORDS, set_wakeup_fd_doc},
Expand Down