Stackless-Python C-API¶
Stackless-Python uses two fundamentally different methods to switch control flow from one tasklet to another. One method, called hard-switching, manipulates the C-stack with hardware-dependent assembly code. This is always possible, but somewhat costly. The other method, called soft-switching, is only possible under special conditions, but is cheap. Moreover, soft-switching allows the storage (pickling) and recovery (unpickling) of active tasklets.
Soft-switching avoids recursive calls to the Python® interpreter, such as those
that occur when calling a Python® function, by maintaining a chained list of
tasks that are processed sequentially. This list consists of
PyFrameObject
and PyCFrameObject
objects chained by their PyFrameObject.f_back
pointer. In the C-function
slp_dispatch()
(and slp_dispatch_top()
) the list is processed
in a loop. In order to proceed
to the processing of the next (C)frame, all C-functions involved in the
processing of the current (C)frame must return. A special return value
Unwind-Token is used here. If a C-function returns the value Py_UnwindToken
,
its caller must add any unfinished tasks to the (C)frame list and return
Py_UnwindToken
itself. It follows that soft-switching is only possible if
it is supported by all functions just called. If this is not the case,
hard-switching remains as a fallback.
Note
Some switching functions have a variant with the same name, but ending on “_nr”. These are non-recursive versions with the same functionality, but they might avoid a hard stack switch. Their return value is ternary, and they require the caller to return to its frame, properly. All three different cases must be treated.
Ternary return from an integer function:
value |
meaning |
action |
---|---|---|
-1 |
failure |
return NULL |
1 |
soft switched |
return |
0 |
hard switched |
return |
Ternary return from a PyObject * function:
value |
meaning |
action |
---|---|---|
NULL |
failure |
return NULL |
Py_UnwindToken |
soft switched |
return |
other |
hard switched |
return value |
Stackless-Python provides the following C functions. Include <stackless_api.h>
.
Tasklets¶
-
PyTaskletObject *
PyTasklet_New
(PyTypeObject *type, PyObject *func)¶ Return a new tasklet object. type must be derived from
PyTasklet_Type
orNULL
. func must be a callable object orNULL
orPy_None
. If func isNULL
orPy_None
you must set it later withPyTasklet_BindEx()
.
-
int
PyTasklet_Setup
(PyTaskletObject *task, PyObject *args, PyObject *kwds)¶ Binds a tasklet function to parameters, making it ready to run and inserts in into the runnables queue. Returns
0
if successful or-1
in the case of failure.
-
int
PyTasklet_BindEx
(PyTaskletObject *task, PyObject *func, PyObject *args, PyObject *kwargs)¶ Binds a tasklet to a function and/or to parameters, making it ready to run. This is the C equivalent to method
tasklet.bind()
. The arguments func, args and kwargs are optional and may beNULL
orPy_None
. Returns0
if successful or-1
in the case of failure.
-
int
PyTasklet_BindThread
(PyTaskletObject *task, unsigned long thread_id)¶ Binds a tasklet function to a thread. This is the C equivalent to method
tasklet.bind_thread()
. Returns0
if successful or-1
in the case of failure.
-
int
PyTasklet_Run
(PyTaskletObject *task)¶ Forces task to run immediately. Returns
0
if successful, and-1
in the case of failure.
-
int
PyTasklet_Run_nr
(PyTaskletObject *task)¶ Forces task to run immediately, soft switching if possible. Returns
1
if the call soft switched,0
if the call hard switched and -1 in the case of failure.
-
int
PyTasklet_Switch
(PyTaskletObject *task)¶ Forces task to run immediately. The previous tasklet is paused. Returns
0
if successful, and-1
in the case of failure.
-
int
PyTasklet_Switch_nr
(PyTaskletObject *task)¶ Forces task to run immediately, soft switching if possible. The previous tasklet is paused. Returns
1
if the call soft switched,0
if the call hard switched and -1 in the case of failure.
-
int
PyTasklet_Remove
(PyTaskletObject *task)¶ Removes task from the runnables queue. Be careful! If this tasklet has a C stack attached, you need to either resume running it or kill it. Just dropping it might give an inconsistent system state. Returns
0
if successful, and-1
in the case of failure.
-
int
PyTasklet_Insert
(PyTaskletObject *task)¶ Insert task into the runnables queue, if it isn’t already there. If it is blocked or dead, the function returns
-1
and aRuntimeError
is raised.
-
int
PyTasklet_RaiseException
(PyTaskletObject *self, PyObject *klass, PyObject *args)¶ Raises an instance of the klass exception on the self tasklet. klass must be a subclass of
Exception
. Returns1
if the call soft switched,0
if the call hard switched and-1
in the case of failure.Note
Raising
TaskletExit
on a tasklet can be done to silently kill it, seePyTasklet_Kill()
.
-
int
PyTasklet_Throw
(PyTaskletObject *self, int pending, PyObject *exc, PyObject *val, PyObject *tb)¶ Raises (exc, val, tb) on the self tasklet. This is the C equivalent to method
tasklet.throw()
. Returns1
if the call soft switched,0
if the call hard switched and-1
in the case of failure.
-
int
PyTasklet_Kill
(PyTaskletObject *self)¶ Raises
TaskletExit
on tasklet self. This should result in task being silently killed. (This exception is ignored by tasklet_end and does not invoke main as exception handler.) Returns1
if the call soft switched,0
if the call hard switched and-1
in the case of failure.
-
int
PyTasklet_KillEx
(PyTaskletObject *self, int pending)¶ Raises
TaskletExit
on tasklet self. This is the C equivalent to methodtasklet.kill()
. Returns1
if the call soft switched,0
if the call hard switched and-1
in the case of failure.
-
int
PyTasklet_GetAtomic
(PyTaskletObject *task)¶ Returns
1
if task is atomic, otherwise0
.
-
int
PyTasklet_SetAtomic
(PyTaskletObject *task, int flag)¶ Returns
1
if task is currently atomic, otherwise0
. Sets the atomic attribute to the logical value of flag.
-
int
PyTasklet_GetIgnoreNesting
(PyTaskletObject *task)¶ Returns
1
if task ignores its nesting level when choosing whether to auto-schedule it, otherwise0
.
-
int
PyTasklet_SetIgnoreNesting
(PyTaskletObject *task, int flag)¶ Returns the existing value of the ignore_nesting attribute for the tasklet task, setting it to the logical value of flag. If true, the tasklet may be auto-scheduled even if its nesting_level is >
0
.
-
int
PyTasklet_GetBlockTrap
(PyTaskletObject *task)¶ Returns
1
if task is designated as not being allowed to be blocked on a channel, otherwise0
.
-
void
PyTasklet_SetBlockTrap
(PyTaskletObject *task, int value)¶ Returns
1
if task was already designated as not being allowed to be blocked on a channel, otherwise0
. This attribute is set to the logical value of value.
-
PyObject *
PyTasklet_GetFrame
(PyTaskletObject *task)¶ Returns the current frame that task is executing in, or NULL
-
int
PyTasklet_IsMain
(PyTaskletObject *task)¶ Returns
1
if task is the main tasklet, otherwise0
.
-
int
PyTasklet_IsCurrent
(PyTaskletObject *task)¶ Returns
1
if task is the current tasklet, otherwise0
.
-
int
PyTasklet_GetRecursionDepth
(PyTaskletObject *task)¶ Return the current recursion depth of task.
-
int
PyTasklet_GetNestingLevel
(PyTaskletObject *task)¶ Return the current nesting level of task.
-
int
PyTasklet_Alive
(PyTaskletObject *task)¶ Returns
1
if task is alive (has an associated frame), otherwise0
if it is dead.
-
int
PyTasklet_Paused
(PyTaskletObject *task)¶ Returns
1
if task is paused, otherwise0
. A tasklet is paused if it is alive, but not scheduled or blocked on a channel.
-
int
PyTasklet_Scheduled
(PyTaskletObject *task)¶ Returns
1
if task is scheduled, otherwise0
. In the context of this function a tasklet is considered to be scheduled if it is alive, and in the scheduler runnables list or blocked on a channel.
-
int
PyTasklet_Restorable
(PyTaskletObject *task)¶ Returns
1
if task can be fully unpickled, otherwise0
. A tasklet can be pickled whether it is fully restorable or not for the purposes of debugging and introspection. A tasklet that has been hard-switched cannot be fully pickled, for instance.
Channels¶
-
PyChannelObject*
PyChannel_New
(PyTypeObject *type)¶ Return a new channel object, or NULL in the case of failure. type must be derived from
PyChannel_Type
or be NULL, otherwise aTypeError
is raised.
-
int
PyChannel_Send
(PyChannelObject *self, PyObject *arg)¶ Send arg on the channel self. Returns
0
if the operation was successful, or-1
in the case of failure.
-
int
PyChannel_Send_nr
(PyChannelObject *self, PyObject *arg)¶ Send arg on the channel self, soft switching if possible. Returns
1
if the call soft switched,0
if the call hard switched and -1 in the case of failure.
-
PyObject *
PyChannel_Receive
(PyChannelObject *self)¶ Receive on the channel self. Returns a Python® object if the operation was successful, or NULL in the case of failure.
-
PyObject *
PyChannel_Receive_nr
(PyChannelObject *self)¶ Receive on the channel self, soft switching if possible. Returns a Python® object if the operation was successful,
Py_UnwindToken
if a soft switch occurred, or NULL in the case of failure.
-
int
PyChannel_SendException
(PyChannelObject *self, PyObject *klass, PyObject *value)¶ Returns
0
if successful or-1
in the case of failure. An instance of the exception type klass is raised on the first tasklet blocked on channel self.
-
int
PyChannel_SendThrow
(PyChannelObject *self, PyObject *exc, PyObject *val, PyObject *tb)¶ Returns
0
if successful or-1
in the case of failure. (exc, val, tb) is raised on the first tasklet blocked on channel self.
-
PyObject *
PyChannel_GetQueue
(PyChannelObject *self)¶ Returns the first tasklet in the channel self’s queue, or NULL in the case the queue is empty.
-
void
PyChannel_Close
(PyChannelObject *self)¶ Marks the channel self as closing. No further tasklets can be blocked on the it from this point, unless it is later reopened.
-
void
PyChannel_Open
(PyChannelObject *self)¶ Reopens the channel self. This allows tasklets to once again send and receive on it, if those operations would otherwise block the given tasklet.
-
int
PyChannel_GetClosing
(PyChannelObject *self)¶ Returns
1
if the channel self is marked as closing, otherwise0
.
-
int
PyChannel_GetClosed
(PyChannelObject *self)¶ Returns
1
if the channel self is marked as closing and there are no tasklets blocked on it, otherwise0
.
-
int
PyChannel_GetPreference
(PyChannelObject *self)¶ Returns the current scheduling preference value of self. See
channel.preference
.
-
void
PyChannel_SetPreference
(PyChannelObject *self, int val)¶ Sets the current scheduling preference value of self. See
channel.preference
.
-
int
PyChannel_GetScheduleAll
(PyChannelObject *self)¶ Gets the schedule_all override flag for self. See
channel.schedule_all
.
-
void
PyChannel_SetScheduleAll
(PyChannelObject *self, int val)¶ Sets the schedule_all override flag for self. See
channel.schedule_all
.
-
int
PyChannel_GetBalance
(PyChannelObject *self)¶ Gets the balance for self. See
channel.balance
.
Module stackless
¶
-
PyObject *
PyStackless_Schedule
(PyObject *retval, int remove)¶ Suspend the current tasklet and schedule the next one in the cyclic chain. if remove is nonzero, the current tasklet will be removed from the chain. retval = success NULL = failure
-
PyObject *
PyStackless_Schedule_nr
(PyObject *retval, int remove)¶ retval = success NULL = failure retval == Py_UnwindToken: soft switched
-
int
PyStackless_GetRunCount
()¶ get the number of runnable tasks of the current thread, including the current one. -1 = failure
-
unsigned long
PyStackless_GetCurrentId
()¶ Get a unique integer ID for the current tasklet
Threadsafe.
This is useful for benchmarking code that needs to get some sort of a stack identifier and must not worry about the GIL being present and so on.
Note
the “main” tasklet on each thread will have the same id, even if a proper tasklet has not been initialized.
IDs may get recycled for new tasklets.
-
PyObject *
PyStackless_RunWatchdog
(long timeout)¶ Runs the scheduler until there are no tasklets remaining within it, or until one of the scheduled tasklets runs for timeout VM instructions without blocking. Returns
Py_None
if the scheduler is empty, a tasklet object if that tasklet timed out, or NULL in the case of failure. If a timed out tasklet is returned, it should be killed or reinserted.This function can only be called from the main tasklet. During the run, main is suspended, but will be invoked after the action. You will write your exception handler here, since every uncaught exception will be directed to main.
-
PyObject *
PyStackless_RunWatchdogEx
(long timeout, int flags)¶ Wraps
PyStackless_RunWatchdog()
, but allows its behaviour to be customised by the value of flags which may contain any of the following bits:Py_WATCHDOG_THREADBLOCK
Allows a thread to block if it runs out of tasklets. Ideally it will be awakened by other threads using channels which its blocked tasklets are waiting on.
PY_WATCHDOG_SOFT
Instead of interrupting a tasklet, we wait until the next tasklet scheduling moment to return. Always returns
Py_None
, as everything is in order.PY_WATCHDOG_IGNORE_NESTING
Allows interrupts at all levels, effectively acting as though the ignore_nesting attribute were set on all tasklets.
PY_WATCHDOG_TIMEOUT
Interprets timeout as a fixed run time, rather than a per-tasklet run limit. The function will then attempt to interrupt execution once this many total opcodes have been executed since the call was made.
Soft-switchable extension functions¶
New in version 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. Soft-switchable functions obey the Stackless-protocol. At the C-language level such a function or method is made from 3 C-definitions:
A declaration object of type
PyStacklessFunctionDeclaration_Type
. It declares the soft-switchable function and must be declared as a global variable.A conventional extension function, that uses
PyStackless_CallFunction()
to call the soft-switchable function.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 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);
-
PyStacklessFunctionDeclarationObject
¶ This subtype of
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;
-
slp_softswitchablefunc
PyStacklessFunctionDeclarationObject.sfunc
¶ Pointer to implementation function.
-
const char *
PyStacklessFunctionDeclarationObject.name
¶ Name of the function.
-
const char *
PyStacklessFunctionDeclarationObject.module_name
¶ Name of the containing module.
-
slp_softswitchablefunc
-
PyTypeObject
PyStacklessFunctionDeclaration_Type
¶ This instance of
PyTypeObject
represents the Stackless soft-switchable extension function declaration type.
-
int
PyStacklessFunctionDeclarationType_CheckExact
(PyObject *p)¶ Return true if p is a PyStacklessFunctionDeclarationObject object, but not an instance of a subtype of this type.
-
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
Py_UnwindToken
.
-
int
PyStackless_InitFunctionDeclaration
(PyStacklessFunctionDeclarationObject *sfd, PyObject *module, PyModuleDef *module_def)¶ Initialize the fields
PyStacklessFunctionDeclarationObject.name
andPyStacklessFunctionDeclarationObject.module_name
of sfd.
Within the body of a soft-switchable extension function (or any other C-function, that obeys the stackless-protocol) you need the following macros.
Macros for the “Stackless-protocol”¶
How does a C-function in Stackless-Python decide whether it may return
Py_UnwindToken
? (After all, this is only allowed if the caller can handle
Py_UnwindToken
). The obvious thing would be to use your own function
argument, but that would change the function prototypes and thus
Python’s C-API. This is not practical. Instead, the global variable
“_PyStackless_TRY_STACKLESS”1 is used as an implicit parameter.
The content of this variable is moved to the local variable “stackless”
at the beginning of a C function. In the process, “_PyStackless_TRY_STACKLESS”
is set to 0, indicating that no unwind-token may be returned.
This is done with the macro STACKLESS_GETARG()
or, for vectorcall 2 functions,
with the macro STACKLESS_VECTORCALL_GETARG()
, which should be added at the
beginning of the function declaration.
This design minimizes the possibility of introducing errors due to improper
return of Py_UnwindToken
. The function can contain arbitrary code because the
flag is hidden in a local variable. If the function is to support
soft-switching, it must be further adapted. The flag may only be passed to
other called functions if they adhere to the Stackless-protocol. The macros
STACKLESS_PROMOTExxx() serve this purpose. To ensure compliance with the
protocol, the macro STACKLESS_ASSERT()
must be called after each such call.
An exception is the call of vectorcall functions. The call of a vectorcall
function must be framed with the macros STACKLESS_VECTORCALL_BEFORE()
and
STACKLESS_VECTORCALL_AFTER()
or - more simply - performed with the macro
STACKLESS_VECTORCALL()
.
Many internal functions have been patched to support this protocol.
Their first action is a direct or indirect call of the macro
STACKLESS_GETARG()
or STACKLESS_VECTORCALL_GETARG()
.
-
STACKLESS_GETARG
()¶ Define and initialize the local variable
int stackless
. The value of stackless is non-zero, if the function may returnPy_UnwindToken
. After a call toSTACKLESS_GETARG()
the value of the global variable “_PyStackless_TRY_STACKLESS” is 0.
-
STACKLESS_VECTORCALL_GETARG
(func)¶ New in version 3.8.0.
Vectorcall variant of the macro
STACKLESS_GETARG()
. Functions of typevectorcallfunc
must useSTACKLESS_VECTORCALL_GETARG()
instead ofSTACKLESS_GETARG()
. The argument func must be set to the vectorcall function itself. See function_PyCFunction_Vectorcall()
for an example.
-
STACKLESS_PROMOTE_ALL
()¶ All STACKLESS_PROMOTExxx() macros are used to propagate the stackless-flag from the local variable “stackless” to the global variable “_PyStackless_TRY_STACKLESS”. These macros can’t be used to call a vectorcall 2 function.
The macro
STACKLESS_PROMOTE_ALL()
does this unconditionally. It is used for cases where we know that the called function obeys the stackless-protocol by calling STACKLESS_GETARG() and possibly returning the unwind token. For example, PyObject_Call() and all other Py{Object,Function,CFunction}_*Call* functions use STACKLESS_GETARG() and STACKLESS_PROMOTE_xxx itself, so we don’t need to check further.
-
STACKLESS_PROMOTE_FLAG
(flag)¶ This macro is the most general conditional variant. If the local variable “stackless” was set, it sets the global variable “_PyStackless_TRY_STACKLESS” to flag and returns flag. Otherwise the macro returns 0. It is used for special cases, like PyCFunction objects. PyCFunction_Type says that it supports a stackless call, but the final action depends on the METH_STACKLESS flag in the object to be called. Therefore, PyCFunction_Call uses
STACKLESS_PROMOTE_FLAG(flags & METH_STACKLESS)
to take care of PyCFunctions which don’t care about it.Another example is the “next” method of iterators. To support this, the wrapperobject’s type has the Py_TPFLAGS_HAVE_STACKLESS_CALL flag set, but wrapper_call then examines the wrapper descriptors flags if PyWrapperFlag_STACKLESS is set. “next” has it set. It also checks whether Py_TPFLAGS_HAVE_STACKLESS_CALL is set for the iterator’s type.
-
STACKLESS_PROMOTE_METHOD
(obj, slot_name)¶ If the local variable “stackless” was set and if the type method for the slot slot_name of the type of object obj obeys the stackless-protocol, then _PyStackless_TRY_STACKLESS is set to 1, and we expect that the function handles it correctly.
-
STACKLESS_PROMOTE
(obj)¶ A special optimized variant of
STACKLESS_PROMOTE_METHOD(
obj, tp_call)
.
-
STACKLESS_ASSERT
()¶ In debug builds this macro asserts that _PyStackless_TRY_STACKLESS was cleared. This debug feature tries to ensure that no unexpected nonrecursive call can happen. In release builds this macro does nothing.
-
STACKLESS_RETRACT
()¶ Set the global variable “_PyStackless_TRY_STACKLESS” unconditionally to 0. Rarely used.
-
STACKLESS_VECTORCALL_BEFORE
(func)¶
-
STACKLESS_VECTORCALL_AFTER
(func)¶ New in version 3.8.0.
If a C-function needs to propagate the stackless-flag from the local variable “stackless” to the global variable “_PyStackless_TRY_STACKLESS” in order to call a vectorcall 2 function, it must frame the call with these macros. Set the argument func to the called function. The called function func is not required to support the Stackless-protocol. 3 Example:
STACKLESS_GETARG(); vectorcallfunc func = a_vectorcal_function; /* other code */ STACKLESS_VECTORCALL_BEFORE(func); PyObject * result = func(callable, args, nargsf, kwnames); STACKLESS_VECTORCALL_AFTER(func); return result;
-
STACKLESS_VECTORCALL
(func, callable, args, nargsf, kwnames)¶ New in version 3.8.0.
Call the vectorcall function func with the given arguments and return the result. It is a convenient alternative to the macros
STACKLESS_VECTORCALL_BEFORE()
andSTACKLESS_VECTORCALL_AFTER()
. The called function func is not required to support the Stackless-protocol.
Examples¶
The Stackless test-module _teststackless
contains the following
example for a soft switchable function.
To call it use
PyStackless_CallFunction(&demo_soft_switchable_declaration, result, NULL, NULL, NULL, action, NULL)
.
/*
* SoftSwitchableDemo methods
*
* The purpose of this demo is to test soft switchable extension methods and
* give an example of their usage. Otherwise the demo code is very simplistic.
*/
static
PyObject *
demo_soft_switchable(PyObject *retval, long *step, PyObject **ob1,
PyObject **ob2, PyObject **ob3, long *n, void **any)
{
/*
* Boiler plate code for soft switchable functions
*/
/* Every function, that supports the stackless-protocol starts with this
* macro. It defines the local variable "int stackless" and moves the value
* of the global variable "_PyStackless_TRY_STACKLESS" to "stackless".
*/
STACKLESS_GETARG();
/* This function returns a new reference.
* If retval is NULL, then PyErr_Occurred() is true.
*/
Py_XINCREF(retval);
/*
* Optional: define a struct for additional state
*/
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; // Minimal example
} *state = *any;
/*
* Specific vars for this example.
*/
int do_schedule = *n;
if (*step == 0 && *n >= 100)
*step = *n; // n==100: demo for calling back to Python
/*
* Always present: the switch, that is used to jump to the next step.
*
* Of course you are not limited to a linear sequence of steps, but
* this is simplest use case. If you think of a state machine, the variable
* (*step) is the number of the next state to enter.
* The initial value of (*step) is 0.
*/
switch(*step) {
case 0:
(*step)++; // set to the next step
/*
* Optional: initialize the state structure.
*/
*any = state = PyMem_Calloc(1, sizeof(*state));
if (state == NULL) {
Py_CLEAR(retval);
goto exit_func;
}
/*
* Code specific for this example
*/
state->var1++; /* This example is a bit simplistic */
/*
* Now eventually schedule.
*/
if (do_schedule) {
/*
* The function PyStackless_Schedule() supports the stackless-protocol.
* Therefore we may call TACKLESS_PROMOTE_ALL(). This macro copies the
* local variable "stackless" into "_PyStackless_TRY_STACKLESS".
*/
STACKLESS_PROMOTE_ALL(); // enable a stackless call
Py_SETREF(retval, PyStackless_Schedule(retval, do_schedule > 1));
/*
* In debug builds STACKLESS_ASSERT asserts, that the previously called
* function did reset "_PyStackless_TRY_STACKLESS". If you call
* STACKLESS_PROMOTE_ALL() (or one of its variants) prior to calling
* a function that does not support the stackless protocol, the
* assertion fails.
*/
STACKLESS_ASSERT(); // sanity check in debug builds
if (STACKLESS_UNWINDING(retval))
return retval;
else if (retval == NULL)
goto exit_func;
}
/* fall through */
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) {
STACKLESS_PROMOTE_ALL(); // enable a stackless call
Py_SETREF(retval, PyStackless_Schedule(retval, do_schedule > 1));
STACKLESS_ASSERT(); // sanity check in debug builds
if (STACKLESS_UNWINDING(retval))
return retval;
else if (retval == NULL)
goto exit_func;
}
/*
* And so on ...
*/
/* fall through */
case 2:
/*
* Prepare the result
*/
Py_SETREF(retval, PyLong_FromLong(*n));
break;
/*
* Demo code for calling back to Python.
*/
case 100:
(*step)++; // set to the next step
/*
* Here we demonstrate a stackless callback into Python code.
* This test assumes, that result is a callable object.
*
* The API function PyObject_Call supports the stackless protocoll.
* Therefore we may call STACKLESS_PROMOTE_ALL();
*/
PyObject * args = PyTuple_New(0);
if (!args) {
Py_CLEAR(retval);
goto exit_func;
}
STACKLESS_PROMOTE_ALL();
Py_SETREF(retval, PyObject_Call(retval, args, NULL));
STACKLESS_ASSERT();
Py_DECREF(args);
if (STACKLESS_UNWINDING(retval))
return retval;
/* fall through */
case 101:
(*step)++; // set to the next step
/*
* Error handling for the callback.
*
* This needs a step of its own, if the callback supports the
* stackless-protocol and fails.
*/
if (retval == NULL) {
assert(PyErr_Occurred());
if (! PyErr_ExceptionMatches(PyExc_Exception))
// it is a BaseException, don't handle it
return NULL;
/*
* A very simple error handling: fetch the exception set it as
* context of a new RuntimeError.
*/
PyObject *et, *ev, *tb;
PyErr_Fetch(&et, &ev, &tb);
PyErr_SetString(PyExc_RuntimeError, "demo_soft_switchable callback failed");
_PyErr_ChainExceptions(et, ev, tb);
}
break;
default:
/*
* Boiler plate code: error handling
*/
PyErr_SetString(PyExc_SystemError, "invalid state");
Py_CLEAR(retval);
}
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"
};
Another, more realistic example is _asyncio._task_step_impl_stackless
, defined in
“Modules/_asynciomodules.c”.
- 1
Actually “_PyStackless_TRY_STACKLESS” is a macro that expands to a C L-value. As long as standard Python® uses the GIL, this L-value is a global variable.
- 2(1,2,3)
See PEP 590 Vectorcall: a fast calling protocol for CPython
- 3
If a Python® type supports the PEP 590 Vectorcall-protocol the actual
vectorcallfunc
C-function is a per object property. This speeds up calling vectorcall functions on classes, but the consequence is, that it is no longer possible to use a flag in the type to indicate, if the vectorcall slot supports the Stackless-protocol. Therefore Stackless-Python has special macros to deal with vectorcall functions.
Debugging and monitoring Functions¶
-
int
PyStackless_SetChannelCallback
(PyObject *callable)¶ channel debugging. The callable will be called on every send or receive. Passing NULL removes the handler. Parameters of the callable: channel, tasklet, int sendflag, int willblock -1 = failure
-
int
PyStackless_SetScheduleCallback
(PyObject *callable)¶ scheduler monitoring. The callable will be called on every scheduling. Passing NULL removes the handler. Parameters of the callable: from, to When a tasklet dies, to is None. After death or when main starts up, from is None. -1 = failure
-
void
PyStackless_SetScheduleFastcallback
(slp_schedule_hook_func func)¶ Scheduler monitoring with a faster interface.
Other functions¶
Stack unwinding¶
-
PyUnwindObject *
Py_UnwindToken
¶ A singleton that indicates C-stack unwinding
Note
Py_UnwindToken
is never inc/decref’ed. Use the
macro STACKLESS_UNWINDING()
to test for
Py_UnwindToken.
-
int
STACKLESS_UNWINDING
(obj)¶ Return 1, if obj is
Py_UnwindToken
and 0 otherwise.
Interface functions¶
Most of the above functions can be called both from “inside” and “outside” stackless. “inside” means there should be a running (c)frame on top which acts as the “main tasklet”. The functions do a check whether the main tasklet exists, and wrap themselves if it is necessary. The following routines are used to support this, and you may use them as well if you need to make your specific functions always available.