Skip to content

Commit b4752d7

Browse files
Anselm KruisAnselm Kruis
authored andcommitted
Stackless issue python#197: stackless call method "contextvars.Context.run"
Enable stackless calls of method "contextvars.Context.run", if soft-switching is enabled.
1 parent 65a02d1 commit b4752d7

File tree

3 files changed

+114
-1
lines changed

3 files changed

+114
-1
lines changed

Python/context.c

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "internal/pystate.h"
55
#include "internal/context.h"
66
#include "internal/hamt.h"
7+
#include "core/stackless_impl.h"
8+
#include "pickling/prickelpit.h"
79

810

911
#define CONTEXT_FREELIST_MAXLEN 255
@@ -562,10 +564,33 @@ _contextvars_Context_copy_impl(PyContext *self)
562564
}
563565

564566

567+
#ifdef STACKLESS
568+
static PyObject* context_run_callback(PyFrameObject *f, int exc, PyObject *result)
569+
{
570+
PyCFrameObject *cf = (PyCFrameObject *)f;
571+
assert(PyContext_CheckExact(cf->ob1));
572+
PyContext *context = (PyContext *)cf->ob1;
573+
cf->ob1 = NULL;
574+
575+
if (PyContext_Exit(context)) {
576+
Py_CLEAR(result);
577+
}
578+
579+
Py_DECREF(context);
580+
SLP_STORE_NEXT_FRAME(PyThreadState_GET(), cf->f_back);
581+
return result;
582+
}
583+
584+
SLP_DEF_INVALID_EXEC(context_run_callback)
585+
#endif
586+
587+
565588
static PyObject *
566589
context_run(PyContext *self, PyObject *const *args,
567590
Py_ssize_t nargs, PyObject *kwnames)
568591
{
592+
STACKLESS_GETARG();
593+
569594
if (nargs < 1) {
570595
PyErr_SetString(PyExc_TypeError,
571596
"run() missing 1 required positional argument");
@@ -576,9 +601,42 @@ context_run(PyContext *self, PyObject *const *args,
576601
return NULL;
577602
}
578603

604+
#ifdef STACKLESS
605+
PyThreadState *ts = PyThreadState_GET();
606+
PyCFrameObject *f = NULL;
607+
if (stackless) {
608+
f = slp_cframe_new(context_run_callback, 1);
609+
if (f == NULL)
610+
return NULL;
611+
Py_INCREF(self);
612+
f->ob1 = (PyObject *)self;
613+
SLP_SET_CURRENT_FRAME(ts, (PyFrameObject *)f);
614+
/* f contains the only counted reference to current frame. This reference
615+
* keeps the fame alive during the following _PyObject_FastCallKeywords().
616+
*/
617+
}
618+
#endif
619+
STACKLESS_PROMOTE_ALL();
620+
579621
PyObject *call_result = _PyObject_FastCallKeywords(
580622
args[0], args + 1, nargs - 1, kwnames);
581623

624+
STACKLESS_ASSERT();
625+
#ifdef STACKLESS
626+
if (stackless && !STACKLESS_UNWINDING(call_result)) {
627+
/* required, because we added a C-frame */
628+
assert(f);
629+
assert((PyFrameObject *)f == SLP_CURRENT_FRAME(ts));
630+
SLP_STORE_NEXT_FRAME(ts, (PyFrameObject *)f);
631+
Py_DECREF(f);
632+
return STACKLESS_PACK(ts, call_result);
633+
}
634+
Py_XDECREF(f);
635+
if (STACKLESS_UNWINDING(call_result)) {
636+
return call_result;
637+
}
638+
#endif
639+
582640
if (PyContext_Exit(self)) {
583641
return NULL;
584642
}
@@ -593,7 +651,7 @@ static PyMethodDef PyContext_methods[] = {
593651
_CONTEXTVARS_CONTEXT_KEYS_METHODDEF
594652
_CONTEXTVARS_CONTEXT_VALUES_METHODDEF
595653
_CONTEXTVARS_CONTEXT_COPY_METHODDEF
596-
{"run", (PyCFunction)context_run, METH_FASTCALL | METH_KEYWORDS, NULL},
654+
{"run", (PyCFunction)context_run, METH_FASTCALL | METH_KEYWORDS | METH_STACKLESS, NULL},
597655
{NULL, NULL}
598656
};
599657

@@ -1222,5 +1280,13 @@ _PyContext_Init(void)
12221280
}
12231281
Py_DECREF(missing);
12241282

1283+
#ifdef STACKLESS
1284+
if (slp_register_execute(&PyCFrame_Type, "context_run_callback",
1285+
context_run_callback, SLP_REF_INVALID_EXEC(context_run_callback)) != 0)
1286+
{
1287+
return 0;
1288+
}
1289+
#endif
1290+
12251291
return 1;
12261292
}

Stackless/changelog.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ What's New in Stackless 3.X.X?
99

1010
*Release date: 20XX-XX-XX*
1111

12+
- https://github.com/stackless-dev/stackless/issues/197
13+
Enable stackless calls of method "contextvars.Context.run", if soft-switching
14+
is enabled.
15+
1216
- https://github.com/stackless-dev/stackless/issues/190
1317
Silently ignore attempts to close a running generator, coroutine or
1418
asynchronous generator. This avoids spurious error messages, if such an

Stackless/unittests/test_generator.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import pickle
77
import contextlib
88
import sys
9+
import contextvars
10+
import asyncio
911

1012
from support import test_main # @UnusedImport
1113
from support import StacklessTestCase, captured_stderr
@@ -242,5 +244,46 @@ def test_finalizer(self):
242244
self._test_finalizer(pf_dump, pf_load)
243245

244246

247+
class TestStacklessOperations(StacklessTestCase):
248+
def assertLevel(self, expected=0):
249+
self.assertTrue(stackless.current.alive)
250+
if stackless.enable_softswitch(None):
251+
self.assertEqual(stackless.current.nesting_level, expected)
252+
else:
253+
self.assertGreater(stackless.current.nesting_level, expected)
254+
255+
@types.coroutine
256+
def coro1yield(self):
257+
yield
258+
259+
async def coro(self):
260+
self.assertLevel()
261+
await self.coro1yield()
262+
self.assertLevel()
263+
await self.coro1yield()
264+
self.assertLevel()
265+
266+
def test_context_run(self):
267+
contextvars.Context().run(self.assertLevel)
268+
269+
# needs Stackless pull request #188
270+
def xx_test_asyncio(self):
271+
async def test():
272+
try:
273+
await self.coro()
274+
finally:
275+
loop.stop()
276+
277+
asyncio.set_event_loop(asyncio.new_event_loop())
278+
self.addCleanup(asyncio.set_event_loop, None)
279+
loop = asyncio.get_event_loop()
280+
task = asyncio.tasks._PyTask(test())
281+
asyncio.ensure_future(task)
282+
try:
283+
loop.run_forever()
284+
finally:
285+
loop.close()
286+
287+
245288
if __name__ == '__main__':
246289
unittest.main()

0 commit comments

Comments
 (0)