Skip to content

Commit 1656f3c

Browse files
committed
Merge branch 'pythongh-119333-exception' into HEAD
2 parents ac65639 + 5f8590c commit 1656f3c

File tree

5 files changed

+31
-24
lines changed

5 files changed

+31
-24
lines changed

Doc/c-api/contextvars.rst

+4-9
Original file line numberDiff line numberDiff line change
@@ -127,23 +127,18 @@ Context object management functions:
127127
128128
.. versionadded:: 3.14
129129
130-
.. c:type:: int (*PyContext_WatchCallback)(PyContextEvent event, PyObject* ctx)
130+
.. c:type:: void (*PyContext_WatchCallback)(PyContextEvent event, PyObject* ctx)
131131
132132
Type of a context object watcher callback function.
133133
If *event* is ``Py_CONTEXT_EVENT_ENTER``, then the callback is invoked
134134
after *ctx* has been set as the current context for the current thread.
135135
Otherwise, the callback is invoked before the deactivation of *ctx* as the current context
136136
and the restoration of the previous contex object for the current thread.
137137
138-
If the callback returns with an exception set, it must return ``-1``; this
139-
exception will be printed as an unraisable exception using
140-
:c:func:`PyErr_FormatUnraisable`. Otherwise it should return ``0``.
138+
Any pending exception is cleared before the callback is called and restored
139+
after the callback returns.
141140
142-
There may already be a pending exception set on entry to the callback. In
143-
this case, the callback should return ``0`` with the same exception still
144-
set. This means the callback may not call any other API that can set an
145-
exception unless it saves and clears the exception state first, and restores
146-
it before returning.
141+
If the callback raises an exception it will be ignored.
147142
148143
.. versionadded:: 3.14
149144

Include/cpython/context.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@ typedef enum {
3838
* The callback is invoked with the event and a reference to
3939
* the context after its entered and before its exited.
4040
*
41-
* if the callback returns with an exception set, it must return -1. Otherwise
42-
* it should return 0
41+
* Any pending exception is cleared before the callback is called and restored
42+
* after the callback returns.
43+
*
44+
* If the callback raises an exception it will be ignored.
4345
*/
44-
typedef int (*PyContext_WatchCallback)(PyContextEvent, PyObject *);
46+
typedef void (*PyContext_WatchCallback)(PyContextEvent, PyObject *);
4547

4648
/*
4749
* Register a per-interpreter callback that will be invoked for context object

Lib/test/test_capi/test_watchers.py

+10
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,16 @@ def _in_context(stack):
640640
ctx.run(_in_context, stack)
641641
self.assertEqual(str(cm.unraisable.exc_value), "boom!")
642642

643+
def test_exception_save(self):
644+
with self.context_watcher(2):
645+
with catch_unraisable_exception() as cm:
646+
def _in_context():
647+
raise RuntimeError("test")
648+
649+
with self.assertRaisesRegex(RuntimeError, "test"):
650+
contextvars.copy_context().run(_in_context)
651+
self.assertEqual(str(cm.unraisable.exc_value), "boom!")
652+
643653
def test_clear_out_of_range_watcher_id(self):
644654
with self.assertRaisesRegex(ValueError, r"Invalid context watcher ID -1"):
645655
_testcapi.clear_context_watcher(-1)

Modules/_testcapi/watchers.c

+8-11
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ static int context_watcher_ids[NUM_CONTEXT_WATCHERS] = {-1, -1};
629629
static int num_context_object_enter_events[NUM_CONTEXT_WATCHERS] = {0, 0};
630630
static int num_context_object_exit_events[NUM_CONTEXT_WATCHERS] = {0, 0};
631631

632-
static int
632+
static void
633633
handle_context_watcher_event(int which_watcher, PyContextEvent event, PyObject *ctx) {
634634
if (event == Py_CONTEXT_EVENT_ENTER) {
635635
num_context_object_enter_events[which_watcher]++;
@@ -638,30 +638,27 @@ handle_context_watcher_event(int which_watcher, PyContextEvent event, PyObject *
638638
num_context_object_exit_events[which_watcher]++;
639639
}
640640
else {
641-
return -1;
641+
Py_UNREACHABLE();
642642
}
643-
return 0;
644643
}
645644

646-
static int
645+
static void
647646
first_context_watcher_callback(PyContextEvent event, PyObject *ctx) {
648-
return handle_context_watcher_event(0, event, ctx);
647+
handle_context_watcher_event(0, event, ctx);
649648
}
650649

651-
static int
650+
static void
652651
second_context_watcher_callback(PyContextEvent event, PyObject *ctx) {
653-
return handle_context_watcher_event(1, event, ctx);
652+
handle_context_watcher_event(1, event, ctx);
654653
}
655654

656-
static int
655+
static void
657656
noop_context_event_handler(PyContextEvent event, PyObject *ctx) {
658-
return 0;
659657
}
660658

661-
static int
659+
static void
662660
error_context_event_handler(PyContextEvent event, PyObject *ctx) {
663661
PyErr_SetString(PyExc_RuntimeError, "boom!");
664-
return -1;
665662
}
666663

667664
static PyObject *

Python/context.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,14 @@ static void notify_context_watchers(PyContextEvent event, PyContext *ctx, PyThre
124124
if (bits & 1) {
125125
PyContext_WatchCallback cb = interp->context_watchers[i];
126126
assert(cb != NULL);
127-
if (cb(event, (PyObject *)ctx) < 0) {
127+
PyObject *exc = _PyErr_GetRaisedException(ts);
128+
cb(event, (PyObject *)ctx);
129+
if (_PyErr_Occurred(ts) != NULL) {
128130
PyErr_FormatUnraisable(
129131
"Exception ignored in %s watcher callback for %R",
130132
context_event_name(event), ctx);
131133
}
134+
_PyErr_SetRaisedException(ts, exc);
132135
}
133136
i++;
134137
bits >>= 1;

0 commit comments

Comments
 (0)