/****************************************************** The Tasklet ******************************************************/ #include "Python.h" #ifdef STACKLESS #include "stackless_impl.h" #include "taskletobject.h" void slp_current_insert(PyTaskletObject *task) { PyThreadState *ts = PyThreadState_GET(); PyTaskletObject **chain = &ts->st.current; SLP_CHAIN_INSERT(PyTaskletObject, chain, task, next, prev); ++ts->st.runcount; } void slp_current_insert_after(PyTaskletObject *task) { PyThreadState *ts = PyThreadState_GET(); PyTaskletObject *hold = ts->st.current; PyTaskletObject **chain = &ts->st.current; *chain = hold->next; SLP_CHAIN_INSERT(PyTaskletObject, chain, task, next, prev); *chain = hold; ++ts->st.runcount; } PyTaskletObject * slp_current_remove(void) { PyThreadState *ts = PyThreadState_GET(); PyTaskletObject **chain = &ts->st.current, *ret; --ts->st.runcount; SLP_CHAIN_REMOVE(PyTaskletObject, chain, ret, next, prev) return ret; } static int tasklet_traverse(PyTaskletObject *t, visitproc visit, void *arg) { int err; #define VISIT(o) if (o) {if ((err = visit((PyObject *)(o), arg))) return err;} VISIT(t->f.frame); VISIT(t->tempval); VISIT(t->cstate); #undef VISIT return 0; } /* * the following function tries to ensure that a tasklet is * really killed. It is called in a context where we can't * afford that it will not be dead afterwards. * Reason: When clearing or resurrecting and killing, the * tasklet is in fact already dead, and the only case that * could revive it was that __del_ was defined. * But in the context of __del__, we can't do anything but rely * on proper destruction, since nobody will listen to an exception. */ static void kill_finally (PyTaskletObject *self) { PyThreadState *ts = PyThreadState_GET(); self->flags.is_zombie = 1; while (self->topframe != NULL) PyTasklet_Kill(self); /* * we might have been scheduled in tasklet_end. * Run the zombie until it is done. */ while (self->next != NULL) slp_schedule_task(ts->st.current, self); /* now we are sure that self has left the runnables */ } static int resurrect_and_kill(PyTaskletObject *self) { /* modelled after typeobject.c's call_finalizer */ PyObject *error_type, *error_value, *error_traceback; /* Temporarily resurrect the object. */ #ifdef Py_TRACE_REFS #ifndef Py_REF_DEBUG # error "Py_TRACE_REFS defined but Py_REF_DEBUG not." #endif /* much too complicated if Py_TRACE_REFS defined */ _Py_NewReference((PyObject *)self); #ifdef COUNT_ALLOCS /* compensate for boost in _Py_NewReference; note that * _Py_RefTotal was also boosted; we'll knock that down later. */ self->ob_type->tp_allocs--; #endif #else /* !Py_TRACE_REFS */ /* Py_INCREF boosts _Py_RefTotal if Py_REF_DEBUG is defined */ Py_INCREF(self); #endif /* !Py_TRACE_REFS */ /* Save the current exception, if any. */ PyErr_Fetch(&error_type, &error_value, &error_traceback); kill_finally(self); /* Restore the saved exception. */ PyErr_Restore(error_type, error_value, error_traceback); /* Undo the temporary resurrection; can't use DECREF here, it would * cause a recursive call. */ #ifdef Py_REF_DEBUG /* _Py_RefTotal was boosted either by _Py_NewReference or * Py_INCREF above. */ _Py_RefTotal--; #endif if (--self->ob_refcnt > 0) { #ifdef COUNT_ALLOCS self->ob_type->tp_frees--; #endif /* This should still be a tracked gc'ed object. */ assert(((PyGC_Head *)(self)-1)->gc.gc_next != NULL); return -1; /* __del__ added a reference; don't delete now */ } #ifdef Py_TRACE_REFS _Py_ForgetReference((PyObject *)self); #ifdef COUNT_ALLOCS /* compensate for increment in _Py_ForgetReference */ self->ob_type->tp_frees--; #endif #endif return 0; } /* destructing a tasklet without destroying it */ static void tasklet_clear(PyTaskletObject *t) { #define ZAP(x) \ if (x != NULL) { \ PyObject *_hold = (PyObject *) x; \ x = NULL; \ Py_DECREF(_hold); \ } /* ZAP(t->channel); */ if (t->topframe != NULL) { kill_finally(t); t->topframe = NULL; ZAP(t->f.frame); } ZAP(t->tempval); /* unlink task from cstate */ if (t->cstate != NULL && t->cstate->task == t) t->cstate->task = NULL; #undef ZAP } static void tasklet_dealloc(PyTaskletObject *t) { if (t->topframe != NULL) { /* * we want to cleanly kill the tasklet in the case it * was forgotten. One way would be to resurrect it, * but this is quite ugly with many ifdefs, see * classobject/typeobject. * Instead, we create a new tasklet and kill it. */ resurrect_and_kill(t); } if (t->tsk_weakreflist != NULL) PyObject_ClearWeakRefs((PyObject *)t); Py_XDECREF(t->f.frame); if (t->cstate != NULL) { if (t->cstate->task == t) t->cstate->task = NULL; Py_DECREF(t->cstate); } Py_XDECREF(t->tempval); t->ob_type->tp_free((PyObject*)t); } PyTaskletObject * PyTasklet_New(PyTypeObject *type, PyObject *func) { PyTaskletObject *t; if (func != NULL && !PyCallable_Check(func)) return TYPE_ERROR("tasklet function must be a callable", NULL); if (type == NULL) type = &PyTasklet_Type; assert(PyType_IsSubtype(type, &PyTasklet_Type)); t = (PyTaskletObject *) type->tp_alloc(type, 0); if (t != NULL) { *(int*)&t->flags = 0; /* t->channel = NULL; */ t->next = NULL; t->prev = NULL; t->f.frame = NULL; t->topframe = NULL; t->tempval = func; t->tsk_weakreflist = NULL; } Py_XINCREF(func); t->cstate = PyThreadState_GET()->st.initial_stub; Py_INCREF(t->cstate); return t; } PyTaskletObject * PyTasklet_Bind(PyTaskletObject *task, PyObject *func) { if (func == NULL || !PyCallable_Check(func)) return TYPE_ERROR("tasklet function must be a callable", NULL); if (task->topframe != NULL) return RUNTIME_ERROR("tasklet is already bound to a frame", NULL); Py_XDECREF(task->tempval); task->tempval = func; Py_XINCREF(func); Py_INCREF(task); return task; } static char tasklet_bind__doc__[] = "Binding a tasklet to a callable object.\n\ The callable is usually passed in to the constructor.\n\ In some cases, it makes sense to be able to re-bind a tasklet,\n\ after it has been run, in order to keep its identity.\n\ Note that a tasklet can only be bound when it doesn't have a frame.\ "; static PyObject * tasklet_bind(PyObject *self, PyObject *func) { return (PyObject *) PyTasklet_Bind ( (PyTaskletObject *) self, func); } #define TASKLET_TUPLEFMT "iiOiO" static PyObject * tasklet_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyFunctionObject *func = NULL; static char *kwlist[] = {"func", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:tasklet", kwlist, &func)) return NULL; return (PyObject*) PyTasklet_New(type, (PyObject*)func); } /* tasklet pickling support */ static char tasklet_reduce__doc__[] = "Pickling a tasklet for later re-animation.\n\ Note that a tasklet can always be pickled, unless it is current.\n\ Whether it can be run after unpickling depends on the state of the\n\ involved frames. In general, you cannot run a frame with a C state.\ "; /* Notes on pickling: We get into trouble with the normal __reduce__ protocol, since tasklets tend to have tasklets in tempval, and this creates infinite recursion on pickling. We therefore adopt the 3-element protocol of __reduce__, where the third thing is the argument tuple for __setstate__. Note that we don't use None as the second tuple. As explained in 'Pickling and unpickling extension types', this would call a property __basicnew__. This is more complicated, since __basicnew__ has no parameters, and we need to track the tasklet type. The easiest solution was to just use an empty tuple, which causes simply the tasklet() call without parameters. */ static PyObject * tasklet_reduce(PyTaskletObject * t) { PyObject *tup = NULL, *lis = NULL; PyFrameObject *f; PyThreadState *ts = PyThreadState_GET(); int have_tempval = (t->tempval != NULL) ? 1 : 0; if (t == ts->st.current) return RUNTIME_ERROR("You cannot __reduce__ the tasklet which is current.", NULL); lis = PyList_New(0); if (lis == NULL) goto err_exit; f = t->topframe ? t->f.frame : NULL; while (f != NULL && f != (PyFrameObject *) ts->st.tasklet_runner) { if (PyList_Append(lis, (PyObject *) f)) goto err_exit; f = f->f_back; } if (PyList_Reverse(lis)) goto err_exit; assert(t->cstate != NULL); tup = Py_BuildValue("(O()(" TASKLET_TUPLEFMT "))", &PyTasklet_Type, t->flags, have_tempval, have_tempval ? t->tempval : Py_None, t->cstate->nesting_level, lis ); err_exit: Py_XDECREF(lis); return tup; } static char tasklet_setstate__doc__[] = "Tasklets are first created without parameters, and then __setstate__\n\ is called. This was necessary, since pickle has problems pickling\n\ extension types when they reference themselves.\ "; /* note that args is a tuple, although we use METH_O */ static PyObject * tasklet_setstate(PyObject *self, PyObject *args) { PyTaskletObject *t = (PyTaskletObject *) self; PyObject *tempval, *lis; int flags, have_tempval, nesting_level; PyFrameObject *f; int i, nframes; if (!PyArg_ParseTuple(args, "iiOiO!:tasklet", &flags, &have_tempval, &tempval, &nesting_level, &PyList_Type, &lis)) return NULL; nframes = PyList_GET_SIZE(lis); if (have_tempval) { t->tempval = tempval; Py_INCREF(tempval); } *(int *)&t->flags = flags; /* t->nesting_level = nesting_level; XXX how do we handle this? */ if (nframes > 0) { PyFrameObject *back; PyThreadState *ts = PyThreadState_GET(); PyFrameObject *runner = (PyFrameObject *) ts->st.tasklet_runner; f = (PyFrameObject *) PyList_GET_ITEM(lis, 0); if ((f = slp_ensure_new_frame(f)) == NULL) return NULL; Py_INCREF(f); t->topframe = f; Py_INCREF(runner); f->f_back = runner; back = f; for (i=1; if_back = back; Py_INCREF(back); back = f; } t->f.frame = f; } /* walk frames again and calculate recursion_depth */ for (f = t->f.frame; f != NULL; f = f->f_back) { if (PyFrame_Check(f) && f->f_execute != PyEval_EvalFrame) { /* we count running frames which *have* added to recursion_depth */ ++t->recursion_depth; } } Py_INCREF(self); return self; } /* other tasklet methods */ static char tasklet_remove__doc__[] = "Removing a tasklet from the runnables queue.\n\ Note: If this tasklet has a non-trivial C stack attached,\n\ it will be destructed when the containing thread state is destroyed.\n\ Since this will happen in some unpredictable order, it may cause unwanted\n\ side-effects. Therefore it is recommended to either run tasklets to the\n\ end or to explicitly kill() them.\ "; static int PyTasklet_Remove_M(PyTaskletObject *task) { PyObject *ret = PyStackless_CallMethod_Main((PyObject*)task, "remove", NULL); return slp_return_wrapper(ret); } int PyTasklet_Remove(PyTaskletObject *task) { PyTasklet_HeapType *t = (PyTasklet_HeapType *)task->ob_type; return t->remove(task); } static TASKLET_REMOVE_HEAD(impl_tasklet_remove) { PyThreadState *ts = PyThreadState_GET(); PyTaskletObject *hold = ts->st.current; assert(PyTasklet_Check(task)); if (ts->st.main == NULL) return PyTasklet_Remove_M(task); assert(ts->st.current != NULL); if (task->flags.blocked) return RUNTIME_ERROR("You cannot remove a blocked tasklet.", -1); if (task == ts->st.current) return RUNTIME_ERROR("The current tasklet cannot be removed." " Use t=tasklet().capture()", -1); if (task->next == NULL) return 0; if (ts->st.runcount == 1 && slp_revive_main()) return RUNTIME_ERROR("the last runnable tasklet cannot be removed.", -1); ts->st.current = task; slp_current_remove(); Py_DECREF(task); ts->st.current = hold; return 0; } static TASKLET_REMOVE_HEAD(wrap_tasklet_remove) { PyObject * ret = PyObject_CallMethod((PyObject *)task, "remove", NULL); return slp_return_wrapper(ret); } static PyObject * tasklet_remove(PyObject *self) { if (impl_tasklet_remove((PyTaskletObject*) self)) return NULL; Py_INCREF(self); return self; } static char tasklet_insert__doc__[] = "Insert this tasklet at the end of the scheduler list,\n\ given that it isn't blocked.\n\ Blocked tasklets need to be reactivated by channels."; int PyTasklet_Insert(PyTaskletObject *task) { PyTasklet_HeapType *t = (PyTasklet_HeapType *)task->ob_type; return t->insert(task); } static TASKLET_INSERT_HEAD(impl_tasklet_insert) { PyThreadState *ts = PyThreadState_GET(); assert(PyTasklet_Check(task)); if (ts->st.main == NULL) return slp_current_wrapper(PyTasklet_Insert, task); if (task->flags.blocked) return RUNTIME_ERROR("You cannot run a blocked tasklet", -1); if (task->topframe == NULL) return RUNTIME_ERROR("You cannot run an unbound(dead) tasklet", -1); if (task->next == NULL) { Py_INCREF(task); slp_current_insert(task); } return 0; } static TASKLET_INSERT_HEAD(wrap_tasklet_insert) { PyObject * ret = PyObject_CallMethod((PyObject *)task, "insert", NULL); return slp_return_wrapper(ret); } static PyObject * tasklet_insert(PyObject *self) { if (impl_tasklet_insert((PyTaskletObject*) self)) return NULL; Py_INCREF(self); return self; } static char tasklet_run__doc__[] = "Run this tasklet, given that it isn't blocked.\n\ Blocked tasks need to be reactivated by channels."; static PyObject * PyTasklet_Run_M(PyTaskletObject *task) { return PyStackless_CallMethod_Main((PyObject*)task, "run", NULL); } int PyTasklet_Run_nr(PyTaskletObject *task) { PyTasklet_HeapType *t = (PyTasklet_HeapType *)task->ob_type; slp_try_stackless = 1; return slp_return_wrapper(t->run(task)); } int PyTasklet_Run(PyTaskletObject *task) { PyTasklet_HeapType *t = (PyTasklet_HeapType *)task->ob_type; return slp_return_wrapper(t->run(task)); } static TASKLET_RUN_HEAD(impl_tasklet_run) { STACKLESS_GETARG(); PyThreadState *ts = PyThreadState_GET(); PyObject *ret; assert(PyTasklet_Check(task)); if (ts->st.main == NULL) return PyTasklet_Run_M(task); if (PyTasklet_Insert(task)) return NULL; Py_INCREF(task); ts->st.tempval = (PyObject *) task; if (stackless) { switch (slp_schedule_nr_maybe(ts->st.current, task)) { case -1: return NULL; case 1: return Py_UnwindToken; } } if (slp_schedule_task(ts->st.current, task)) return NULL; if (ts->st.tempval == NULL) return NULL; ret = ts->st.tempval; ts->st.tempval = NULL; return ret; } static TASKLET_RUN_HEAD(wrap_tasklet_run) { return PyObject_CallMethod((PyObject *)task, "run", NULL); } static PyObject * tasklet_run(PyObject *self) { return impl_tasklet_run((PyTaskletObject *) self); } static char tasklet_set_atomic__doc__[] = "t.set_atomic(flag) -- set tasklet atomic status and return current one.\n\ If set, the tasklet will not be auto-scheduled.\n\ This flag is useful for critical sections which should not be interrupted.\n\ usage:\n\ tmp = t.set_atomic(1)\n\ # do critical stuff\n\ t.set_atomic(tmp)\n\ Note: Whenever a new tasklet is created, the atomic flag is initialized\n\ with the atomic flag of the current tasklet.\ Atomic behavior is additionally influenced by the interpreter nesting level.\ See set_ignore_nesting.\ "; int PyTasklet_SetAtomic(PyTaskletObject *task, int flag) { PyTasklet_HeapType *t = (PyTasklet_HeapType *)task->ob_type; return t->set_atomic(task, flag); } static TASKLET_SETATOMIC_HEAD(impl_tasklet_set_atomic) { int ret; PyTaskletFlagStruc * flags = slp_get_flags(task); ret = flags->atomic; flags->atomic = flag ? 1 : 0; if (flags->pending_irq && task == PyThreadState_GET()->st.current) slp_check_pending_irq(); return ret; } static TASKLET_SETATOMIC_HEAD(wrap_tasklet_set_atomic) { PyObject * ret = PyObject_CallMethod((PyObject *)task, "set_atomic", "(i)", flag); return slp_int_wrapper(ret); } static PyObject * tasklet_set_atomic(PyObject *self, PyObject *flag) { if (! (flag && PyInt_Check(flag)) ) return TYPE_ERROR("set_atomic needs exactly one bool or integer", NULL); return PyBool_FromLong(impl_tasklet_set_atomic((PyTaskletObject*)self, PyInt_AS_LONG(flag))); } static char tasklet_set_ignore_nesting__doc__[] = "t.set_ignore_nesting(flag) -- set tasklet ignore_nesting status and return current one.\n\ If set, the tasklet may be be auto-scheduled, even if its nesting_level is > 0.\n\ This flag makes sense if you know that nested interpreter levels are safe\n\ for auto-scheduling. This is on your own risk, handle with care!\n\ usage:\n\ tmp = t.set_ignore_nesting(1)\n\ # do critical stuff\n\ t.set_ignore_nesting(tmp)\ "; int PyTasklet_SetIgnoreNesting(PyTaskletObject *task, int flag) { PyTasklet_HeapType *t = (PyTasklet_HeapType *)task->ob_type; return t->set_ignore_nesting(task, flag); } static TASKLET_SETIGNORENESTING_HEAD(impl_tasklet_set_ignore_nesting) { int ret; PyTaskletFlagStruc * flags = slp_get_flags(task); ret = flags->ignore_nesting; flags->ignore_nesting = flag ? 1 : 0; if (flags->pending_irq && task == PyThreadState_GET()->st.current) slp_check_pending_irq(); return ret; } static TASKLET_SETIGNORENESTING_HEAD(wrap_tasklet_set_ignore_nesting) { PyObject * ret = PyObject_CallMethod((PyObject *)task, "set_ignore_nesting", "(i)", flag); return slp_int_wrapper(ret); } static PyObject * tasklet_set_ignore_nesting(PyObject *self, PyObject *flag) { if (! (flag && PyInt_Check(flag)) ) return TYPE_ERROR("set_ignore_nesting needs exactly one bool or integer", NULL); return PyBool_FromLong(impl_tasklet_set_ignore_nesting((PyTaskletObject*)self, PyInt_AS_LONG(flag))); } static int bind_tasklet_to_frame(PyTaskletObject *task, PyFrameObject *frame) { PyThreadState *ts = PyThreadState_GET(); if (task->topframe != NULL) return RUNTIME_ERROR("tasklet is already bound to a frame", -1); Py_INCREF(frame); task->f.frame = frame; task->topframe = frame; Py_XDECREF(task->tempval); task->tempval = Py_None; Py_INCREF(Py_None); if (task->cstate != ts->st.initial_stub) { PyCStackObject *hold = task->cstate; task->cstate = ts->st.initial_stub; Py_INCREF(task->cstate); Py_DECREF(hold); } return 0; /* note: We expect that f_back is NULL, or will be adjusted immediately */ } static char tasklet_become__doc__[] = "t.become(retval) -- catch the current running frame in a tasklet.\n\ It is also inserted at the end of the runnables chain.\n\ If it is a toplevel frame (and therefore has no caller), an exception is raised.\n\ The function result is the tasklet itself. retval is passed to the calling frame.\n\ If retval is not given, the tasklet is used as default.\ "; PyObject * PyTasklet_Become(PyTaskletObject *task, PyObject *retval) { PyTasklet_HeapType *t = (PyTasklet_HeapType *)task->ob_type; return t->become(task, retval); } static TASKLET_BECOME_HEAD(impl_tasklet_become) { PyThreadState *ts = PyThreadState_GET(); assert(PyTasklet_Check(task)); if ( ts->frame == NULL || ts->frame->f_back == NULL || ts->frame == ts->st.tasklet_runner || ts->frame->f_back == ts->st.tasklet_runner) return RUNTIME_ERROR("become/capture cannot be called from toplevel or no frame", NULL); /* now we have the bound frame. Create a tasklet. */ if (bind_tasklet_to_frame(task, ts->frame)) return NULL; ts->frame = ts->frame->f_back; --ts->recursion_depth; task->f.frame->f_back = NULL; Py_DECREF(ts->frame); slp_current_insert(task); Py_INCREF(task); if (retval == NULL) retval = (PyObject*)task; Py_XDECREF(task->tempval); task->tempval = (PyObject *) task; Py_INCREF(task); /* returned to caller */ ts->st.tempval = retval; Py_INCREF(retval); /* returned to caller's caller */ return Py_UnwindToken; } static TASKLET_BECOME_HEAD(wrap_tasklet_become) { PyObject * ret = PyObject_CallMethod((PyObject *)task, "become", "(O)", retval ? retval : (PyObject *) task); return ret; } static char tasklet_capture__doc__[] = "t.capture(retval) -- capture the current running frame in a tasklet,\n\ like t.become(). In addition the tasklet is run immediately, and the\n\ parent tasklet is removed from the runnables and returned as the value.\ "; PyObject * PyTasklet_Capture(PyTaskletObject *task, PyObject *retval) { PyTasklet_HeapType *t = (PyTasklet_HeapType *)task->ob_type; return t->capture(task, retval); } static PyObject * post_schedule_remove(PyFrameObject *f) { PyThreadState *ts = PyThreadState_GET(); PyObject *retval = (PyObject *) ts->st.current; ts->st.tempval = NULL; ts->frame = f->f_back; return PyStackless_Schedule_nr(retval, 1); } static TASKLET_CAPTURE_HEAD(impl_tasklet_capture) { PyThreadState *ts = PyThreadState_GET(); PyFrameObject *save; if (impl_tasklet_become(task, retval) != Py_UnwindToken) return NULL; Py_DECREF(retval); save = ts->frame; /* create a helper frame to perform the schedule_remove after return */ ts->frame = (PyFrameObject *) slp_baseframe_new(post_schedule_remove, 1, 0); if (ts->frame == NULL) { ts->frame = save; return NULL; } /* the retval is still in ts->st.tempval, so we're ready */ return Py_UnwindToken; } static TASKLET_CAPTURE_HEAD(wrap_tasklet_capture) { PyObject * ret = PyObject_CallMethod((PyObject *)task, "capture", "(O)", retval ? retval : (PyObject *) task); return ret; } static PyObject * tasklet_become(PyObject *self, PyObject *args, PyObject *kwds) { PyObject *retval = self; static char *kwlist[] = {"retval", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:become", kwlist, &retval)) return NULL; return impl_tasklet_become((PyTaskletObject *)self, retval); } static PyObject * tasklet_capture(PyObject *self, PyObject *args, PyObject *kwds) { PyObject *retval = self; static char *kwlist[] = {"retval", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:capture", kwlist, &retval)) return NULL; return impl_tasklet_capture((PyTaskletObject *)self, retval); } /* this is also the setup method */ static char tasklet_setup__doc__[] = "supply the parameters for the callable"; static int PyTasklet_Setup_M(PyTaskletObject *task, PyObject *args, PyObject *kwds) { PyObject *ret = PyStackless_Call_Main((PyObject*)task, args, kwds); return slp_return_wrapper(ret); } int PyTasklet_Setup(PyTaskletObject *task, PyObject *args, PyObject *kwds) { PyObject *ret = task->ob_type->tp_call((PyObject *) task, args, kwds); return slp_return_wrapper(ret); } static int impl_tasklet_setup(PyTaskletObject *task, PyObject *args, PyObject *kwds) { PyThreadState *ts = PyThreadState_GET(); PyFrameObject *frame; PyObject *func; assert(PyTasklet_Check(task)); if (ts->st.main == NULL) return PyTasklet_Setup_M(task, args, kwds); func = task->tempval; if (func == NULL) return RUNTIME_ERROR("the tasklet was not bound to a function", -1); if ((frame = (PyFrameObject *) slp_cframe_new(func, args, kwds, 0)) == NULL) { return -1; } if (bind_tasklet_to_frame(task, frame)) { Py_DECREF(frame); return -1; } Py_INCREF(task); slp_current_insert(task); return 0; } static PyObject * tasklet_setup(PyObject *self, PyObject *args, PyObject *kwds) { PyTaskletObject *task = (PyTaskletObject *) self; if (impl_tasklet_setup(task, args, kwds)) return NULL; Py_INCREF(task); return (PyObject*) task; } static char tasklet_raise_exception__doc__[] = "tasklet.raise_exception(exc, value) -- raise an exception for the tasklet.\n\ exc must be a subclass of Exception.\n\ The tasklet is immediately activated."; static PyObject * PyTasklet_RaiseException_M(PyTaskletObject *self, PyObject *klass, PyObject *args) { return PyStackless_CallMethod_Main((PyObject*)self, "raise_exception", "(OO)", klass, args); } int PyTasklet_RaiseException(PyTaskletObject *self, PyObject *klass, PyObject *args) { PyTasklet_HeapType *t = (PyTasklet_HeapType *)self->ob_type; return slp_return_wrapper(t->raise_exception(self, klass, args)); } static TASKLET_RAISE_EXCEPTION_HEAD(impl_tasklet_raise_exception) { STACKLESS_GETARG(); PyThreadState *ts = PyThreadState_GET(); PyObject *tb = NULL; PyObject *ret; if (ts->st.main == NULL) return PyTasklet_RaiseException_M(self, klass, args); if (! (PyObject_IsSubclass(klass, PyExc_Exception) == 1 || PyString_Check(klass) ) ) return TYPE_ERROR("tasklet.raise_exception needs Exception or string subclass as first parameter", NULL); if (self->topframe == NULL) return RUNTIME_ERROR("You cannot raise an exception in an unbound(dead) tasklet", NULL); Py_INCREF(klass); if (! PyTuple_Check(args)) { args = Py_BuildValue("(O)", args); if (args == NULL) return NULL; } else { Py_INCREF(args); } PyErr_NormalizeException(&klass, &args, &tb); PyErr_Restore(klass, args, tb); if (self == ts->st.current) { /* killing ourselves */ return NULL; } if (slp_get_flags(self)->blocked) { /* channels are still bad. We can't get out of them without a hard swith */ stackless = 0; } else if (self->next != NULL) { PyTasklet_Remove(self); } /* XXX we should track containment in a channel and be able to remove at any time */ if (self->next == NULL) { Py_INCREF(self); slp_current_insert(self); } if (self->tempval != NULL) { Py_DECREF(self->tempval); self->tempval = NULL; } Py_INCREF(Py_None); ts->st.tempval = Py_None; if (stackless) { switch (slp_schedule_nr_maybe(ts->st.current, self)) { case -1: return NULL; case 1: return Py_UnwindToken; } } if (slp_schedule_task(ts->st.current, self)) return NULL; if (ts->st.tempval == NULL) return NULL; ret = ts->st.tempval; ts->st.tempval = NULL; return ret; } static TASKLET_RAISE_EXCEPTION_HEAD(wrap_tasklet_raise_exception) { return PyObject_CallMethod((PyObject *)self, "raise_exception", "(OO)", klass, args); } static PyObject * tasklet_raise_exception(PyObject *myself, PyObject *args) { STACKLESS_GETARG(); PyObject *result = NULL; PyObject *klass = PySequence_GetItem(args, 0); if (klass == NULL) return VALUE_ERROR("tasklet.raise_exception(e, v...)", NULL); args = PySequence_GetSlice(args, 1, PySequence_Size(args)); if (!args) goto err_exit; STACKLESS_PROMOTE_ALL(); result = impl_tasklet_raise_exception((PyTaskletObject*)myself, klass, args); STACKLESS_ASSERT(); err_exit: Py_DECREF(klass); Py_XDECREF(args); return result; } static char tasklet_kill__doc__[] = "tasklet.kill -- raise a TaskletExit exception for the tasklet.\n\ Note that this is a regular exception that can be caught.\n\ The tasklet is immediately activated.\n\ If the exception passes the toplevel frame of the tasklet,\n\ the tasklet will silently die."; int PyTasklet_Kill(PyTaskletObject *task) { PyTasklet_HeapType *t = (PyTasklet_HeapType *)task->ob_type; return slp_return_wrapper(t->kill(task)); } static TASKLET_KILL_HEAD(impl_tasklet_kill) { STACKLESS_GETARG(); PyObject *noargs = PyTuple_New(0); PyObject *ret; if (task->topframe == NULL) { ret = RUNTIME_ERROR("You cannot kill an unbound(dead) tasklet", NULL); } else { STACKLESS_PROMOTE_ALL(); ret = impl_tasklet_raise_exception(task, PyExc_TaskletExit, noargs); STACKLESS_ASSERT(); } Py_DECREF(noargs); return ret; } static TASKLET_KILL_HEAD(wrap_tasklet_kill) { return PyObject_CallMethod((PyObject *)task, "kill", NULL); } static PyObject * tasklet_kill(PyObject *self) { return impl_tasklet_kill((PyTaskletObject*)self); } /* attributes which are hiding in small fields */ static PyObject * tasklet_get_blocked(PyTaskletObject *task) { return PyBool_FromLong(slp_get_flags(task)->blocked); } int PyTasklet_GetBlocked(PyTaskletObject *task) { return slp_get_flags(task)->blocked; } static PyObject * tasklet_get_atomic(PyTaskletObject *task) { return PyBool_FromLong(slp_get_flags(task)->atomic); } int PyTasklet_GetAtomic(PyTaskletObject *task) { return slp_get_flags(task)->atomic; } static PyObject * tasklet_get_ignore_nesting(PyTaskletObject *task) { return PyBool_FromLong(slp_get_flags(task)->ignore_nesting); } int PyTasklet_GetIgnoreNesting(PyTaskletObject *task) { return slp_get_flags(task)->ignore_nesting; } static PyObject * tasklet_get_frame(PyTaskletObject *task) { PyObject *ret = (PyObject*) slp_get_frame(task); if (ret == NULL) ret = Py_None; Py_INCREF(ret); return ret; } PyObject * PyTasklet_GetFrame(PyTaskletObject *task) { PyObject * ret = (PyObject *) slp_get_frame(task); Py_XINCREF(ret); return ret; } static PyObject * tasklet_get_block_trap(PyTaskletObject *task) { return PyBool_FromLong(slp_get_flags(task)->block_trap); } int PyTasklet_GetBlockTrap(PyTaskletObject *task) { return slp_get_flags(task)->block_trap; } static int tasklet_set_block_trap(PyTaskletObject *task, PyObject *value) { if (!PyInt_Check(value)) return TYPE_ERROR("block_trap must be set to a bool or integer", -1); slp_get_flags(task)->block_trap = PyInt_AsLong(value) ? 1 : 0; return 0; } void PyTasklet_SetBlockTrap(PyTaskletObject *task, int value) { slp_get_flags(task)->block_trap = value ? 1 : 0; } static PyObject * tasklet_is_main(PyTaskletObject *task) { return PyBool_FromLong(task == PyThreadState_GET()->st.main); } int PyTasklet_IsMain(PyTaskletObject *task) { return task == PyThreadState_GET()->st.main; } static PyObject * tasklet_is_current(PyTaskletObject *task) { return PyBool_FromLong(task == PyThreadState_GET()->st.current); } int PyTasklet_IsCurrent(PyTaskletObject *task) { return task == PyThreadState_GET()->st.current; } static PyObject * tasklet_get_recursion_depth(PyTaskletObject *task) { PyThreadState *ts; assert(task->cstate != NULL); ts = task->cstate->tstate; return PyInt_FromLong(ts->st.current == task ? ts->recursion_depth : task->recursion_depth); } int PyTasklet_GetRecursionDepth(PyTaskletObject *task) { PyThreadState *ts; assert(task->cstate != NULL); ts = task->cstate->tstate; return ts->st.current == task ? ts->recursion_depth : task->recursion_depth; } static PyObject * tasklet_get_nesting_level(PyTaskletObject *task) { PyThreadState *ts; assert(task->cstate != NULL); ts = task->cstate->tstate; return PyInt_FromLong(ts->st.current == task ? ts->st.nesting_level : task->cstate->nesting_level); } int PyTasklet_GetNestingLevel(PyTaskletObject *task) { PyThreadState *ts; assert(task->cstate != NULL); ts = task->cstate->tstate; return ts->st.current == task ? ts->st.nesting_level : task->cstate->nesting_level; } /* attributes which are handy, but easily computed */ static PyObject * tasklet_alive(PyTaskletObject *task) { return PyBool_FromLong(slp_get_frame(task) != NULL); } int PyTasklet_Alive(PyTaskletObject *task) { return slp_get_frame(task) != NULL; } static PyObject * tasklet_paused(PyTaskletObject *task) { return PyBool_FromLong(slp_get_frame(task) != NULL && task->next == NULL); } int PyTasklet_Paused(PyTaskletObject *task) { return slp_get_frame(task) != NULL && task->next == NULL; } static PyObject * tasklet_scheduled(PyTaskletObject *task) { return PyBool_FromLong(task->next != NULL); } int PyTasklet_Scheduled(PyTaskletObject *task) { return task->next != NULL; } static PyObject * tasklet_restorable(PyTaskletObject *task) { PyThreadState *ts; assert(task->cstate != NULL); ts = task->cstate->tstate; return PyBool_FromLong(0 == (ts->st.current == task ? ts->st.nesting_level : task->cstate->nesting_level) ); } int PyTasklet_Restorable(PyTaskletObject *task) { PyThreadState *ts; assert(task->cstate != NULL); ts = task->cstate->tstate; return 0 == (ts->st.current == task ? ts->st.nesting_level : task->cstate->nesting_level); } static PyMemberDef tasklet_members[] = { /* {"channel", T_OBJECT, offsetof(PyTaskletObject, channel), READONLY, "The channel this tasklet is blocked on, or None if it is not blocked."}, */ {"next", T_OBJECT, offsetof(PyTaskletObject, next), READONLY, "the next tasklet in a a circular list of tasklets."}, {"prev", T_OBJECT, offsetof(PyTaskletObject, prev), READONLY, "the previous tasklet in a circular list of tasklets"}, {"cstate", T_OBJECT, offsetof(PyTaskletObject, cstate), READONLY, "the C stack object associated with the tasklet.\n\ Every tasklet has a cstate, even if it is a trivial one.\n\ Please see the cstate doc and the stackless documentation."}, {"tempval", T_OBJECT, offsetof(PyTaskletObject, tempval), 0}, /* blocked, slicing_lock, atomic and such are treated by tp_getset */ {0} }; static PyGetSetDef tasklet_getsetlist[] = { {"blocked", (getter)tasklet_get_blocked, NULL, "Nonzero if waiting on a channel (1: send, -1: receive).\n" "Part of the flags word."}, {"atomic", (getter)tasklet_get_atomic, NULL, "atomic inhibits scheduling of this tasklet. See set_atomic()\n" "Part of the flags word."}, {"ignore_nesting", (getter)tasklet_get_ignore_nesting, NULL, "unless ignore_nesting is set, any nesting level > 0 inhibits auto-scheduling.\n" "of this tasklet. See set_ignore_nesting()\n" "Part of the flags word."}, {"frame", (getter)tasklet_get_frame, NULL, "the current frame of this tasklet. For the running tasklet,\n" "this is redirected to tstate.frame."}, {"block_trap", (getter)tasklet_get_block_trap, (setter)tasklet_set_block_trap, "An individual lock against blocking on a channel.\n" "This is used as a debugging aid to find out undesired blocking.\n" "Instead of trying to block, an exception is raised."}, {"is_main", (getter)tasklet_is_main, NULL, "There always exists exactly one tasklet per thread which acts as main.\n" "It receives all uncaught exceptions and can act as a watchdog.\n" "This attribute is computed."}, {"is_current", (getter)tasklet_is_current, NULL, "There always exists exactly one tasklet per thread which is currently running.\n" "This attribute is computed."}, {"paused", (getter)tasklet_paused, NULL, "A tasklet is said to be paused if it is neither in the runnables list\n" "nor blocked, but alive. This state is entered after a t.remove()\n" "or by the main tasklet, when it is acting as a watchdog.\n" "This attribute is computed."}, {"scheduled", (getter)tasklet_scheduled, NULL, "A tasklet is said to be scheduled if it is either in the runnables list\n" "or waiting in a channel.\n" "This attribute is computed."}, {"recursion_depth", (getter)tasklet_get_recursion_depth, NULL, "The system recursion_depth is replicated for every tasklet.\n" "They all start running with a recursion_depth of zero."}, {"nesting_level", (getter)tasklet_get_nesting_level, NULL, "The interpreter nesting level is monitored by every tasklet.\n" "They all start running with a nesting level of zero."}, {"restorable", (getter)tasklet_restorable, NULL, "True, if the tasklet can be completely restored by pickling/unpickling.\n" "All tasklets can be pickled for debugging/inspection purposes, but an \n" "unpickled tasklet might have lost runtime information (C stack)."}, {"alive", (getter)tasklet_alive, NULL, "A tasklet is alive if it has an associated frame.\n" "This attribute is computed."}, {0}, }; static PyMethodDef tasklet_methods[] = { {"insert", (PyCFunction)tasklet_insert, METH_NOARGS, tasklet_insert__doc__}, {"run", (PyCFunction)tasklet_run, METH_NOARGS | METH_STACKLESS, tasklet_run__doc__}, {"remove", (PyCFunction)tasklet_remove, METH_NOARGS, tasklet_remove__doc__}, {"set_atomic", (PyCFunction)tasklet_set_atomic, METH_O, tasklet_set_atomic__doc__}, {"set_ignore_nesting", (PyCFunction)tasklet_set_ignore_nesting, METH_O, tasklet_set_ignore_nesting__doc__}, {"become", (PyCFunction)tasklet_become, METH_KEYWORDS, tasklet_become__doc__}, {"capture", (PyCFunction)tasklet_capture, METH_KEYWORDS, tasklet_capture__doc__}, {"raise_exception", (PyCFunction)tasklet_raise_exception, METH_KEYWORDS | METH_STACKLESS, tasklet_raise_exception__doc__}, {"kill", (PyCFunction)tasklet_kill, METH_NOARGS | METH_STACKLESS, tasklet_kill__doc__}, {"bind", (PyCFunction)tasklet_bind, METH_O, tasklet_bind__doc__}, {"setup", (PyCFunction)tasklet_setup, METH_VARARGS | METH_KEYWORDS, tasklet_setup__doc__}, {"__reduce__", (PyCFunction)tasklet_reduce, METH_NOARGS, tasklet_reduce__doc__}, {"__setstate__", (PyCFunction)tasklet_setstate, METH_O, tasklet_setstate__doc__}, {NULL, NULL} /* sentinel */ }; static PyCMethodDef tasklet_cmethods[] = { CMETHOD_PUBLIC_ENTRY(PyTasklet_HeapType, tasklet, insert), CMETHOD_PUBLIC_ENTRY(PyTasklet_HeapType, tasklet, run), CMETHOD_PUBLIC_ENTRY(PyTasklet_HeapType, tasklet, remove), CMETHOD_PUBLIC_ENTRY(PyTasklet_HeapType, tasklet, set_atomic), CMETHOD_PUBLIC_ENTRY(PyTasklet_HeapType, tasklet, set_ignore_nesting), CMETHOD_PUBLIC_ENTRY(PyTasklet_HeapType, tasklet, become), CMETHOD_PUBLIC_ENTRY(PyTasklet_HeapType, tasklet, capture), CMETHOD_PUBLIC_ENTRY(PyTasklet_HeapType, tasklet, raise_exception), CMETHOD_PUBLIC_ENTRY(PyTasklet_HeapType, tasklet, kill), {NULL} /* sentinel */ }; static char tasklet_doc[] = "A tasklet object represents a tiny task in a Python thread.\n\ At program start, there is always one running main tasklet.\n\ New tasklets can be created with methods from the stackless\n\ module.\n\ "; PyTypeObject _PyTasklet_Type = { PyObject_HEAD_INIT(&PyType_Type) 0, "tasklet", sizeof(PyTaskletObject), 0, (destructor)tasklet_dealloc, /* 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 */ tasklet_setup, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ PyObject_GenericSetAttr, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ tasklet_doc, /* tp_doc */ (traverseproc)tasklet_traverse, /* tp_traverse */ (inquiry) tasklet_clear, /* tp_clear */ 0, /* tp_richcompare */ offsetof(PyTaskletObject, tsk_weakreflist), /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ tasklet_methods, /* tp_methods */ tasklet_members, /* tp_members */ tasklet_getsetlist, /* 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 */ tasklet_new, /* tp_new */ _PyObject_GC_Del, /* tp_free */ }; PyTypeObject *PyTasklet_TypePtr = NULL; int init_tasklettype(void) { PyTypeObject *t = &_PyTasklet_Type; if ( (t = PyFlexType_Build("stackless", "tasklet", t->tp_doc, "", t, sizeof(PyTasklet_HeapType), tasklet_cmethods) ) == NULL) return -1; PyTasklet_TypePtr = t; return 0; } #endif