Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd3c82b9

Browse files
authored
gh-125512: Revert "gh-124872: Replace enter/exit events with "switched" (#124776)" (#125513)
1 parent55c4f4c commitd3c82b9

File tree

6 files changed

+117
-117
lines changed

6 files changed

+117
-117
lines changed

‎Doc/c-api/contextvars.rst‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,16 @@ Context object management functions:
123123
124124
Enumeration of possible context object watcher events:
125125
126-
- ``Py_CONTEXT_SWITCHED``: The :term:`current context` has switched to a
127-
different context. The object passed to the watch callback is the
128-
now-current :class:`contextvars.Context` object, or None if no context is
129-
current.
126+
- ``Py_CONTEXT_EVENT_ENTER``: A context has been entered, causing the
127+
:term:`current context` to switch to it. The object passed to the watch
128+
callback is the now-current :class:`contextvars.Context` object. Each
129+
enter event will eventually have a corresponding exit event for the same
130+
context object after any subsequently entered contexts have themselves been
131+
exited.
132+
- ``Py_CONTEXT_EVENT_EXIT``: A context is about to be exited, which will
133+
cause the :term:`current context` to switch back to what it was before the
134+
context was entered. The object passed to the watch callback is the
135+
still-current :class:`contextvars.Context` object.
130136
131137
.. versionadded:: 3.14
132138

‎Include/cpython/context.h‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,20 @@ PyAPI_FUNC(int) PyContext_Exit(PyObject *);
2929

3030
typedefenum {
3131
/*
32-
* The current context has switched to a different context. The object
33-
* passed to the watch callback is the now-current contextvars.Context
34-
* object, or None if no context is current.
32+
* A context has been entered, causing the "current context" to switch to
33+
* it. The object passed to the watch callback is the now-current
34+
* contextvars.Context object. Each enter event will eventually have a
35+
* corresponding exit event for the same context object after any
36+
* subsequently entered contexts have themselves been exited.
3537
*/
36-
Py_CONTEXT_SWITCHED=1,
38+
Py_CONTEXT_EVENT_ENTER,
39+
/*
40+
* A context is about to be exited, which will cause the "current context"
41+
* to switch back to what it was before the context was entered. The
42+
* object passed to the watch callback is the still-current
43+
* contextvars.Context object.
44+
*/
45+
Py_CONTEXT_EVENT_EXIT,
3746
}PyContextEvent;
3847

3948
/*

‎Lib/test/test_capi/test_watchers.py‎

Lines changed: 44 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -577,62 +577,68 @@ class TestContextObjectWatchers(unittest.TestCase):
577577
defcontext_watcher(self,which_watcher):
578578
wid=_testcapi.add_context_watcher(which_watcher)
579579
try:
580-
switches=_testcapi.get_context_switches(which_watcher)
581-
exceptValueError:
582-
switches=None
583-
try:
584-
yieldswitches
580+
yieldwid
585581
finally:
586582
_testcapi.clear_context_watcher(wid)
587583

588-
defassert_event_counts(self,want_0,want_1):
589-
self.assertEqual(len(_testcapi.get_context_switches(0)),want_0)
590-
self.assertEqual(len(_testcapi.get_context_switches(1)),want_1)
584+
defassert_event_counts(self,exp_enter_0,exp_exit_0,
585+
exp_enter_1,exp_exit_1):
586+
self.assertEqual(
587+
exp_enter_0,_testcapi.get_context_watcher_num_enter_events(0))
588+
self.assertEqual(
589+
exp_exit_0,_testcapi.get_context_watcher_num_exit_events(0))
590+
self.assertEqual(
591+
exp_enter_1,_testcapi.get_context_watcher_num_enter_events(1))
592+
self.assertEqual(
593+
exp_exit_1,_testcapi.get_context_watcher_num_exit_events(1))
591594

592595
deftest_context_object_events_dispatched(self):
593596
# verify that all counts are zero before any watchers are registered
594-
self.assert_event_counts(0,0)
597+
self.assert_event_counts(0,0,0,0)
595598

596599
# verify that all counts remain zero when a context object is
597600
# entered and exited with no watchers registered
598601
ctx=contextvars.copy_context()
599-
ctx.run(self.assert_event_counts,0,0)
600-
self.assert_event_counts(0,0)
602+
ctx.run(self.assert_event_counts,0,0,0,0)
603+
self.assert_event_counts(0,0,0,0)
601604

602605
# verify counts are as expected when first watcher is registered
603606
withself.context_watcher(0):
604-
self.assert_event_counts(0,0)
605-
ctx.run(self.assert_event_counts,1,0)
606-
self.assert_event_counts(2,0)
607+
self.assert_event_counts(0,0,0,0)
608+
ctx.run(self.assert_event_counts,1,0,0,0)
609+
self.assert_event_counts(1,1,0,0)
607610

608611
# again with second watcher registered
609612
withself.context_watcher(1):
610-
self.assert_event_counts(2,0)
611-
ctx.run(self.assert_event_counts,3,1)
612-
self.assert_event_counts(4,2)
613+
self.assert_event_counts(1,1,0,0)
614+
ctx.run(self.assert_event_counts,2,1,1,0)
615+
self.assert_event_counts(2,2,1,1)
613616

614617
# verify counts are reset and don't change after both watchers are cleared
615-
ctx.run(self.assert_event_counts,0,0)
616-
self.assert_event_counts(0,0)
617-
618-
deftest_callback_error(self):
619-
ctx_outer=contextvars.copy_context()
620-
ctx_inner=contextvars.copy_context()
621-
unraisables= []
622-
623-
def_in_outer():
624-
withself.context_watcher(2):
625-
withcatch_unraisable_exception()ascm:
626-
ctx_inner.run(lambda:unraisables.append(cm.unraisable))
627-
unraisables.append(cm.unraisable)
628-
629-
ctx_outer.run(_in_outer)
630-
self.assertEqual([x.err_msgforxinunraisables],
631-
["Exception ignored in Py_CONTEXT_SWITCHED "
632-
f"watcher callback for{ctx!r}"
633-
forctxin [ctx_inner,ctx_outer]])
634-
self.assertEqual([str(x.exc_value)forxinunraisables],
635-
["boom!","boom!"])
618+
ctx.run(self.assert_event_counts,0,0,0,0)
619+
self.assert_event_counts(0,0,0,0)
620+
621+
deftest_enter_error(self):
622+
withself.context_watcher(2):
623+
withcatch_unraisable_exception()ascm:
624+
ctx=contextvars.copy_context()
625+
ctx.run(int,0)
626+
self.assertEqual(
627+
cm.unraisable.err_msg,
628+
"Exception ignored in "
629+
f"Py_CONTEXT_EVENT_EXIT watcher callback for{ctx!r}"
630+
)
631+
self.assertEqual(str(cm.unraisable.exc_value),"boom!")
632+
633+
deftest_exit_error(self):
634+
ctx=contextvars.copy_context()
635+
def_in_context(stack):
636+
stack.enter_context(self.context_watcher(2))
637+
638+
withcatch_unraisable_exception()ascm:
639+
withExitStack()asstack:
640+
ctx.run(_in_context,stack)
641+
self.assertEqual(str(cm.unraisable.exc_value),"boom!")
636642

637643
deftest_clear_out_of_range_watcher_id(self):
638644
withself.assertRaisesRegex(ValueError,r"Invalid context watcher ID -1"):
@@ -648,12 +654,5 @@ def test_allocate_too_many_watchers(self):
648654
withself.assertRaisesRegex(RuntimeError,r"no more context watcher IDs available"):
649655
_testcapi.allocate_too_many_context_watchers()
650656

651-
deftest_exit_base_context(self):
652-
ctx=contextvars.Context()
653-
_testcapi.clear_context_stack()
654-
withself.context_watcher(0)asswitches:
655-
ctx.run(lambda:None)
656-
self.assertEqual(switches, [ctx,None])
657-
658657
if__name__=="__main__":
659658
unittest.main()

‎Modules/_testcapi/watchers.c‎

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -626,12 +626,16 @@ allocate_too_many_func_watchers(PyObject *self, PyObject *args)
626626
// Test contexct object watchers
627627
#defineNUM_CONTEXT_WATCHERS 2
628628
staticintcontext_watcher_ids[NUM_CONTEXT_WATCHERS]= {-1,-1};
629-
staticPyObject*context_switches[NUM_CONTEXT_WATCHERS];
629+
staticintnum_context_object_enter_events[NUM_CONTEXT_WATCHERS]= {0,0};
630+
staticintnum_context_object_exit_events[NUM_CONTEXT_WATCHERS]= {0,0};
630631

631632
staticint
632633
handle_context_watcher_event(intwhich_watcher,PyContextEventevent,PyObject*ctx) {
633-
if (event==Py_CONTEXT_SWITCHED) {
634-
PyList_Append(context_switches[which_watcher],ctx);
634+
if (event==Py_CONTEXT_EVENT_ENTER) {
635+
num_context_object_enter_events[which_watcher]++;
636+
}
637+
elseif (event==Py_CONTEXT_EVENT_EXIT) {
638+
num_context_object_exit_events[which_watcher]++;
635639
}
636640
else {
637641
return-1;
@@ -663,28 +667,31 @@ error_context_event_handler(PyContextEvent event, PyObject *ctx) {
663667
staticPyObject*
664668
add_context_watcher(PyObject*self,PyObject*which_watcher)
665669
{
666-
staticconstPyContext_WatchCallbackcallbacks[]= {
667-
&first_context_watcher_callback,
668-
&second_context_watcher_callback,
669-
&error_context_event_handler,
670-
};
670+
intwatcher_id;
671671
assert(PyLong_Check(which_watcher));
672672
longwhich_l=PyLong_AsLong(which_watcher);
673-
if (which_l<0||which_l >= (long)Py_ARRAY_LENGTH(callbacks)) {
673+
if (which_l==0) {
674+
watcher_id=PyContext_AddWatcher(first_context_watcher_callback);
675+
context_watcher_ids[0]=watcher_id;
676+
num_context_object_enter_events[0]=0;
677+
num_context_object_exit_events[0]=0;
678+
}
679+
elseif (which_l==1) {
680+
watcher_id=PyContext_AddWatcher(second_context_watcher_callback);
681+
context_watcher_ids[1]=watcher_id;
682+
num_context_object_enter_events[1]=0;
683+
num_context_object_exit_events[1]=0;
684+
}
685+
elseif (which_l==2) {
686+
watcher_id=PyContext_AddWatcher(error_context_event_handler);
687+
}
688+
else {
674689
PyErr_Format(PyExc_ValueError,"invalid watcher %d",which_l);
675690
returnNULL;
676691
}
677-
intwatcher_id=PyContext_AddWatcher(callbacks[which_l]);
678692
if (watcher_id<0) {
679693
returnNULL;
680694
}
681-
if (which_l >=0&&which_l<NUM_CONTEXT_WATCHERS) {
682-
context_watcher_ids[which_l]=watcher_id;
683-
Py_XSETREF(context_switches[which_l],PyList_New(0));
684-
if (context_switches[which_l]==NULL) {
685-
returnNULL;
686-
}
687-
}
688695
returnPyLong_FromLong(watcher_id);
689696
}
690697

@@ -701,42 +708,30 @@ clear_context_watcher(PyObject *self, PyObject *watcher_id)
701708
for (inti=0;i<NUM_CONTEXT_WATCHERS;i++) {
702709
if (watcher_id_l==context_watcher_ids[i]) {
703710
context_watcher_ids[i]=-1;
704-
Py_CLEAR(context_switches[i]);
711+
num_context_object_enter_events[i]=0;
712+
num_context_object_exit_events[i]=0;
705713
}
706714
}
707715
}
708716
Py_RETURN_NONE;
709717
}
710718

711719
staticPyObject*
712-
clear_context_stack(PyObject*Py_UNUSED(self),PyObject*Py_UNUSED(args))
720+
get_context_watcher_num_enter_events(PyObject*self,PyObject*watcher_id)
713721
{
714-
PyThreadState*tstate=PyThreadState_Get();
715-
if (tstate->context==NULL) {
716-
Py_RETURN_NONE;
717-
}
718-
if (((PyContext*)tstate->context)->ctx_prev!=NULL) {
719-
PyErr_SetString(PyExc_RuntimeError,
720-
"must first exit all non-base contexts");
721-
returnNULL;
722-
}
723-
Py_CLEAR(tstate->context);
724-
Py_RETURN_NONE;
722+
assert(PyLong_Check(watcher_id));
723+
longwatcher_id_l=PyLong_AsLong(watcher_id);
724+
assert(watcher_id_l >=0&&watcher_id_l<NUM_CONTEXT_WATCHERS);
725+
returnPyLong_FromLong(num_context_object_enter_events[watcher_id_l]);
725726
}
726727

727728
staticPyObject*
728-
get_context_switches(PyObject*Py_UNUSED(self),PyObject*watcher_id)
729+
get_context_watcher_num_exit_events(PyObject*self,PyObject*watcher_id)
729730
{
730731
assert(PyLong_Check(watcher_id));
731732
longwatcher_id_l=PyLong_AsLong(watcher_id);
732-
if (watcher_id_l<0||watcher_id_l >=NUM_CONTEXT_WATCHERS) {
733-
PyErr_Format(PyExc_ValueError,"invalid watcher %ld",watcher_id_l);
734-
returnNULL;
735-
}
736-
if (context_switches[watcher_id_l]==NULL) {
737-
returnPyList_New(0);
738-
}
739-
returnPy_NewRef(context_switches[watcher_id_l]);
733+
assert(watcher_id_l >=0&&watcher_id_l<NUM_CONTEXT_WATCHERS);
734+
returnPyLong_FromLong(num_context_object_exit_events[watcher_id_l]);
740735
}
741736

742737
staticPyObject*
@@ -840,8 +835,10 @@ static PyMethodDef test_methods[] = {
840835
// Code object watchers.
841836
{"add_context_watcher",add_context_watcher,METH_O,NULL},
842837
{"clear_context_watcher",clear_context_watcher,METH_O,NULL},
843-
{"clear_context_stack",clear_context_stack,METH_NOARGS,NULL},
844-
{"get_context_switches",get_context_switches,METH_O,NULL},
838+
{"get_context_watcher_num_enter_events",
839+
get_context_watcher_num_enter_events,METH_O,NULL},
840+
{"get_context_watcher_num_exit_events",
841+
get_context_watcher_num_exit_events,METH_O,NULL},
845842
{"allocate_too_many_context_watchers",
846843
(PyCFunction)allocate_too_many_context_watchers,METH_NOARGS,NULL},
847844
{NULL},

‎Python/context.c‎

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@ PyContext_CopyCurrent(void)
102102
staticconstchar*
103103
context_event_name(PyContextEventevent) {
104104
switch (event) {
105-
casePy_CONTEXT_SWITCHED:
106-
return"Py_CONTEXT_SWITCHED";
105+
casePy_CONTEXT_EVENT_ENTER:
106+
return"Py_CONTEXT_EVENT_ENTER";
107+
casePy_CONTEXT_EVENT_EXIT:
108+
return"Py_CONTEXT_EVENT_EXIT";
107109
default:
108110
return"?";
109111
}
@@ -113,13 +115,6 @@ context_event_name(PyContextEvent event) {
113115
staticvoid
114116
notify_context_watchers(PyThreadState*ts,PyContextEventevent,PyObject*ctx)
115117
{
116-
if (ctx==NULL) {
117-
// This will happen after exiting the last context in the stack, which
118-
// can occur if context_get was never called before entering a context
119-
// (e.g., called `contextvars.Context().run()` on a fresh thread, as
120-
// PyContext_Enter doesn't call context_get).
121-
ctx=Py_None;
122-
}
123118
assert(Py_REFCNT(ctx)>0);
124119
PyInterpreterState*interp=ts->interp;
125120
assert(interp->_initialized);
@@ -180,16 +175,6 @@ PyContext_ClearWatcher(int watcher_id)
180175
}
181176

182177

183-
staticinlinevoid
184-
context_switched(PyThreadState*ts)
185-
{
186-
ts->context_ver++;
187-
// ts->context is used instead of context_get() because context_get() might
188-
// throw if ts->context is NULL.
189-
notify_context_watchers(ts,Py_CONTEXT_SWITCHED,ts->context);
190-
}
191-
192-
193178
staticint
194179
_PyContext_Enter(PyThreadState*ts,PyObject*octx)
195180
{
@@ -206,7 +191,9 @@ _PyContext_Enter(PyThreadState *ts, PyObject *octx)
206191
ctx->ctx_entered=1;
207192

208193
ts->context=Py_NewRef(ctx);
209-
context_switched(ts);
194+
ts->context_ver++;
195+
196+
notify_context_watchers(ts,Py_CONTEXT_EVENT_ENTER,octx);
210197
return0;
211198
}
212199

@@ -240,11 +227,13 @@ _PyContext_Exit(PyThreadState *ts, PyObject *octx)
240227
return-1;
241228
}
242229

230+
notify_context_watchers(ts,Py_CONTEXT_EVENT_EXIT,octx);
243231
Py_SETREF(ts->context, (PyObject*)ctx->ctx_prev);
232+
ts->context_ver++;
244233

245234
ctx->ctx_prev=NULL;
246235
ctx->ctx_entered=0;
247-
context_switched(ts);
236+
248237
return0;
249238
}
250239

‎Tools/c-analyzer/cpython/ignored.tsv‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -455,8 +455,8 @@ Modules/_testcapi/watchers.c-pyfunc_watchers-
455455
Modules/_testcapi/watchers.c-func_watcher_ids-
456456
Modules/_testcapi/watchers.c-func_watcher_callbacks-
457457
Modules/_testcapi/watchers.c-context_watcher_ids-
458-
Modules/_testcapi/watchers.c-context_switches-
459-
Modules/_testcapi/watchers.cadd_context_watchercallbacks-
458+
Modules/_testcapi/watchers.c-num_context_object_enter_events-
459+
Modules/_testcapi/watchers.c-num_context_object_exit_events-
460460
Modules/_testcapimodule.c-BasicStaticTypes-
461461
Modules/_testcapimodule.c-num_basic_static_types_used-
462462
Modules/_testcapimodule.c-ContainerNoGC_members-

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp