/** Thread-based, thread-safe "alarms". Alarms are created with alarm.alarm(sleepfn, sleepargs, callbackfn [, callbackargs]). Alarms are in one of three states, as retreived by the state() method. Here are the states and the actions performed by the alarm object: - enter "waiting" state - call sleepfn(*sleepargs) # typically, sleepfn = time.sleep - enter "running" state - call callbackfn(*callbackargs) - enter "waiting" state again and start over The return value of callbackfn() must be either None, in which case the alarm stops ("stopped" state), or a 3- or 4-tuple as the arguments of alarm.alarm(), in which case the alarm starts again. The subtlety of all this is that alarms have a stop() method that can be asynchronously called from any thread, and cause the alarm to switch from "waiting" to "stopped" state. - stop(1) blocks while the alarm is "running". When it returns, the alarm is guaranteed to be "stopped". - stop(0) does not block, and only works if the alarm was "waiting" at that time. In both cases, if the alarm was "waiting", then the actual call to sleepfn(*sleepargs) is NOT interrupted. The alarm object will simply not enter the "running" state and be DECREFed away when this call finally returns. The whole point is that I couldn't obtain this behavior in a thread-safe way in pure Python because other threads must not be allowed to run between the call to sleepfn() and the call to callbackfn(). **/ /* Define this to compile as a stand-alone "alarm" module */ #define STANDALONE #ifdef STANDALONE # include #else # include "alarm.h" #endif #include typedef struct { PyObject_HEAD PyInterpreterState* interp; PyThread_type_lock lock; PyObject* args; enum { st_waiting, st_running, st_stopped } state; } PyAlarmObject; staticforward PyTypeObject PyAlarm_Type; static void t_bootstrap(void* rawself) { PyAlarmObject* self = (PyAlarmObject*) rawself; PyThreadState *tstate; PyObject* args = NULL; tstate = PyThreadState_New(self->interp); PyEval_AcquireThread(tstate); while (1) { PyObject *res, *sleepfn, *sleeparg, *callback, *cbarg = NULL; Py_XDECREF(args); args = self->args; Py_XINCREF(args); if (args == NULL || args == Py_None) break; if (!PyArg_ParseTuple(args, "OOO|O", &sleepfn, &sleeparg, &callback, &cbarg)) break; res = PyObject_CallObject(sleepfn, sleeparg); if (res == NULL) break; Py_DECREF(res); res = NULL; if (self->args == NULL) break; PyThread_acquire_lock(self->lock, WAIT_LOCK); if (self->args != NULL) { self->state = st_running; res = PyObject_CallObject(callback, cbarg); self->state = st_waiting; } PyThread_release_lock(self->lock); Py_DECREF(args); args = self->args; self->args = res; } Py_XDECREF(args); self->state = st_stopped; if (PyErr_Occurred()) { if (PyErr_ExceptionMatches(PyExc_SystemExit)) PyErr_Clear(); else { PySys_WriteStderr("Unhandled exception in alarm:\n"); PyErr_PrintEx(0); } } Py_DECREF(self); PyThreadState_Clear(tstate); PyThreadState_DeleteCurrent(); PyThread_exit_thread(); } /***************************************************************/ static void alarm_dealloc(PyAlarmObject* self) { Py_XDECREF(self->args); if (self->lock != NULL) PyThread_free_lock(self->lock); PyObject_Del((PyObject*) self); } static int alarm_traverse(PyAlarmObject* self, visitproc visit, void* arg) { if (self->args != NULL) return visit(self->args, arg); else return 0; } static int alarm_clear(PyAlarmObject* self) { PyObject* nargs = self->args; self->args = NULL; Py_XDECREF(nargs); return 0; } static PyObject* alarmstate(PyAlarmObject* self, PyObject* args) { char* s; if (!PyArg_ParseTuple(args, "")) return NULL; switch (self->state) { case st_waiting: s = "waiting"; break; case st_running: s = "running"; break; default: s = "stopped"; break; } return PyString_FromString(s); } static PyObject* alarmstop(PyAlarmObject* self, PyObject* args) { int wait; if (!PyArg_ParseTuple(args, "i", &wait)) return NULL; alarm_clear(self); if (wait && self->state != st_stopped) { Py_BEGIN_ALLOW_THREADS PyThread_acquire_lock(self->lock, WAIT_LOCK); PyThread_release_lock(self->lock); Py_END_ALLOW_THREADS } Py_INCREF(Py_None); return Py_None; } #ifdef STANDALONE static #else DEFINEFN #endif PyObject* new_alarm(PyObject* dummy, PyObject* args) { PyAlarmObject* self; PyObject* sleepfn; PyObject* sleeparg; PyObject* callback; long ident; self = PyObject_New(PyAlarmObject, &PyAlarm_Type); if (self == NULL) return NULL; PyEval_InitThreads(); /* Start the interpreter's thread-awareness */ self->interp = PyThreadState_Get()->interp; self->lock = PyThread_allocate_lock(); Py_INCREF(args); self->args = args; self->state = st_waiting; if (self->lock == NULL) goto error; Py_INCREF(self); /* t_bootstrap consumes a ref */ ident = PyThread_start_new_thread(t_bootstrap, (void*) self); if (ident == -1) { Py_DECREF(self); PyErr_SetString(PyExc_RuntimeError, "can't start new thread"); goto error; } return (PyObject*) self; error: Py_DECREF(self); return NULL; } /***************************************************************/ static PyMethodDef alarm_methods[] = { {"state", (PyCFunction)alarmstate, METH_VARARGS}, {"stop", (PyCFunction)alarmstop, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; static PyObject* alarm_getattr(PyObject* self, char* name) { return Py_FindMethod(alarm_methods, self, name); } statichere PyTypeObject PyAlarm_Type = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "alarm", /*tp_name*/ sizeof(PyAlarmObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)alarm_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ alarm_getattr, /*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 | Py_TPFLAGS_HAVE_GC,/* tp_flags */ 0, /* tp_doc */ (traverseproc)alarm_traverse, /* tp_traverse */ (inquiry)alarm_clear, /* tp_clear */ }; #ifdef STANDALONE static PyMethodDef AlarmMethods[] = { {"alarm", new_alarm, METH_VARARGS}, {NULL, NULL} /* Sentinel */ }; void initalarm() { PyObject* m; PyAlarm_Type.ob_type = &PyType_Type; m = Py_InitModule("alarm", AlarmMethods); if (m == NULL) return; Py_INCREF(&PyAlarm_Type); if (PyModule_AddObject(m, "AlarmType", (PyObject*) &PyAlarm_Type)) return; } #else INITIALIZATIONFN void initalarm() { PyAlarm_Type.ob_type = &PyType_Type; Py_INCREF(&PyAlarm_Type); PyModule_AddObject(CPsycoModule, "AlarmType", (PyObject*) &PyAlarm_Type); } #endif