diff --git a/devsupApp/src/dbfield.c b/devsupApp/src/dbfield.c index 30568a7..7ba2883 100644 --- a/devsupApp/src/dbfield.c +++ b/devsupApp/src/dbfield.c @@ -115,10 +115,21 @@ static PyObject* pyField_putval(pyField *self, PyObject* args) OP(DOUBLE,epicsFloat64,PyFloat_AsDouble); #undef OP case DBF_STRING: { - char *fld = PyString_AsString(val); - strncpy(self->addr.pfield, fld, MAX_STRING_SIZE); - fld = self->addr.pfield; - fld[MAX_STRING_SIZE-1]='\0'; + const char *fld; + char *dest=self->addr.pfield; +#if PY_MAJOR_VERSION >= 3 + PyObject *data = PyUnicode_AsEncodedString(val, "ascii", "Encoding error:"); + if(!data) + return NULL; + fld = PyUnicode_AS_DATA(data); +#else + fld = PyString_AsString(val); +#endif + strncpy(dest, fld, MAX_STRING_SIZE); + dest[MAX_STRING_SIZE-1]='\0'; +#if PY_MAJOR_VERSION >= 3 + Py_DECREF(data); +#endif break; } default: @@ -166,6 +177,7 @@ static PyMethodDef pyField_methods[] = { {NULL, NULL, 0, NULL} }; +#if PY_MAJOR_VERSION < 3 static Py_ssize_t pyField_buf_getcount(pyField *self, Py_ssize_t *totallen) { if(totallen) @@ -195,6 +207,7 @@ static Py_ssize_t pyField_buf_getcharbuf(pyField *self, Py_ssize_t bufid, char * *data = self->addr.pfield; return self->addr.field_size * self->addr.no_elements; } +#endif static int pyField_buf_getbufferproc(pyField *self, Py_buffer *buf, int flags) { @@ -205,18 +218,24 @@ static int pyField_buf_getbufferproc(pyField *self, Py_buffer *buf, int flags) } static PyBufferProcs pyField_buf_methods = { +#if PY_MAJOR_VERSION < 3 (readbufferproc)pyField_buf_getbuf, (writebufferproc)pyField_buf_getbuf, (segcountproc)pyField_buf_getcount, (charbufferproc)pyField_buf_getcharbuf, +#endif (getbufferproc)pyField_buf_getbufferproc, (releasebufferproc)NULL, }; static PyTypeObject pyField_type = { +#if PY_MAJOR_VERSION >= 3 + PyVarObject_HEAD_INIT(NULL, 0) +#else PyObject_HEAD_INIT(NULL) 0, +#endif "_dbapi._Field", sizeof(pyField), }; @@ -226,7 +245,9 @@ int pyField_prepare(void) size_t i; pyField_type.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE; +#if PY_MAJOR_VERSION < 3 pyField_type.tp_flags |= Py_TPFLAGS_HAVE_GETCHARBUFFER|Py_TPFLAGS_HAVE_NEWBUFFER; +#endif pyField_type.tp_methods = pyField_methods; pyField_type.tp_as_buffer = &pyField_buf_methods; pyField_type.tp_init = (initproc)pyField_Init; diff --git a/devsupApp/src/dbrec.c b/devsupApp/src/dbrec.c index feb7bac..17e8ad3 100644 --- a/devsupApp/src/dbrec.c +++ b/devsupApp/src/dbrec.c @@ -24,7 +24,7 @@ typedef struct { static void pyRecord_dealloc(pyRecord *self) { dbFinishEntry(&self->entry); - self->ob_type->tp_free((PyObject*)self); + Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* pyRecord_new(PyTypeObject *type, PyObject *args, PyObject *kws) @@ -57,6 +57,7 @@ static PyObject* pyRecord_ispyrec(pyRecord *self) return PyBool_FromLong(self->ispyrec); } +#if PY_MAJOR_VERSION < 3 static int pyRecord_compare(pyRecord *A, pyRecord *B) { dbCommon *a=A->entry.precnode->precord, @@ -66,6 +67,7 @@ static int pyRecord_compare(pyRecord *A, pyRecord *B) return 0; return strcmp(a->name, b->name); } +#endif static PyObject* pyRecord_name(pyRecord *self) { @@ -259,8 +261,12 @@ static PyMethodDef pyRecord_methods[] = { }; static PyTypeObject pyRecord_type = { +#if PY_MAJOR_VERSION >= 3 + PyVarObject_HEAD_INIT(NULL, 0) +#else PyObject_HEAD_INIT(NULL) 0, +#endif "_dbapi._Record", sizeof(pyRecord), }; @@ -274,7 +280,9 @@ int pyRecord_prepare(void) pyRecord_type.tp_new = (newfunc)pyRecord_new; pyRecord_type.tp_dealloc = (destructor)pyRecord_dealloc; pyRecord_type.tp_init = (initproc)pyRecord_Init; +#if PY_MAJOR_VERSION < 3 pyRecord_type.tp_compare = (cmpfunc)pyRecord_compare; +#endif if(PyType_Ready(&pyRecord_type)<0) return -1; diff --git a/devsupApp/src/pydevsup.h b/devsupApp/src/pydevsup.h index cdfbda2..b106e28 100644 --- a/devsupApp/src/pydevsup.h +++ b/devsupApp/src/pydevsup.h @@ -1,6 +1,16 @@ #ifndef PYDEVSUP_H #define PYDEVSUP_H +#if PY_MAJOR_VERSION >= 3 +#define PyInt_FromLong PyLong_FromLong +#define PyInt_AsLong PyLong_AsLong +#define PyString_FromString PyUnicode_FromString +#define MODINIT_RET(VAL) return (VAL) + +#else +#define MODINIT_RET(VAL) return + +#endif void pyDBD_cleanup(void); int pyField_prepare(void); diff --git a/devsupApp/src/setup.c b/devsupApp/src/setup.c index 5e7b261..165bb77 100644 --- a/devsupApp/src/setup.c +++ b/devsupApp/src/setup.c @@ -64,43 +64,6 @@ static pystate statenames[] = { }; #undef INITST -static void pyhook(initHookState state); - -static void cleanupPy(void *junk) -{ - PyThreadState *state = PyGILState_GetThisThreadState(); - - PyEval_RestoreThread(state); - - /* special "fake" hook for shutdown */ - pyhook((initHookState)9999); - - pyDBD_cleanup(); - - pyField_cleanup(); - - /* release extra reference for hooktable */ - Py_DECREF(hooktable); - hooktable = NULL; - - Py_Finalize(); -} - -/* Initialize the interpreter environment - */ -static epicsThreadOnceId setupPyOnceId = EPICS_THREAD_ONCE_INIT; -static void setupPyOnce(void *junk) -{ - PyThreadState *state; - - Py_Initialize(); - PyEval_InitThreads(); - - state = PyEval_SaveThread(); - - epicsAtExit(&cleanupPy, NULL); -} - void evalPy(const char* code) { PyGILState_STATE state; @@ -193,8 +156,18 @@ static PyMethodDef devsup_methods[] = { {NULL, NULL, 0, NULL} }; +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef dbapimodule = { + PyModuleDef_HEAD_INIT, + "_dbapi", + NULL, + -1, + devsup_methods +}; +#endif + /* initialize "magic" builtin module */ -static void init_dbapi(void) +PyMODINIT_FUNC init_dbapi(void) { PyObject *mod, *hookdict, *pysuptable; pystate *st; @@ -203,23 +176,27 @@ static void init_dbapi(void) hooktable = PyDict_New(); if(!hooktable) - return; + MODINIT_RET(NULL); if(pyField_prepare()) - return; + MODINIT_RET(NULL); if(pyRecord_prepare()) - return; + MODINIT_RET(NULL); +#if PY_MAJOR_VERSION >= 3 + mod = PyModule_Create(&dbapimodule); +#else mod = Py_InitModule("_dbapi", devsup_methods); +#endif pysuptable = PySet_New(NULL); if(!pysuptable) - return; + MODINIT_RET(NULL); PyModule_AddObject(mod, "_supports", pysuptable); hookdict = PyDict_New(); if(!hookdict) - return; + MODINIT_RET(NULL); PyModule_AddObject(mod, "_hooks", hookdict); for(st = statenames; st->name; st++) { @@ -231,6 +208,43 @@ static void init_dbapi(void) pyField_setup(mod); pyRecord_setup(mod); + MODINIT_RET(mod); +} + +static void cleanupPy(void *junk) +{ + PyThreadState *state = PyGILState_GetThisThreadState(); + + PyEval_RestoreThread(state); + + /* special "fake" hook for shutdown */ + pyhook((initHookState)9999); + + pyDBD_cleanup(); + + pyField_cleanup(); + + /* release extra reference for hooktable */ + Py_DECREF(hooktable); + hooktable = NULL; + + Py_Finalize(); +} + +/* Initialize the interpreter environment + */ +static void setupPyInit(void) +{ + PyThreadState *state; + + PyImport_AppendInittab("_dbapi", init_dbapi); + + Py_Initialize(); + PyEval_InitThreads(); + + state = PyEval_SaveThread(); + + epicsAtExit(&cleanupPy, NULL); } #include @@ -251,7 +265,7 @@ static void pySetupReg(void) { PyGILState_STATE state; - epicsThreadOnce(&setupPyOnceId, &setupPyOnce, NULL); + setupPyInit(); iocshRegister(&codeDef, &codeRun); iocshRegister(&fileDef, &fileRun); initHookRegister(&pyhook); diff --git a/python/devsup/__init__.py b/python/devsup/__init__.py index d5ffaa0..a0e16c2 100644 --- a/python/devsup/__init__.py +++ b/python/devsup/__init__.py @@ -1,7 +1,7 @@ try: import _dbapi except ImportError: - import _nullapi as _dbapi + import devsup._nullapi as _dbapi __all__ = ['verinfo'] diff --git a/python/devsup/db.py b/python/devsup/db.py index d4a1231..d5efeb9 100644 --- a/python/devsup/db.py +++ b/python/devsup/db.py @@ -1,7 +1,7 @@ import threading, sys, traceback -from util import Worker +from devsup.util import Worker try: import _dbapi @@ -60,14 +60,11 @@ class IOScanListBlock(object): self.lock = threading.Lock() self.scan1 = IOScanListBlock() def run(self): - try: - while self.shouldRun(): - time.sleep(1) - with self.lock: - self.scan1.interrupt() - finally: - self.finish() - + while self.shouldRun(): + time.sleep(1) + with self.lock: + self.scan1.interrupt() + class MySup(object): def __init__(self, driver): self.driver = driver @@ -140,9 +137,9 @@ class IOScanListThread(IOScanListBlock): with cls._worker_lock: if cls._worker is not None: return cls._worker - import hooks + import devsup.hooks T = Worker(max=cls.queuelength) - hooks.addHook('AtIocExit', T.join) + devsup.hooks.addHook('AtIocExit', T.join) T.start() cls._worker = T return T @@ -162,12 +159,9 @@ class IOScanListThread(IOScanListBlock): super(MyDriver,self).__init__() self.scan1 = IOScanListThread() def run(self): - try: - while self.shouldRun(): - time.sleep(1) - self.scan1.interrupt() - finally: - self.finish() + while self.shouldRun(): + time.sleep(1) + self.scan1.interrupt() class MySup(object): def __init__(self, driver): @@ -234,7 +228,7 @@ class Record(_dbapi._Record): if F is _no_such_field: raise ValueError() return F - except KeyError, e: + except KeyError as e: try: fld = Field("%s.%s"%(self.name(), name)) except ValueError: diff --git a/python/devsup/util.py b/python/devsup/util.py index 4982d54..0d85424 100644 --- a/python/devsup/util.py +++ b/python/devsup/util.py @@ -3,73 +3,51 @@ from __future__ import print_function import threading, traceback class StoppableThread(threading.Thread): - """A thread which can be required to stop. + """A thread which can be requested to stop. The thread run() method should periodically call the shouldRun() - method if this yields False, the finish() should be called before returning. - This is really only feasible by sub-classing. + method and return if this yields False. >>> class TestThread(StoppableThread): ... def __init__(self): ... super(TestThread,self).__init__() ... self.E=threading.Event() ... def run(self): - ... try: - ... import time - ... self.E.set() - ... while self.shouldRun(): - ... time.sleep(0.01) - ... finally: - ... self.finish() + ... import time + ... self.cur = threading.current_thread() + ... self.E.set() + ... while self.shouldRun(): + ... time.sleep(0.01) >>> T = TestThread() >>> T.start() >>> T.E.wait() True + >>> T.cur is T + True >>> T.join() >>> T.is_alive() False """ def __init__(self, max=0): super(StoppableThread, self).__init__() - self._run, self._stop = False, False - self._sevt, self._lock = threading.Event(), threading.Lock() - - def running(self): - with self._lock: - return self._run and not self._stop + self.__stop = True + self.__lock = threading.Lock() def start(self): - with self._lock: - assert not self._stop - self._run = True - self._sevt.clear() + with self.__lock: + self.__stop = False super(StoppableThread, self).start() def join(self): - with self._lock: - if not self._run: - return - self._stop = True - - self._sevt.wait() - + with self.__lock: + self.__stop = True + super(StoppableThread, self).join() def shouldRun(self): - with self._lock: - return not self._stop - - def finish(self): - with self._lock: - self._run = self._stop = False - self._sevt.set() - - def run(self, *args, **kws): - try: - super(StoppableThread, self).start(*args, **kws) - finally: - self.finish() + with self.__lock: + return not self.__stop class Worker(threading.Thread): """A threaded work queue. @@ -89,89 +67,77 @@ class Worker(threading.Thread): """ def __init__(self, max=0): super(Worker, self).__init__() - self._run, self._stop = False, None - self._lock = threading.Lock() - self._update = threading.Condition(self._lock) + self.__stop = None + self.__lock = threading.Lock() + self.__update = threading.Condition(self.__lock) self.maxQ, self._Q = max, [] - def running(self): - with self._lock: - return self._run and not self._stop - - def start(self): - with self._lock: - if self._run or self._stop: - return - super(Worker, self).start() - self._run = True def join(self, flush=True): - self._update.acquire() + self.__update.acquire() try: - if self._stop: + if self.__stop is not None: raise RuntimeError("Someone else is already trying to stop me") - self._stop = threading.Event() - self._update.notify() + self.__stop = threading.Event() + self.__update.notify() - self._update.release() + self.__update.release() try: - self._stop.wait() + self.__stop.wait() finally: - self._update.acquire() + self.__update.acquire() - self._stop = None - assert not self._run + self.__stop = None if flush: self._Q = [] finally: - self._update.release() + self.__update.release() def __len__(self): - with self._lock: + with self.__lock: return len(self._Q) def add(self, func, args=(), kws={}): - with self._lock: - if not self._run or self._stop: + with self.__lock: + if self.__stop is not None: return elif self.maxQ>0 and len(self._Q)>=self.maxQ: raise RuntimeError('Worker queue full') self._Q.append((func,args,kws)) - self._update.notify() + self.__update.notify() def run(self): - self._update.acquire() + self.__update.acquire() try: - assert self._run while True: - while self._stop is None and len(self._Q)==0: - self._update.wait() + while self.__stop is None and len(self._Q)==0: + self.__update.wait() - if self._stop is not None: + if self.__stop is not None: break F, A, K = self._Q.pop(0) - self._update.release() + self.__update.release() try: F(*A,**K) except: print('Error running',F,A,K) traceback.print_exc() finally: - self._update.acquire() - - self._run = False - self._stop.set() + self.__update.acquire() + self.__stop.set() + except: + traceback.print_exc() finally: - self._update.release() + self.__update.release() if __name__=='__main__': import doctest diff --git a/testApp/test1.py b/testApp/test1.py index 6f707ac..c8f0da3 100644 --- a/testApp/test1.py +++ b/testApp/test1.py @@ -1,13 +1,14 @@ +from __future__ import print_function class MySup(object): def __init__(self, rec): - print rec, rec.field('VAL').fieldinfo() - print 'VAL', rec.VAL + print(rec, rec.field('VAL').fieldinfo()) + print('VAL', rec.VAL) def process(self, rec, reason): rec.VAL = 1+rec.VAL def detach(self, rec): - print 'test1 detach',rec.name() + print('test1 detach',rec.name()) def build(rec, args): - print 'test1 build for',rec.name() + print('test1 build for',rec.name()) return MySup(rec) diff --git a/testApp/test2.py b/testApp/test2.py index e0ea453..53ac21b 100644 --- a/testApp/test2.py +++ b/testApp/test2.py @@ -1,3 +1,4 @@ +from __future__ import print_function import threading, time from devsup.hooks import addHook @@ -7,7 +8,7 @@ from devsup.util import StoppableThread insts = {} def done(obj): - print obj,'Expires' + print(obj,'Expires') class Driver(StoppableThread): def __init__(self, name): @@ -19,19 +20,17 @@ class Driver(StoppableThread): addHook('AtIocExit', self.join) def run(self): - try: - while self.shouldRun(): - time.sleep(1.0) - - val = self.value - self.value += 1 - self.scan.interrupt(reason=val) - finally: - self.finish() + print('Starting driver',self) + while self.shouldRun(): + time.sleep(1.0) + + val = self.value + self.value += 1 + self.scan.interrupt(reason=val) def addDrv(name): - print 'Create driver',name insts[name] = Driver(name) + print('Created driver',name,insts[name]) class Device(object): def __init__(self, rec, args): @@ -39,7 +38,7 @@ class Device(object): self.allowScan = self.driver.scan.add def detach(self, rec): - print 'detach',rec + print('detach',rec) def process(self, rec, data): if data is not None: