From b4752d7c25d0e1bf6b17b2ca8b83962acbfac53f Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Tue, 1 Jan 2019 18:32:53 +0100 Subject: [PATCH] Stackless issue #197: stackless call method "contextvars.Context.run" Enable stackless calls of method "contextvars.Context.run", if soft-switching is enabled. --- Python/context.c | 68 ++++++++++++++++++++++++++- Stackless/changelog.txt | 4 ++ Stackless/unittests/test_generator.py | 43 +++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) diff --git a/Python/context.c b/Python/context.c index 2034a204121501..be154ce7a84c18 100644 --- a/Python/context.c +++ b/Python/context.c @@ -4,6 +4,8 @@ #include "internal/pystate.h" #include "internal/context.h" #include "internal/hamt.h" +#include "core/stackless_impl.h" +#include "pickling/prickelpit.h" #define CONTEXT_FREELIST_MAXLEN 255 @@ -562,10 +564,33 @@ _contextvars_Context_copy_impl(PyContext *self) } +#ifdef STACKLESS +static PyObject* context_run_callback(PyFrameObject *f, int exc, PyObject *result) +{ + PyCFrameObject *cf = (PyCFrameObject *)f; + assert(PyContext_CheckExact(cf->ob1)); + PyContext *context = (PyContext *)cf->ob1; + cf->ob1 = NULL; + + if (PyContext_Exit(context)) { + Py_CLEAR(result); + } + + Py_DECREF(context); + SLP_STORE_NEXT_FRAME(PyThreadState_GET(), cf->f_back); + return result; +} + +SLP_DEF_INVALID_EXEC(context_run_callback) +#endif + + static PyObject * context_run(PyContext *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { + STACKLESS_GETARG(); + if (nargs < 1) { PyErr_SetString(PyExc_TypeError, "run() missing 1 required positional argument"); @@ -576,9 +601,42 @@ context_run(PyContext *self, PyObject *const *args, return NULL; } +#ifdef STACKLESS + PyThreadState *ts = PyThreadState_GET(); + PyCFrameObject *f = NULL; + if (stackless) { + f = slp_cframe_new(context_run_callback, 1); + if (f == NULL) + return NULL; + Py_INCREF(self); + f->ob1 = (PyObject *)self; + SLP_SET_CURRENT_FRAME(ts, (PyFrameObject *)f); + /* f contains the only counted reference to current frame. This reference + * keeps the fame alive during the following _PyObject_FastCallKeywords(). + */ + } +#endif + STACKLESS_PROMOTE_ALL(); + PyObject *call_result = _PyObject_FastCallKeywords( args[0], args + 1, nargs - 1, kwnames); + STACKLESS_ASSERT(); +#ifdef STACKLESS + if (stackless && !STACKLESS_UNWINDING(call_result)) { + /* required, because we added a C-frame */ + assert(f); + assert((PyFrameObject *)f == SLP_CURRENT_FRAME(ts)); + SLP_STORE_NEXT_FRAME(ts, (PyFrameObject *)f); + Py_DECREF(f); + return STACKLESS_PACK(ts, call_result); + } + Py_XDECREF(f); + if (STACKLESS_UNWINDING(call_result)) { + return call_result; + } +#endif + if (PyContext_Exit(self)) { return NULL; } @@ -593,7 +651,7 @@ static PyMethodDef PyContext_methods[] = { _CONTEXTVARS_CONTEXT_KEYS_METHODDEF _CONTEXTVARS_CONTEXT_VALUES_METHODDEF _CONTEXTVARS_CONTEXT_COPY_METHODDEF - {"run", (PyCFunction)context_run, METH_FASTCALL | METH_KEYWORDS, NULL}, + {"run", (PyCFunction)context_run, METH_FASTCALL | METH_KEYWORDS | METH_STACKLESS, NULL}, {NULL, NULL} }; @@ -1222,5 +1280,13 @@ _PyContext_Init(void) } Py_DECREF(missing); +#ifdef STACKLESS + if (slp_register_execute(&PyCFrame_Type, "context_run_callback", + context_run_callback, SLP_REF_INVALID_EXEC(context_run_callback)) != 0) + { + return 0; + } +#endif + return 1; } diff --git a/Stackless/changelog.txt b/Stackless/changelog.txt index 11b5eeb47452eb..bbd16a12be8f73 100644 --- a/Stackless/changelog.txt +++ b/Stackless/changelog.txt @@ -9,6 +9,10 @@ What's New in Stackless 3.X.X? *Release date: 20XX-XX-XX* +- https://github.com/stackless-dev/stackless/issues/197 + Enable stackless calls of method "contextvars.Context.run", if soft-switching + is enabled. + - https://github.com/stackless-dev/stackless/issues/190 Silently ignore attempts to close a running generator, coroutine or asynchronous generator. This avoids spurious error messages, if such an diff --git a/Stackless/unittests/test_generator.py b/Stackless/unittests/test_generator.py index 79d5cb40ccbfac..a3fb4ccc9e6cdf 100644 --- a/Stackless/unittests/test_generator.py +++ b/Stackless/unittests/test_generator.py @@ -6,6 +6,8 @@ import pickle import contextlib import sys +import contextvars +import asyncio from support import test_main # @UnusedImport from support import StacklessTestCase, captured_stderr @@ -242,5 +244,46 @@ def test_finalizer(self): self._test_finalizer(pf_dump, pf_load) +class TestStacklessOperations(StacklessTestCase): + def assertLevel(self, expected=0): + self.assertTrue(stackless.current.alive) + if stackless.enable_softswitch(None): + self.assertEqual(stackless.current.nesting_level, expected) + else: + self.assertGreater(stackless.current.nesting_level, expected) + + @types.coroutine + def coro1yield(self): + yield + + async def coro(self): + self.assertLevel() + await self.coro1yield() + self.assertLevel() + await self.coro1yield() + self.assertLevel() + + def test_context_run(self): + contextvars.Context().run(self.assertLevel) + + # needs Stackless pull request #188 + def xx_test_asyncio(self): + async def test(): + try: + await self.coro() + finally: + loop.stop() + + asyncio.set_event_loop(asyncio.new_event_loop()) + self.addCleanup(asyncio.set_event_loop, None) + loop = asyncio.get_event_loop() + task = asyncio.tasks._PyTask(test()) + asyncio.ensure_future(task) + try: + loop.run_forever() + finally: + loop.close() + + if __name__ == '__main__': unittest.main()