From 3004a2901a7f404286009c587d78b91b4dfa6074 Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Tue, 30 Oct 2018 00:10:41 +0100 Subject: [PATCH] Implement an API for stackless C-extension functions. It enables writing soft switchable C-functions/methods. This API is provisional. --- Doc/c-api/stackless.rst | 96 +++++++++++++ Stackless/changelog.txt | 3 + Stackless/core/cframeobject.c | 207 ++++++++++++++++++++++++++++- Stackless/module/_teststackless.c | 202 +++++++++++++++++++++++++++- Stackless/module/stacklessmodule.c | 2 +- Stackless/stackless_api.h | 32 +++++ Stackless/unittests/test_capi.py | 134 +++++++++++++++++++ 7 files changed, 669 insertions(+), 7 deletions(-) diff --git a/Doc/c-api/stackless.rst b/Doc/c-api/stackless.rst index 5c57c13c7e43be..a48b461b6fe65f 100644 --- a/Doc/c-api/stackless.rst +++ b/Doc/c-api/stackless.rst @@ -343,6 +343,88 @@ stackless module interrupt execution once this many total opcodes have been executed since the call was made. +Soft-switchable extension functions +----------------------------------- + +.. versionadded:: 3.7 + +.. note:: + The API for soft-switchable extension function has been added on a + provisional basis (see :pep:`411` for details.) + +A soft switchable extension function or method is a function or method defined +by an extension module written in C. In contrast to an normal C-function you +can soft-switch tasklets while this function executes. At the C-language level +such a function or method is made from 3 C-definitions: + +1. A declaration object of type :c:type:`PyStacklessFunctionDeclaration_Type`. + It declares the soft-switchable function and must be declared as a global + variable. +2. A conventional extension function, that uses + :c:func:`PyStackless_CallFunction` to call the soft switchable function. +3. A C-function of type ``slp_softswitchablefunc``. This function provides the + implemantation of the soft switchable function. + +To create a soft switchable function declaration simply define it as a static +variable and call :c:func:`PyStackless_InitFunctionDeclaration` from your +module init code to initialise it. See the example code in the source +of the extension module `_teststackless `_. + +Typedef ``slp_softswitchablefunc``:: + + typedef PyObject *(slp_softswitchablefunc) (PyObject *retval, + long *step, PyObject **ob1, PyObject **ob2, PyObject **ob3, + long *n, void **any); + +.. c:type:: PyStacklessFunctionDeclarationObject + + This subtype of :c:type:`PyObject` represents a Stackless soft switchable + extension function declaration object. + + Here is the structure definition:: + + typedef struct { + PyObject_HEAD + slp_softswitchablefunc * sfunc; + const char * name; + const char * module_name; + } PyStacklessFunctionDeclarationObject; + + .. c:member:: slp_softswitchablefunc PyStacklessFunctionDeclarationObject.sfunc + + Pointer to implementation function. + + .. c:member:: const char * PyStacklessFunctionDeclarationObject.name + + Name of the function. + + .. c:member:: const char * PyStacklessFunctionDeclarationObject.module_name + + Name of the containing module. + +.. c:var:: PyTypeObject PyStacklessFunctionDeclaration_Type + + This instance of :c:type:`PyTypeObject` represents the Stackless + soft switchable extension function declaration type. + +.. c:function:: int PyStacklessFunctionDeclarationType_CheckExact(PyObject *p) + + Return true if *p* is a PyStacklessFunctionDeclarationObject object, but + not an instance of a subtype of this type. + +.. c:function:: PyObject* PyStackless_CallFunction(PyStacklessFunctionDeclarationObject *sfd, PyObject *arg, PyObject *ob1, PyObject *ob2, PyObject *ob3, long n, void *any) + + Invoke the soft switchable extension, which is represented by *sfd*. + Pass *arg* as initial value for argument *retval* and *ob1*, *ob2*, *ob3*, + *n* and *any* as general purpose in-out-arguments. + + Return the result of the function call or :c:data:`Py_UnwindToken`. + +.. c:function:: int PyStackless_InitFunctionDeclaration(PyStacklessFunctionDeclarationObject *sfd, PyObject *module, PyModuleDef *module_def) + + Initialize the fields :c:member:`PyStacklessFunctionDeclarationObject.name` and + :c:member:`PyStacklessFunctionDeclarationObject.module_name` of *sfd*. + debugging and monitoring functions ---------------------------------- @@ -368,6 +450,20 @@ debugging and monitoring functions Scheduler monitoring with a faster interface. +Other functions +--------------- + +Stack unwinding +~~~~~~~~~~~~~~~ + +.. c:var:: PyUnwindObject * Py_UnwindToken + + A singleton that indicates C-stack unwinding + +.. c:function:: int STACKLESS_UNWINDING(obj) + + Return 1, if *obj* is :c:data:`Py_UnwindToken` and 0 otherwise. + Interface functions ------------------- diff --git a/Stackless/changelog.txt b/Stackless/changelog.txt index 4d6c9def102eff..c5a0a8e46bd5ae 100644 --- a/Stackless/changelog.txt +++ b/Stackless/changelog.txt @@ -9,6 +9,9 @@ What's New in Stackless 3.X.X? *Release date: 20XX-XX-XX* +- https://github.com/stackless-dev/stackless/issues/178 + There is a new, provisional API to write soft switchable functions in C. + - https://github.com/stackless-dev/stackless/issues/149 The Stackless version is now "3.7". diff --git a/Stackless/core/cframeobject.c b/Stackless/core/cframeobject.c index 913938fa4c4c75..1c3c312d40f8b4 100644 --- a/Stackless/core/cframeobject.c +++ b/Stackless/core/cframeobject.c @@ -125,8 +125,10 @@ slp_cframe_new(PyFrame_ExecFunc *exec, unsigned int linked) /* pickling support for cframes */ #define cframetuplefmt "iOOll" -#define cframetuplenewfmt "iUO!ll:cframe" +#define cframetuplenewfmt "iOO!ll:cframe" +static PyObject * execute_soft_switchable_func(PyFrameObject *, int, PyObject *); +SLP_DEF_INVALID_EXEC(execute_soft_switchable_func) static PyObject * cframe_reduce(PyCFrameObject *cf) @@ -135,7 +137,13 @@ cframe_reduce(PyCFrameObject *cf) PyObject *params = NULL; int valid = 1; - if ((exec_name = slp_find_execname((PyFrameObject *) cf, &valid)) == NULL) + if (cf->f_execute == execute_soft_switchable_func) { + exec_name = (PyObject *) cf->any2; + assert(cf->any2); + assert(PyStacklessFunctionDeclarationType_CheckExact(exec_name)); + Py_INCREF(exec_name); + valid = cf->any1 == NULL; + } else if ((exec_name = slp_find_execname((PyFrameObject *) cf, &valid)) == NULL) return NULL; params = slp_into_tuple_with_nulls(&cf->ob1, 3); @@ -176,6 +184,7 @@ cframe_setstate(PyObject *self, PyObject *args) PyFrame_ExecFunc *good_func, *bad_func; PyObject *params; long i, n; + void *any2=NULL; if (!PyArg_ParseTuple (args, cframetuplenewfmt, &valid, @@ -185,7 +194,11 @@ cframe_setstate(PyObject *self, PyObject *args) &n)) return NULL; - if (slp_find_execfuncs(Py_TYPE(cf), exec_name, &good_func, &bad_func)) + if (PyStacklessFunctionDeclarationType_CheckExact(exec_name)) { + good_func = execute_soft_switchable_func; + bad_func = cannot_execute_soft_switchable_func; + any2 = exec_name; /* not ref counted */ + } else if (slp_find_execfuncs(Py_TYPE(cf), exec_name, &good_func, &bad_func)) return NULL; if (PyTuple_GET_SIZE(params)-1 != 3) @@ -198,7 +211,8 @@ cframe_setstate(PyObject *self, PyObject *args) slp_from_tuple_with_nulls(&cf->ob1, params); cf->i = i; cf->n = n; - cf->any1 = cf->any2 = NULL; + cf->any1 = NULL; + cf->any2 = any2; Py_INCREF(cf); return (PyObject *) cf; } @@ -324,6 +338,8 @@ int slp_init_cframetype(void) { if (PyType_Ready(&PyCFrame_Type)) return -1; + if (PyType_Ready(&PyStacklessFunctionDeclaration_Type)) + return -1; /* register the cframe exec func */ return slp_register_execute(&PyCFrame_Type, "run_cframe", run_cframe, SLP_REF_INVALID_EXEC(run_cframe)); @@ -343,4 +359,187 @@ slp_cframe_fini(void) assert(numfree == 0); } + +/* + * API for soft switchable extension functions / methods + */ + +static PyObject * +function_declaration_reduce(PyStacklessFunctionDeclarationObject *self) +{ + if (self->name == NULL || *self->name == '\0') { + PyErr_SetString(PyExc_SystemError, "no function name"); + return NULL; + } + return PyUnicode_FromString(self->name); +} + +static PyMethodDef function_declaration_methods[] = { + {"__reduce__", (PyCFunction)function_declaration_reduce, METH_NOARGS, NULL}, + {"__reduce_ex__", (PyCFunction)function_declaration_reduce, METH_VARARGS, NULL}, + {NULL, NULL} +}; + +static PyMemberDef function_declaration_memberlist[] = { + {"__module__", T_STRING, offsetof(PyStacklessFunctionDeclarationObject, module_name), READONLY}, + {"name", T_STRING, offsetof(PyStacklessFunctionDeclarationObject, name), READONLY}, + {NULL} /* Sentinel */ +}; + +PyDoc_STRVAR(PyStacklessFunctionDeclaration_Type__doc__, +"SoftSwitchableFunctionDeclaration objects represent a soft switchable\n\ +extension function written in C in a pickle.\n\ +"); + +PyTypeObject PyStacklessFunctionDeclaration_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "_stackless.FunctionDeclaration", /*tp_name*/ + sizeof(PyStacklessFunctionDeclarationObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + PyStacklessFunctionDeclaration_Type__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + function_declaration_methods, /* tp_methods */ + function_declaration_memberlist, /* tp_members */ +}; + +static +PyObject * +execute_soft_switchable_func(PyFrameObject *f, int exc, PyObject *retval) +{ + /* + * Special rule for frame execution functions: we now own a reference to retval! + */ + PyCFrameObject *cf = (PyCFrameObject *)f; + PyThreadState *ts = PyThreadState_GET(); + PyObject *ob1, *ob2, *ob3; + PyStacklessFunctionDeclarationObject *ssfd = + (PyStacklessFunctionDeclarationObject*)cf->any2; + if (retval == NULL) { + SLP_STORE_NEXT_FRAME(ts, cf->f_back); + return NULL; + } + assert(ssfd); + assert(ssfd->sfunc); + + /* make sure we own refs to the arguments during the function call */ + ob1 = cf->ob1; + ob2 = cf->ob2; + ob3 = cf->ob3; + Py_XINCREF(ob1); + Py_XINCREF(ob2); + Py_XINCREF(ob3); + STACKLESS_PROPOSE_ALL(ts); + /* use Py_SETREF, because we own a ref to retval. */ + Py_SETREF(retval, ssfd->sfunc(retval, &cf->i, &cf->ob1, &cf->ob2, &cf->ob3, &cf->n, &cf->any1)); + STACKLESS_RETRACT(); + STACKLESS_ASSERT(); + Py_XDECREF(ob1); + Py_XDECREF(ob2); + Py_XDECREF(ob3); + if (STACKLESS_UNWINDING(retval)) + return retval; + SLP_STORE_NEXT_FRAME(ts, cf->f_back); + return retval; +} + +PyAPI_FUNC(int) +PyStackless_InitFunctionDeclaration(PyStacklessFunctionDeclarationObject *sfd, PyObject *module, PyModuleDef *module_def) { + assert(sfd != NULL); + Py_TYPE(sfd) = &PyStacklessFunctionDeclaration_Type; + if (module_def != NULL) { + assert(module_def->m_name); + sfd->module_name = module_def->m_name; + } + if (module == NULL) + return 0; + Py_INCREF(sfd); /* PyModule_AddObject steals a reference. */ + return PyModule_AddObject(module, sfd->name, (PyObject *)sfd); +} + +PyObject * +PyStackless_CallFunction(PyStacklessFunctionDeclarationObject *ssfd, PyObject *arg, + PyObject *ob1, PyObject *ob2, PyObject *ob3, long n, void *any) +{ + STACKLESS_GETARG(); + PyThreadState *ts = PyThreadState_GET(); + + assert(ssfd); + assert(ts->st.main != NULL); + + if (stackless) { + /* Only soft-switch, if the caller can handle Py_UnwindToken. + * If called from Python-code, this is always the case, + * but if you call this method from a C-function, you don't know. */ + PyCFrameObject *cf; + cf = slp_cframe_new(execute_soft_switchable_func, 1); + if (cf == NULL) + return NULL; + cf->any2 = ssfd; + + Py_XINCREF(ob1); + Py_XINCREF(ob2); + Py_XINCREF(ob3); + cf->ob1 = ob1; + cf->ob2 = ob2; + cf->ob3 = ob3; + /* already set cf->i = 0 */ + assert(cf->i == 0); + cf->n = n; + cf->any1 = any; + SLP_STORE_NEXT_FRAME(ts, (PyFrameObject *) cf); + Py_DECREF(cf); + if (arg == NULL) + arg = Py_None; + Py_INCREF(arg); + return STACKLESS_PACK(ts, arg); + } else { + /* + * Call the PySoftSwitchableFunc direct + */ + PyObject *retval, *saved_ob1, *saved_ob2, *saved_ob3; + long step = 0; + + if (arg == NULL) + arg = Py_None; + Py_INCREF(arg); + + saved_ob1 = ob1; + saved_ob2 = ob2; + saved_ob3 = ob3; + Py_XINCREF(saved_ob1); + Py_XINCREF(saved_ob2); + Py_XINCREF(saved_ob3); + retval = ssfd->sfunc(arg, &step, &ob1, &ob2, &ob3, &n, &any); + STACKLESS_ASSERT(); + assert(!STACKLESS_UNWINDING(retval)); + Py_XDECREF(saved_ob1); + Py_XDECREF(saved_ob2); + Py_XDECREF(saved_ob3); + Py_DECREF(arg); + return retval; + } +} + #endif diff --git a/Stackless/module/_teststackless.c b/Stackless/module/_teststackless.c index 216dee954a5729..43240397619954 100644 --- a/Stackless/module/_teststackless.c +++ b/Stackless/module/_teststackless.c @@ -22,7 +22,201 @@ #include "malloc.h" /* for alloca */ #endif - /****************************************************** +/* + * Demo and test code for soft switchable C-functions + */ + +typedef struct { + PyObject_HEAD +} SoftSwitchableDemoObject; + +static PyTypeObject SoftSwitchableDemo_Type; + +#define SoftSwitchableDemoObject_Check(v) (Py_TYPE(v) == &SoftSwitchableDemo_Type) + +/* SoftSwitchableDemo methods */ + + +static +PyObject * +demo_soft_switchable(PyObject *retval, long *step, PyObject **ob1, PyObject **ob2, PyObject **ob3, long *n, void **any) +{ + int do_schedule = *n; + struct { + /* + * If *ob1, *ob2, *ob3, *n and *any are insufficient for the state of this method, + * you can define state variables here and store this structure in *any. + */ + int var1; + } *state = *any; + + Py_INCREF(retval); /* we need our own reference */ + + switch(*step) { + case 0: + (*step)++; + /* + * Initialize state + */ + *any = state = PyMem_Calloc(1, sizeof(*state)); + if (state == NULL) { + Py_CLEAR(retval); + goto exit_func; + } + + /* + * Add your business logic here + */ + state->var1++; /* This example is a bit simplistic */ + + /* + * Now eventually schedule. + */ + if (do_schedule) { + Py_SETREF(retval, PyStackless_Schedule(retval, do_schedule > 1)); + if (STACKLESS_UNWINDING(retval)) + return retval; + else if (retval == NULL) + goto exit_func; + } + /* no break */ + case 1: + (*step)++; + /* + * Add more business logic here + */ + + state->var1++; /* This example is a bit simplistic */ + /* now get rid of the state in *any, because it can't be pickled. */ + *n = state->var1; + PyMem_Free(*any); + *any = NULL; /* only if *any is NULL, the state can be pickled. */ + + /* + * Now eventually schedule. + */ + if (do_schedule) { + Py_SETREF(retval, PyStackless_Schedule(retval, do_schedule > 1)); + if (STACKLESS_UNWINDING(retval)) + return retval; + else if (retval == NULL) + goto exit_func; + } + + /* + * And so on ... + */ + /* no break */ + case 2: + break; + default: + PyErr_SetString(PyExc_SystemError, "invalid state"); + Py_CLEAR(retval); + goto exit_func; + } + + /* + * Prepare the result + */ + Py_SETREF(retval, PyLong_FromLong(*n)); + +exit_func: + PyMem_Free(*any); + *any = NULL; + return retval; +} + + +static PyStacklessFunctionDeclarationObject demo_soft_switchable_declaration = { + PyObject_HEAD_INIT(NULL) + demo_soft_switchable, + "demo_soft_switchable" +}; + + +static PyObject * +SoftSwitchableDemo_demo(SoftSwitchableDemoObject *self, PyObject *args) +{ + PyObject *result; + int action; + if (!PyArg_ParseTuple(args, "iO:demo", &action, &result)) + return NULL; + + return PyStackless_CallFunction(&demo_soft_switchable_declaration, result, NULL, NULL, NULL, action, NULL); +} + + +static PyMethodDef SoftSwitchableDemo_methods[] = { + {"demo", (PyCFunction)SoftSwitchableDemo_demo, METH_VARARGS | METH_STACKLESS, + PyDoc_STR("demo(action, result) -> result")}, + {NULL, NULL} /* sentinel */ +}; + + +static PyTypeObject SoftSwitchableDemo_Type = { + /* The ob_type field must be initialized in the module init function + * to be portable to Windows without using C++. */ + PyVarObject_HEAD_INIT(NULL, 0) + "_teststackless.SoftSwitchableDemo", /*tp_name*/ + sizeof(SoftSwitchableDemoObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_reserved*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + SoftSwitchableDemo_methods, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + PyType_GenericNew, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; +/* --------------------------------------------------------------------- */ + +/* Function of two integers returning integer */ + +PyDoc_STRVAR(_teststackless_softswitchabledemo_doc, "demo(action, result) -> result"); + +static PyObject * +_teststackless_softswitchabledemo(PyObject *self, PyObject *args) +{ + PyObject *result; + int action; + if (!PyArg_ParseTuple(args, "iO:demo", &action, &result)) + return NULL; + + return PyStackless_CallFunction(&demo_soft_switchable_declaration, result, NULL, NULL, NULL, action, NULL); +} + + +/****************************************************** some test functions @@ -186,6 +380,8 @@ static PyObject* test_PyEval_EvalFrameEx(PyObject *self, PyObject *args, PyObjec /* List of functions defined in the module */ static PyMethodDef _teststackless_methods[] = { + {"softswitchablefunc", _teststackless_softswitchabledemo, METH_VARARGS | METH_STACKLESS, + _teststackless_softswitchabledemo_doc}, { "test_cframe", (PyCFunction) test_cframe, METH_VARARGS | METH_KEYWORDS, test_cframe__doc__ }, { "test_cstate", (PyCFunction) test_cstate, METH_O, @@ -204,7 +400,9 @@ _teststackless_exec(PyObject *m) { /* Finalize the type object including setting type of the new type * object; doing it here is required for portability, too. */ - if (0 /* add calls here */) + if (PyType_Ready(&SoftSwitchableDemo_Type) < 0 || + PyModule_AddObject(m, "SoftSwitchableDemo", (PyObject *)&SoftSwitchableDemo_Type) < 0 || + PyStackless_InitFunctionDeclaration(&demo_soft_switchable_declaration, m, &_teststacklessmodule) < 0 ) goto fail; return 0; diff --git a/Stackless/module/stacklessmodule.c b/Stackless/module/stacklessmodule.c index 16ca814d119926..ece69f5a9d6c3e 100644 --- a/Stackless/module/stacklessmodule.c +++ b/Stackless/module/stacklessmodule.c @@ -1758,6 +1758,7 @@ _PyStackless_InitTypes(void) || slp_init_bombtype() || init_stackless_methods() || PyType_Ready(&PyTasklet_Type) /* need this early for the main tasklet */ + || slp_init_cframetype() ) return 0; return -1; @@ -1784,7 +1785,6 @@ PyInit__stackless(void) }; if (0 - || slp_init_cframetype() || PyType_Ready(&PyChannel_Type) || PyType_Ready(&PyAtomic_Type) ) diff --git a/Stackless/stackless_api.h b/Stackless/stackless_api.h index f94714c92bf916..28376815d386e4 100644 --- a/Stackless/stackless_api.h +++ b/Stackless/stackless_api.h @@ -359,6 +359,38 @@ PyAPI_FUNC(unsigned long) PyStackless_GetCurrentId(void); PyAPI_FUNC(PyObject *) PyStackless_RunWatchdog(long timeout); PyAPI_FUNC(PyObject *) PyStackless_RunWatchdogEx(long timeout, int flags); +/****************************************************** + + Support for soft switchable extension functions: SLFunction + + These functions have been added on a provisional basis (See PEP 411). + + ******************************************************/ + +typedef PyObject *(slp_softswitchablefunc) (PyObject *retval, + long *step, PyObject **ob1, PyObject **ob2, PyObject **ob3, + long *n, void **any); + +typedef struct { + PyObject_HEAD + slp_softswitchablefunc * sfunc; + const char * name; + const char * module_name; +} PyStacklessFunctionDeclarationObject; + +PyAPI_DATA(PyTypeObject) PyStacklessFunctionDeclaration_Type; + +#define PyStacklessFunctionDeclarationType_CheckExact(op) \ + (Py_TYPE(op) == &PyStacklessFunctionDeclaration_Type) + +PyAPI_FUNC(PyObject *) PyStackless_CallFunction( + PyStacklessFunctionDeclarationObject *sfd, PyObject *arg, + PyObject *ob1, PyObject *ob2, PyObject *ob3, long n, void *any); + +PyAPI_FUNC(int) PyStackless_InitFunctionDeclaration( + PyStacklessFunctionDeclarationObject *sfd, PyObject *module, PyModuleDef *module_def); + + /****************************************************** debugging and monitoring functions diff --git a/Stackless/unittests/test_capi.py b/Stackless/unittests/test_capi.py index c3e9d1e39878d0..8d2bfdc99db48b 100644 --- a/Stackless/unittests/test_capi.py +++ b/Stackless/unittests/test_capi.py @@ -29,12 +29,18 @@ import time import traceback import unittest +import os +import subprocess +import glob +import pickle import _teststackless from support import test_main # @UnusedImport from support import (StacklessTestCase, withThreads, require_one_thread, testcase_leaks_references) +from test.support import args_from_interpreter_flags, temp_cwd + if withThreads: try: import thread as _thread @@ -202,6 +208,134 @@ def f2(arg): self.assertIs(_teststackless.test_PyEval_EvalFrameEx(f.__code__, f.__globals__, (f2,), code2=f2.__code__), f2) +OBJ1 = object() +OBJ2 = object() + + +class Test_SoftSwitchableExtensionFunctions(StacklessTestCase): + # Tests for PyStackless_CallFunction() + def _test_func(self, func): + + is_soft = stackless.enable_softswitch(None) + self.tasklet_result = None + + def task(action, retval): + r = func(action, retval) + self.tasklet_result = r + + t = stackless.tasklet(task)(2, OBJ1) + t.remove() + t.run() + self.assertTrue(t.alive) + self.assertFalse(t.scheduled) + self.assertEqual(t.restorable, is_soft) + self.assertIs(t.tempval, OBJ1) + # is_soft = False + if is_soft: + p1 = pickle.dumps(t) + t.tempval = OBJ2 + t.insert() + stackless.run() + self.assertTrue(t.alive) + self.assertFalse(t.scheduled) + self.assertIs(t.tempval, OBJ2) + if is_soft: + p2 = pickle.dumps(t) + + t.insert() + stackless.run() + self.assertEqual(self.tasklet_result, 2) + self.assertFalse(t.alive) + + # the same for action == 1, schedule, but don't remove + self.tasklet_result = None + t = stackless.tasklet(task)(1, OBJ1) + stackless.run() + self.assertEqual(self.tasklet_result, 2) + self.assertFalse(t.alive) + + # the same for action == 0, don't schedule + self.tasklet_result = None + t = stackless.tasklet(task)(0, OBJ1) + stackless.run() + self.assertEqual(self.tasklet_result, 2) + self.assertFalse(t.alive) + + if not is_soft: + return + + # the same for p2 + self.tasklet_result = None + t = pickle.loads(p2) + self.assertTrue(t.alive) + t.insert() + stackless.run() + self.assertEqual(self.tasklet_result, 2) + self.assertFalse(t.alive) + + # the same for p1. It is expected to raise + self.tasklet_result = None + t = pickle.loads(p1) + self.assertTrue(t.alive) + t.insert() + self.assertRaisesRegex(RuntimeError, 'execute_soft_switchable_func', stackless.run) + self.assertEqual(self.tasklet_result, None) + self.assertFalse(t.alive) + + def test_softswitchablemethod(self): + f = _teststackless.SoftSwitchableDemo().demo + self._test_func(f) + + def test_softswitchablefunc(self): + f = _teststackless.softswitchablefunc + self._test_func(f) + + +class TestDemoExtensions(unittest.TestCase): + def setUp(self): + super(TestDemoExtensions, self).setUp() + demo_dir = os.path.normpath(os.path.join(os.path.abspath(__file__), + os.pardir, os.pardir, "demo", + self.id().rpartition('.')[2].partition('_')[2])) + if not os.path.isdir(demo_dir): + self.skipTest('Not a directory: ' + demo_dir) + self.demo_dir = demo_dir + self.verbose = True + + def _test_soft_switchable_extension(self): + with temp_cwd(self.id()) as tmpdir: + args = [sys.executable] + args.extend(args_from_interpreter_flags()) + args.extend(["setup.py", "build", "-b", tmpdir, "install_lib", "-d", tmpdir, "--no-compile"]) + try: + output = subprocess.check_output(args, stdin=subprocess.DEVNULL, stderr=subprocess.STDOUT, cwd=self.demo_dir) + except subprocess.CalledProcessError as e: + if e.stdout: + sys.stdout.buffer.write(e.stdout) + if e.stderr: + sys.stderr.buffer.write(e.stderr) + raise + if self.verbose: + sys.stdout.buffer.write(output) + py = glob.glob("*.py") + self.assertEqual(len(py), 1) + py = py[0] + + args = [sys.executable] + args.extend(args_from_interpreter_flags()) + args.extend([py, '-v']) + try: + output = subprocess.check_output(args, stdin=subprocess.DEVNULL, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + if e.stdout: + sys.stdout.buffer.write(e.stdout) + if e.stderr: + sys.stderr.buffer.write(e.stderr) + raise + if self.verbose: + sys.stdout.buffer.write(output) + + if __name__ == "__main__": if not sys.argv[1:]: sys.argv.append('-v')