- Executing a pending :ref:`remote debugger <remote-debugging>` script.
+ - Raise the exception set by :c:func:`PyThreadState_SetAsyncExc`.
+
If any handler raises an exception, immediately return ``-1`` with that
exception set.
Any remaining interruptions are left to be processed on the next
This function may now execute a remote debugger script, if remote
debugging is enabled.
+ .. versionchanged:: next
+ The exception set by :c:func:`PyThreadState_SetAsyncExc` is now raised.
+
.. c:function:: void PyErr_SetInterrupt()
.. c:function:: int PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc)
- Asynchronously raise an exception in a thread. The *id* argument is the thread
- id of the target thread; *exc* is the exception object to be raised. This
- function does not steal any references to *exc*. To prevent naive misuse, you
- must write your own C extension to call this. Must be called with an :term:`attached thread state`.
- Returns the number of thread states modified; this is normally one, but will be
- zero if the thread id isn't found. If *exc* is ``NULL``, the pending
- exception (if any) for the thread is cleared. This raises no exceptions.
+ Schedule an exception to be raised asynchronously in a thread.
+ If the thread has a previously scheduled exception, it is overwritten.
+
+ The *id* argument is the thread id of the target thread, as returned by
+ :c:func:`PyThread_get_thread_ident`.
+ *exc* is the class of the exception to be raised, or ``NULL`` to clear
+ the pending exception (if any).
+
+ Return the number of affected thread states.
+ This is normally ``1`` if *id* is found, even when no change was
+ made (the given *exc* was already pending, or *exc* is ``NULL`` but
+ no exception is pending).
+ If the thread id isn't found, return ``0``. This raises no exceptions.
+
+ To prevent naive misuse, you must write your own C extension to call this.
+ This function must be called with an :term:`attached thread state`.
+ This function does not steal any references to *exc*.
+ This function does not necessarily interrupt system calls such as
+ :py:func:`~time.sleep`.
.. versionchanged:: 3.7
The type of the *id* parameter changed from :c:expr:`long` to
:term:`attached thread state`.
.. seealso::
- :py:func:`threading.get_ident`
+ :py:func:`threading.get_ident` and :py:attr:`threading.Thread.ident`
+ expose this identifier to Python.
.. c:function:: PyObject *PyThread_GetInfo(void)
and asynchronous exception */
PyAPI_FUNC(int) _Py_HandlePending(PyThreadState *tstate);
+/* Raise exception set by PyThreadState_SetAsyncExc, if any */
+PyAPI_FUNC(int) _PyEval_RaiseAsyncExc(PyThreadState *tstate);
+
extern PyObject * _PyEval_GetFrameLocals(void);
typedef PyObject *(*conversion_func)(PyObject *);
t.join()
# else the thread is still running, and we have no way to kill it
+ @cpython_only
+ @unittest.skipUnless(hasattr(signal, "pthread_kill"), "need pthread_kill")
+ @unittest.skipUnless(hasattr(signal, "SIGUSR1"), "need SIGUSR1")
+ def test_PyThreadState_SetAsyncExc_interrupts_sleep(self):
+ _testcapi = import_module("_testlimitedcapi")
+
+ worker_started = threading.Event()
+
+ class InjectedException(Exception):
+ """Custom exception for testing"""
+
+ caught_exception = None
+
+ def catch_exception():
+ nonlocal caught_exception
+ day_as_seconds = 60 * 60 * 24
+ try:
+ worker_started.set()
+ time.sleep(day_as_seconds)
+ except InjectedException as exc:
+ caught_exception = exc
+
+ thread = threading.Thread(target=catch_exception)
+ thread.start()
+ worker_started.wait()
+
+ signal.signal(signal.SIGUSR1, lambda sig, frame: None)
+
+ result = _testcapi.threadstate_set_async_exc(
+ thread.ident, InjectedException)
+ self.assertEqual(result, 1)
+
+ for _ in support.sleeping_retry(support.SHORT_TIMEOUT):
+ if not thread.is_alive():
+ break
+ try:
+ signal.pthread_kill(thread.ident, signal.SIGUSR1)
+ except OSError:
+ # The thread might have terminated between the is_alive check
+ # and the pthread_kill
+ break
+
+ thread.join()
+ signal.signal(signal.SIGUSR1, signal.SIG_DFL)
+
+ self.assertIsInstance(caught_exception, InjectedException)
+
def test_limbo_cleanup(self):
# Issue 7481: Failure to start thread should cleanup the limbo map.
def fail_new_thread(*args, **kwargs):
--- /dev/null
+:c:func:`PyErr_CheckSignals` now raises the exception scheduled by
+:c:func:`PyThreadState_SetAsyncExc`, if any.
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c
-@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c
+@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
if (_PyTestLimitedCAPI_Init_Sys(mod) < 0) {
return NULL;
}
+ if (_PyTestLimitedCAPI_Init_ThreadState(mod) < 0) {
+ return NULL;
+ }
if (_PyTestLimitedCAPI_Init_Tuple(mod) < 0) {
return NULL;
}
int _PyTestLimitedCAPI_Init_PyOS(PyObject *module);
int _PyTestLimitedCAPI_Init_Set(PyObject *module);
int _PyTestLimitedCAPI_Init_Sys(PyObject *module);
+int _PyTestLimitedCAPI_Init_ThreadState(PyObject *module);
int _PyTestLimitedCAPI_Init_Tuple(PyObject *module);
int _PyTestLimitedCAPI_Init_Unicode(PyObject *module);
int _PyTestLimitedCAPI_Init_VectorcallLimited(PyObject *module);
--- /dev/null
+#include "parts.h"
+#include "util.h"
+
+static PyObject *
+threadstate_set_async_exc(PyObject *module, PyObject *args)
+{
+ unsigned long id;
+ PyObject *exc;
+ if (!PyArg_ParseTuple(args, "kO", &id, &exc)) {
+ return NULL;
+ }
+ int result = PyThreadState_SetAsyncExc(id, exc);
+ return PyLong_FromLong(result);
+}
+
+static PyMethodDef test_methods[] = {
+ {"threadstate_set_async_exc", threadstate_set_async_exc, METH_VARARGS, NULL},
+ {NULL},
+};
+
+int
+_PyTestLimitedCAPI_Init_ThreadState(PyObject *m)
+{
+ return PyModule_AddFunctions(m, test_methods);
+}
Python code to ensure signals are handled. Checking for the GC here
allows long running native code to clean cycles created using the C-API
even if it doesn't run the evaluation loop */
- if (_Py_eval_breaker_bit_is_set(tstate, _PY_GC_SCHEDULED_BIT)) {
+ uintptr_t breaker = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker);
+ if (breaker & _PY_GC_SCHEDULED_BIT) {
_Py_unset_eval_breaker_bit(tstate, _PY_GC_SCHEDULED_BIT);
_Py_RunGC(tstate);
}
+ if (breaker & _PY_ASYNC_EXCEPTION_BIT) {
+ if (_PyEval_RaiseAsyncExc(tstate) < 0) {
+ return -1;
+ }
+ }
#if defined(Py_REMOTE_DEBUG) && defined(Py_SUPPORTS_REMOTE_DEBUG)
_PyRunRemoteDebugger(tstate);
#endif
- if (!_Py_ThreadCanHandleSignals(tstate->interp)) {
- return 0;
+ if (_Py_ThreadCanHandleSignals(tstate->interp)) {
+ if (_PyErr_CheckSignalsTstate(tstate) < 0) {
+ return -1;
+ }
}
- return _PyErr_CheckSignalsTstate(tstate);
+ return 0;
}
<ClCompile Include="..\Modules\_testlimitedcapi\pyos.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\set.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\sys.c" />
+ <ClCompile Include="..\Modules\_testlimitedcapi\threadstate.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\tuple.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\unicode.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\vectorcall_limited.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\set.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\sys.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\testcapi_long.h" />
+ <ClCompile Include="..\Modules\_testlimitedcapi\threadstate.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\tuple.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\unicode.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\vectorcall_limited.c" />
/* Check for asynchronous exception. */
if ((breaker & _PY_ASYNC_EXCEPTION_BIT) != 0) {
- _Py_unset_eval_breaker_bit(tstate, _PY_ASYNC_EXCEPTION_BIT);
- PyObject *exc = _Py_atomic_exchange_ptr(&tstate->async_exc, NULL);
- if (exc != NULL) {
- _PyErr_SetNone(tstate, exc);
- Py_DECREF(exc);
+ if (_PyEval_RaiseAsyncExc(tstate) < 0) {
return -1;
}
}
return 0;
}
+
+int
+_PyEval_RaiseAsyncExc(PyThreadState *tstate)
+{
+ assert(tstate != NULL);
+ assert(tstate == _PyThreadState_GET());
+ _Py_unset_eval_breaker_bit(tstate, _PY_ASYNC_EXCEPTION_BIT);
+ PyObject *exc = _Py_atomic_exchange_ptr(&tstate->async_exc, NULL);
+ if (exc != NULL) {
+ _PyErr_SetNone(tstate, exc);
+ Py_DECREF(exc);
+ return -1;
+ }
+ return 0;
+}