Skip to content
This repository was archived by the owner on Jan 10, 2022. It is now read-only.

Commit 54f4a63

Browse files
authored
Make contextvars to behave correctly
2 parents a701ec5 + 9ec19d6 commit 54f4a63

File tree

6 files changed

+71
-18
lines changed

6 files changed

+71
-18
lines changed

src/asyncio/_asynciomodule.c

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,34 @@ future_cancel(FutureObj *fut)
716716
Py_RETURN_TRUE;
717717
}
718718

719+
// 7TO6: This method replaces built-in API PyContext_CopyContext
720+
// from Python >= 3.7 by running contextvars.copy_context()
721+
// from a Python module.
722+
static PyObject *
723+
copy_context()
724+
{
725+
PyObject *empty_tuple = PyTuple_New(0);
726+
727+
PyObject *contextvars_lib = PyImport_ImportModule("contextvars");
728+
if (contextvars_lib == NULL) {
729+
Py_DECREF(empty_tuple);
730+
return NULL;
731+
}
732+
PyObject *copy_context_fun = PyObject_GetAttrString(contextvars_lib, "copy_context");
733+
if (copy_context_fun == NULL) {
734+
Py_DECREF(empty_tuple);
735+
Py_DECREF(contextvars_lib);
736+
return NULL;
737+
}
738+
PyObject *context = PyObject_Call(copy_context_fun, empty_tuple, NULL);
739+
740+
Py_DECREF(empty_tuple);
741+
Py_DECREF(copy_context_fun);
742+
Py_DECREF(contextvars_lib);
743+
744+
return context;
745+
}
746+
719747
/*[clinic input]
720748
_asyncio.Future.__init__
721749
@@ -911,16 +939,17 @@ _asyncio_Future_add_done_callback_impl(FutureObj *self, PyObject *fn,
911939
PyObject *context)
912940
/*[clinic end generated code: output=7ce635bbc9554c1e input=15ab0693a96e9533]*/
913941
{
914-
// 7TO6: Ignore context as is implemented in pure Python
915-
/* if (context == NULL) {
916-
context = PyContext_CopyCurrent();
942+
if (context == NULL) {
943+
// 7TO6: Use pure-Python version of copy_context
944+
/* context = PyContext_CopyCurrent(); */
945+
context = copy_context();
917946
if (context == NULL) {
918947
return NULL;
919948
}
920949
PyObject *res = future_add_done_callback(self, fn, context);
921950
Py_DECREF(context);
922951
return res;
923-
} */
952+
}
924953
return future_add_done_callback(self, fn, context);
925954
}
926955

@@ -1994,11 +2023,12 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop)
19942023
return -1;
19952024
}
19962025

1997-
// 7TO6: Ignore context as is implemented in pure Python
1998-
/* Py_XSETREF(self->task_context, PyContext_CopyCurrent());
2026+
// 7TO6: Use pure-Python version of copy_context
2027+
/* Py_XSETREF(self->task_context, PyContext_CopyCurrent()); */
2028+
Py_XSETREF(self->task_context, copy_context());
19992029
if (self->task_context == NULL) {
20002030
return -1;
2001-
} */
2031+
}
20022032

20032033
Py_CLEAR(self->task_fut_waiter);
20042034
self->task_must_cancel = 0;
@@ -2091,6 +2121,18 @@ TaskObj_get_fut_waiter(TaskObj *task, void *Py_UNUSED(ignored))
20912121
Py_RETURN_NONE;
20922122
}
20932123

2124+
// 7TO6: Expose task context to Python modules
2125+
static PyObject *
2126+
TaskObj_get_task_context(TaskObj *task, void *Py_UNUSED(ignored))
2127+
{
2128+
if (task->task_context) {
2129+
Py_INCREF(task->task_context);
2130+
return task->task_context;
2131+
}
2132+
2133+
Py_RETURN_NONE;
2134+
}
2135+
20942136
/*[clinic input]
20952137
@classmethod
20962138
_asyncio.Task.current_task
@@ -2433,6 +2475,7 @@ static PyGetSetDef TaskType_getsetlist[] = {
24332475
{"_must_cancel", (getter)TaskObj_get_must_cancel, NULL, NULL},
24342476
{"_coro", (getter)TaskObj_get_coro, NULL, NULL},
24352477
{"_fut_waiter", (getter)TaskObj_get_fut_waiter, NULL, NULL},
2478+
{"_task_context", (getter)TaskObj_get_task_context, NULL, NULL}, // 7TO6: Expose _task_context
24362479
{NULL} /* Sentinel */
24372480
};
24382481

src/asyncio/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
__version__ = '0.1.2'
1+
__version__ = '0.1.3'
22

src/asyncio/events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -784,8 +784,8 @@ def set_child_watcher(watcher):
784784
# get_event_loop() is one of the most frequently called
785785
# functions in asyncio. Pure Python implementation is
786786
# about 4 times slower than C-accelerated.
787-
from _asyncio import (_get_running_loop, _set_running_loop,
788-
get_running_loop, get_event_loop)
787+
from ._asyncio import (_get_running_loop, _set_running_loop,
788+
get_running_loop, get_event_loop)
789789
except ImportError:
790790
pass
791791
else:

src/asyncio/futures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ def wrap_future(future, *, loop=None):
382382

383383

384384
try:
385-
import _asyncio
385+
from . import _asyncio
386386
except ImportError:
387387
pass
388388
else:

src/asyncio/tasks.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def __wakeup(self, future):
334334

335335

336336
try:
337-
import _asyncio
337+
from . import _asyncio
338338
except ImportError:
339339
pass
340340
else:
@@ -888,9 +888,9 @@ def _unregister_task(task):
888888

889889

890890
try:
891-
from _asyncio import (_register_task, _unregister_task,
892-
_enter_task, _leave_task,
893-
_all_tasks, _current_tasks)
891+
from ._asyncio import (_register_task, _unregister_task,
892+
_enter_task, _leave_task,
893+
_all_tasks, _current_tasks)
894894
except ImportError:
895895
pass
896896
else:

src/contextvars.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ def set(self, value):
125125
except KeyError:
126126
old_value = Token.MISSING
127127

128-
updated_data = data.set(self, value)
128+
updated_data = data.copy()
129+
updated_data[self] = value
129130
ctx._data = updated_data
130131
return Token(ctx, self, old_value)
131132

@@ -145,7 +146,8 @@ def reset(self, token):
145146
if token._old_value is Token.MISSING:
146147
ctx._data = ctx._data.delete(token._var)
147148
else:
148-
ctx._data = ctx._data.set(token._var, token._old_value)
149+
ctx._data = ctx._data.copy()
150+
ctx._data[token._var] = token._old_value
149151

150152
token._used = True
151153

@@ -198,7 +200,15 @@ def copy_context():
198200

199201

200202
def _get_context():
201-
ctx = getattr(_state, 'context', None)
203+
import asyncio.tasks
204+
try:
205+
task = asyncio.tasks.current_task()
206+
ctx = getattr(task, '_task_context', None)
207+
except RuntimeError:
208+
ctx = None
209+
210+
if ctx is None:
211+
ctx = getattr(_state, 'context', None)
202212
if ctx is None:
203213
ctx = Context()
204214
_state.context = ctx

0 commit comments

Comments
 (0)