From fe76c97bc5dac1e845a76d6efc7359f40dbcf631 Mon Sep 17 00:00:00 2001 From: Dylan Maxwell Date: Thu, 29 Nov 2018 12:01:51 -0500 Subject: [PATCH 1/8] Add support for EPICS v3.15.6 --- debian/control | 4 ++-- .../0003-simpler-debian-specific-load_ca.py.patch | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/debian/control b/debian/control index a0bca17..3e493d6 100644 --- a/debian/control +++ b/debian/control @@ -7,7 +7,7 @@ Build-Depends: debhelper (>= 7), python-all-dev, python-all-dbg, dh-python, python-numpy, python-sphinx, - libepics3.14.11 | libepics3.14.12 | libepics3.14.12.3 | libepics3.15.3 | libepics3.16.1, + libepics3.14.11 | libepics3.14.12 | libepics3.14.12.3 | libepics3.15.3 | libepics3.15.6 | libepics3.16.1, XS-Python-Version: >= 2.7 Standards-Version: 3.8.0 Homepage: http://controls.diamond.ac.uk/downloads/python/cothread/ @@ -16,7 +16,7 @@ Package: python-cothread Architecture: any Depends: ${shlibs:Depends}, ${python:Depends}, python-setuptools, python-numpy, - libepics3.14.11 | libepics3.14.12 | libepics3.14.12.3 | libepics3.15.3 | libepics3.16.1, + libepics3.14.11 | libepics3.14.12 | libepics3.14.12.3 | libepics3.15.3 | libepics3.15.6 | libepics3.16.1, Conflicts: python-cothread-doc (<< 1.15) XB-Python-Version: ${python:Versions} Suggests: python-cothread-doc diff --git a/debian/patches/0003-simpler-debian-specific-load_ca.py.patch b/debian/patches/0003-simpler-debian-specific-load_ca.py.patch index 73a81f7..2f71b6b 100644 --- a/debian/patches/0003-simpler-debian-specific-load_ca.py.patch +++ b/debian/patches/0003-simpler-debian-specific-load_ca.py.patch @@ -4,8 +4,8 @@ Subject: simpler debian specific load_ca.py --- cothread/cadef.py | 3 +- - cothread/load_ca.py | 109 ++++++++++++++++------------------------------------ - 2 files changed, 34 insertions(+), 78 deletions(-) + cothread/load_ca.py | 110 ++++++++++++++++------------------------------------ + 2 files changed, 35 insertions(+), 78 deletions(-) diff --git a/cothread/cadef.py b/cothread/cadef.py index 6651b00..106ce84 100644 @@ -29,10 +29,10 @@ index 6651b00..106ce84 100644 # write = ca_write_access(channel_id) # diff --git a/cothread/load_ca.py b/cothread/load_ca.py -index 1184ad7..d5f831b 100644 +index 1184ad7..e5cbc51 100644 --- a/cothread/load_ca.py +++ b/cothread/load_ca.py -@@ -32,94 +32,51 @@ +@@ -32,94 +32,52 @@ # This file can also be run as a standalone script to discover the path to # libca. @@ -81,6 +81,7 @@ index 1184ad7..d5f831b 100644 + 'libca.so.3.14.12', + 'libca.so.3.14.12.3', + 'libca.so.3.15.3', ++ 'libca.so.3.15.6', + 'libca.so.3.16.1', +] +# Allow user to provide additional names (eg "libca.so.3.15:libca.so.3.15.1") From 99fd83e6e70331bb131cf189fae21aff43da7794 Mon Sep 17 00:00:00 2001 From: Dylan Maxwell Date: Thu, 29 Nov 2018 12:06:34 -0500 Subject: [PATCH 2/8] Update changelog for v2.12-5 --- debian/changelog | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index b658326..9a93ebb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,12 @@ cothread (2.12-5) UNRELEASED; urgency=medium + [ Michael Davidsaver ] * Added libepics dependency option for base 3.15.3 and 3.16.1 - -- Michael Davidsaver Fri, 27 Oct 2017 17:38:29 -0500 + [ Dylan Maxwell ] + * Add support for EPICS v3.15.6 + + -- Dylan Maxwell Thu, 29 Nov 2018 12:02:32 -0500 cothread (2.12-4) unstable; urgency=low From 74f644941fef4c08423c76385ec5bc68a1012cb3 Mon Sep 17 00:00:00 2001 From: Dylan Maxwell Date: Fri, 30 Nov 2018 10:13:21 -0500 Subject: [PATCH 3/8] New upstream version 2.15 --- .travis.yml | 49 ++ MANIFEST.in | 3 + Makefile | 14 + README.rst | 50 ++ context/_coroutine.c | 49 ++ context/switch-arm.c | 14 + context/tests/coco.py | 2 +- context/tests/leak.py | 2 +- cothread/__init__.py | 1 + cothread/cadef.py | 6 +- cothread/catools.py | 40 +- cothread/coselect.py | 3 +- cothread/coserver.py | 36 +- cothread/cosocket.py | 99 ++- cothread/cothread.py | 143 +++- cothread/dbr.py | 80 +- cothread/input_hook.py | 8 +- cothread/load_ca.py | 38 +- cothread/pv.py | 55 +- cothread/py23.py | 56 ++ cothread/qt.py | 33 + cothread/tools/pvtree.py | 4 +- cothread/version.py | 1 + docrequirements.txt | 1 + docs/catools.rst | 71 +- docs/conf.py | 9 +- docs/cothread.rst | 90 ++- docs/training/.gitignore | 1 + docs/training/Makefile | 18 + docs/training/cothread.rst | 684 ++++++++++++++++++ docs/training/docutils.conf | 8 + docs/training/ioc.db | 23 + docs/training/styles/blank.gif | Bin 0 -> 49 bytes docs/training/styles/dlsfooterpad.png | Bin 0 -> 165745 bytes docs/training/styles/framing.css | 24 + docs/training/styles/html4css1.css | 311 ++++++++ docs/training/styles/iepngfix.htc | 42 ++ docs/training/styles/opera.css | 8 + docs/training/styles/outline.css | 16 + docs/training/styles/pretty.css | 126 ++++ docs/training/styles/print.css | 24 + docs/training/styles/pygments.css | 62 ++ docs/training/styles/s5-core.css | 11 + docs/training/styles/slides.css | 10 + docs/training/styles/slides.js | 544 ++++++++++++++ docs/training/testioc | 4 + examples/caget.py | 2 +- examples/camonitor.py | 2 +- examples/caput.py | 2 +- examples/qt_monitor.py | 2 +- examples/require.py | 2 + examples/scope_epics.py | 6 +- examples/simple.py | 2 +- {tests => old_tests}/caget_failure.py | 4 +- {tests => old_tests}/caget_structure.py | 4 +- {tests => old_tests}/callback.py | 4 +- old_tests/callback_result.py | 25 + {tests => old_tests}/camonitor.big.py | 4 +- {tests => old_tests}/camonitor_test.py | 4 +- {tests => old_tests}/control-c.py | 4 +- {tests => old_tests}/interactive.py | 4 +- {tests => old_tests}/late_qt.py | 2 +- {tests => old_tests}/leaktest.py | 4 +- {tests => old_tests}/load.py | 4 +- {tests => old_tests}/plottest.py | 4 +- {tests => old_tests}/recursion-test.py | 4 +- {tests => old_tests}/require.py | 6 + {tests => old_tests}/simple-modal.py | 4 +- tests/test_ih.py => old_tests/test-ih.py | 2 - {tests => old_tests}/test-modal.py | 4 +- {tests => old_tests}/test-select.py | 4 +- {tests => old_tests}/test-server.py | 27 +- {tests => old_tests}/test-socket.py | 12 +- {tests => old_tests}/test_df | 0 {tests => old_tests}/testthreads.py | 4 +- {tests => old_tests}/timing-test.py | 4 +- {tests => old_tests}/timing/Makefile | 0 old_tests/timing/__init__.py | 0 {tests => old_tests}/timing/getpv.c | 0 {tests => old_tests}/timing/timing.c | 0 .../timing/timing.py | 2 +- setup.py | 38 +- tests/__init__.py | 0 tests/counittest.py | 359 +++++++++ tests/soft_records.db | 20 + tests/test_catools.py | 150 ++++ tests/{cosocket.py => test_cosocket.py} | 70 +- tests/test_cothread.py | 153 ++++ 88 files changed, 3549 insertions(+), 272 deletions(-) create mode 100644 .travis.yml create mode 100644 MANIFEST.in create mode 100644 README.rst create mode 100644 cothread/py23.py create mode 100644 cothread/qt.py create mode 100644 cothread/version.py create mode 100644 docrequirements.txt create mode 100644 docs/training/.gitignore create mode 100644 docs/training/Makefile create mode 100644 docs/training/cothread.rst create mode 100644 docs/training/docutils.conf create mode 100644 docs/training/ioc.db create mode 100644 docs/training/styles/blank.gif create mode 100644 docs/training/styles/dlsfooterpad.png create mode 100644 docs/training/styles/framing.css create mode 100644 docs/training/styles/html4css1.css create mode 100644 docs/training/styles/iepngfix.htc create mode 100644 docs/training/styles/opera.css create mode 100644 docs/training/styles/outline.css create mode 100644 docs/training/styles/pretty.css create mode 100644 docs/training/styles/print.css create mode 100644 docs/training/styles/pygments.css create mode 100644 docs/training/styles/s5-core.css create mode 100644 docs/training/styles/slides.css create mode 100644 docs/training/styles/slides.js create mode 100755 docs/training/testioc rename {tests => old_tests}/caget_failure.py (92%) rename {tests => old_tests}/caget_structure.py (90%) rename {tests => old_tests}/callback.py (88%) create mode 100644 old_tests/callback_result.py rename {tests => old_tests}/camonitor.big.py (95%) rename {tests => old_tests}/camonitor_test.py (79%) rename {tests => old_tests}/control-c.py (52%) rename {tests => old_tests}/interactive.py (91%) rename {tests => old_tests}/late_qt.py (95%) rename {tests => old_tests}/leaktest.py (96%) rename {tests => old_tests}/load.py (93%) rename {tests => old_tests}/plottest.py (92%) rename {tests => old_tests}/recursion-test.py (88%) rename {tests => old_tests}/require.py (57%) rename {tests => old_tests}/simple-modal.py (91%) rename tests/test_ih.py => old_tests/test-ih.py (93%) rename {tests => old_tests}/test-modal.py (94%) rename {tests => old_tests}/test-select.py (90%) rename {tests => old_tests}/test-server.py (58%) rename {tests => old_tests}/test-socket.py (61%) rename {tests => old_tests}/test_df (100%) rename {tests => old_tests}/testthreads.py (96%) rename {tests => old_tests}/timing-test.py (93%) rename {tests => old_tests}/timing/Makefile (100%) create mode 100644 old_tests/timing/__init__.py rename {tests => old_tests}/timing/getpv.c (100%) rename {tests => old_tests}/timing/timing.c (100%) rename tests/timing/test_cothread.py => old_tests/timing/timing.py (96%) create mode 100644 tests/__init__.py create mode 100644 tests/counittest.py create mode 100644 tests/soft_records.db create mode 100755 tests/test_catools.py rename tests/{cosocket.py => test_cosocket.py} (81%) create mode 100644 tests/test_cothread.py diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d66bc01 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,49 @@ +sudo: false +language: python +python: + - "2.6" + - "2.7" + - "3.3" + - "3.4" + - "3.5" + - "3.6" + + + +# Get epics base +addons: + apt: + sources: + - sourceline: 'deb http://epics.nsls2.bnl.gov/debian/ wheezy main contrib' + key_url: 'http://epics.nsls2.bnl.gov/debian/repo-key.pub' + + packages: + - epics-dev + - build-essential + +env: +- EPICS_BASE=/usr/lib/epics EPICS_HOST_ARCH=linux-x86_64 + +cache: + directories: + - $HOME/.cache/pip + - ${VIRTUAL_ENV}/lib/python${TRAVIS_PYTHON_VERSION}/site-packages + - ${VIRTUAL_ENV}/bin + +install: + - env + - ls -al ${VIRTUAL_ENV}/lib/python${TRAVIS_PYTHON_VERSION}/site-packages + - ls -al ${VIRTUAL_ENV}/bin + - pip install numpy pytest + - pip install pytest-cov coveralls + - ls -al ${VIRTUAL_ENV}/lib/python${TRAVIS_PYTHON_VERSION}/site-packages + - ls -al ${VIRTUAL_ENV}/bin + - make PYTHON=python dist build_ext + +# command to run tests +script: + - py.test --cov=cothread --tb=native -vv tests + +# submit coverage +after_script: + - coveralls diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f3762ce --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include context/* +include README.rst + diff --git a/Makefile b/Makefile index d6331df..4a15af6 100644 --- a/Makefile +++ b/Makefile @@ -31,9 +31,20 @@ install: dist --install-dir=$(INSTALL_DIR) \ --script-dir=$(SCRIPT_DIR) dist/*.egg +# publish +publish: default + $(PYTHON) setup.py sdist upload -r pypi + +# publish to test pypi +testpublish: default + $(PYTHON) setup.py sdist upload -r pypitest + docs: cothread/_coroutine.so sphinx-build -b html docs docs/html +test: + $(PYTHON) setup.py test + clean_docs: rm -rf docs/html @@ -46,3 +57,6 @@ cothread/libca_path.py: cothread/_coroutine.so: $(wildcard context/*.c context/*.h) $(PYTHON) setup.py build_ext -i + +build_ext: cothread/_coroutine.so +.PHONY: build_ext diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..25a9f3d --- /dev/null +++ b/README.rst @@ -0,0 +1,50 @@ +|build_status| |coverage| |pypi-version| |readthedocs| + +cothread +======== + +The `cothread` Python library is designed for building tools using cooperative +threading. This means that, with care, programs can effectively run several +tasks simultaneously. + +The `cothread.catools` library is designed to support easy channel access from +Python, and makes essential use of the features of cooperative threads -- in +particular, `catools.camonitor()` notifies updates in the background. + +See the documentation for more details. + + +Installation +------------ +To install the latest release, type:: + + pip install cothread + +To install the latest code directly from source, type:: + + pip install git+git://github.com/dls-controls/cothread + +Documentation +============= + +Full documentation is available at http://cothread.readthedocs.org + +License +======= +GPL2 License (see COPYING) + +.. |pypi-version| image:: https://img.shields.io/pypi/v/cothread.svg + :target: https://pypi.python.org/pypi/cothread/ + :alt: Latest PyPI version + +.. |readthedocs| image:: https://readthedocs.org/projects/cothread/badge/?version=latest + :target: https://readthedocs.org/projects/cothread/?badge=latest + :alt: Documentation Status + +.. |build_status| image:: https://travis-ci.org/dls-controls/cothread.svg?style=flat + :target: https://travis-ci.org/dls-controls/cothread + :alt: Build Status + +.. |coverage| image:: https://coveralls.io/repos/dls-controls/cothread/badge.svg?branch=master&service=github + :target: https://coveralls.io/github/dls-controls/cothread?branch=master + :alt: Test coverage diff --git a/context/_coroutine.c b/context/_coroutine.c index 65f2fc5..d7f045b 100644 --- a/context/_coroutine.c +++ b/context/_coroutine.c @@ -78,16 +78,34 @@ static int get_cocore(PyObject *object, void **result) static void *coroutine_wrapper(void *action_, void *arg_) { PyThreadState *thread_state = PyThreadState_GET(); + /* New coroutine gets a brand new Python interpreter stack frame. */ thread_state->frame = NULL; thread_state->recursion_depth = 0; + /* Also reset the exception state in case it's non NULL at this point. We + * don't own these pointers at this point, coroutine_switch does. */ + thread_state->exc_type = NULL; + thread_state->exc_value = NULL; + thread_state->exc_traceback = NULL; + /* Call the given action with the passed argument. */ PyObject *action = *(PyObject **)action_; PyObject *arg = arg_; PyObject *result = PyObject_CallFunctionObjArgs(action, arg, NULL); Py_DECREF(action); Py_DECREF(arg); + + /* Some of the stuff we've initialised can leak through, so far I've only + * seen exc_type still set at this point, but maybe other fields can also + * leak. Avoid a memory leak by making sure we're not holding onto these. + * All these pointers really are defunct, because as soon as we return + * coroutine_switch will replace all these values. */ + Py_XDECREF(thread_state->frame); + Py_XDECREF(thread_state->exc_type); + Py_XDECREF(thread_state->exc_value); + Py_XDECREF(thread_state->exc_traceback); + return result; } @@ -126,6 +144,14 @@ static PyObject *coroutine_switch(PyObject *Self, PyObject *args) struct _frame *python_frame = thread_state->frame; int recursion_depth = thread_state->recursion_depth; + /* We also need to switch the exception state around: if we don't do + * this then we get confusion about the lifetime of exception state + * between coroutines. The most obvious problem is that the exception + * isn't properly cleared on function return. */ + PyObject *exc_type = thread_state->exc_type; + PyObject *exc_value = thread_state->exc_value; + PyObject *exc_traceback = thread_state->exc_traceback; + /* Switch to new coroutine. For the duration arg needs an extra * reference count, it'll be accounted for either on the next returned * result or in the entry to a new coroutine. */ @@ -137,6 +163,11 @@ static PyObject *coroutine_switch(PyObject *Self, PyObject *args) thread_state = PyThreadState_GET(); thread_state->frame = python_frame; thread_state->recursion_depth = recursion_depth; + + /* Restore the exception state. */ + thread_state->exc_type = exc_type; + thread_state->exc_value = exc_value; + thread_state->exc_traceback = exc_traceback; return result; } else @@ -157,6 +188,21 @@ static PyObject *coroutine_getcurrent(PyObject *self, PyObject *args) } +static PyObject *coroutine_is_equal(PyObject *self, PyObject *args) +{ + struct cocore *cocore1, *cocore2; + if (PyArg_ParseTuple(args, "O&O&", get_cocore, &cocore1, get_cocore, &cocore2)) + { + if (cocore1 == cocore2) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; + } + else + return NULL; +} + + static PyObject *enable_check_stack(PyObject *self, PyObject *arg) { int is_true = PyObject_IsTrue(arg); @@ -228,6 +274,9 @@ static PyObject *install_readline_hook(PyObject *self, PyObject *arg) static PyMethodDef module_methods[] = { { "get_current", coroutine_getcurrent, METH_NOARGS, "_coroutine.getcurrent()\nReturns the current coroutine." }, + { "is_equal", coroutine_is_equal, METH_VARARGS, + "is_equal(coroutine1, coroutine2)\n\ +Compares two coroutine objects for equality" }, { "create", coroutine_create, METH_VARARGS, "create(parent, action, stack_size)\n\ Creates a new coroutine with the given action to invoke. The parent\n\ diff --git a/context/switch-arm.c b/context/switch-arm.c index f4bb5ba..15ef951 100644 --- a/context/switch-arm.c +++ b/context/switch-arm.c @@ -29,6 +29,14 @@ // Coroutine frame switching for ARM +// If Vector Floating Point support is present then we need to preserve the VFP +// registers d8-d15. +#ifdef __VFP_FP__ +#define IF_VFP_FP(code) code +#else +#define IF_VFP_FP(code) +#endif + __asm__( " .text\n" " .align 2\n" @@ -41,9 +49,13 @@ __asm__( FNAME(switch_frame) " stmfd sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}\n" +IF_VFP_FP( +" fstmfdd sp!, {d8-d15}\n") " str sp, [r0]\n" " mov sp, r1\n" " mov r0, r2\n" +IF_VFP_FP( +" fldmfdd sp!, {d8-d15}\n") " ldmfd sp!, {r4, r5, r6, r7, r8, r9, sl, fp, pc}\n" FSIZE(switch_frame) @@ -58,6 +70,8 @@ FNAME(create_frame) " mov ip, lr\n" // Save LR so can use same STM slot " ldr lr, =action_entry\n" " stmfd r0!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}\n" +IF_VFP_FP( +" fstmfdd r0!, {d8-d15}\n") " bx ip\n" "action_entry:\n" diff --git a/context/tests/coco.py b/context/tests/coco.py index 4464d9b..d86bf08 100755 --- a/context/tests/coco.py +++ b/context/tests/coco.py @@ -1,4 +1,4 @@ -#!/dls_sw/tools/python2.4-debug/bin/python2.4 +#!/usr/bin/env python from __future__ import print_function diff --git a/context/tests/leak.py b/context/tests/leak.py index 8c886dd..ac0c735 100755 --- a/context/tests/leak.py +++ b/context/tests/leak.py @@ -1,4 +1,4 @@ -#!/dls_sw/tools/python2.4-debug/bin/python2.4 +#!/usr/bin/env python from __future__ import print_function diff --git a/cothread/__init__.py b/cothread/__init__.py index 1e044ad..e0a8018 100644 --- a/cothread/__init__.py +++ b/cothread/__init__.py @@ -45,6 +45,7 @@ from .input_hook import * from .coselect import * from .cosocket import * +from .version import __version__ # Publish all public symbols from cothread and input_hook as default exports. # The coselect functions aren't exported by default but are available. diff --git a/cothread/cadef.py b/cothread/cadef.py index 6651b00..8d98fa9 100644 --- a/cothread/cadef.py +++ b/cothread/cadef.py @@ -47,6 +47,7 @@ import ctypes from .load_ca import libca +from . import py23 @@ -192,6 +193,7 @@ def convert_py_object(object, function, args): ca_message = libca.ca_message ca_message.argtypes = [ctypes.c_long] ca_message.restype = ctypes.c_char_p +ca_message.errcheck = py23.auto_decode # channel_name = ca_name(channel) @@ -200,6 +202,7 @@ def convert_py_object(object, function, args): ca_name = libca.ca_name ca_name.argtypes = [ctypes.c_void_p] ca_name.restype = ctypes.c_char_p +ca_name.errcheck = py23.auto_decode # @exception_handler @@ -243,7 +246,7 @@ def convert_py_object(object, function, args): # recovered by calling ca_puser(channel_id). ca_create_channel = libca.ca_create_channel ca_create_channel.argtypes = [ - ctypes.c_char_p, connection_handler, ctypes.py_object, + py23.auto_encode, connection_handler, ctypes.py_object, ctypes.c_int, ctypes.c_void_p] ca_create_channel.errcheck = expect_ECA_NORMAL @@ -397,6 +400,7 @@ def convert_py_object(object, function, args): ca_host_name = libca.ca_host_name ca_host_name.argtypes = [ctypes.c_void_p] ca_host_name.restype = ctypes.c_char_p +ca_host_name.errcheck = py23.auto_decode # read = ca_read_access(channel_id) diff --git a/cothread/catools.py b/cothread/catools.py index 6ea9586..f8bdf11 100644 --- a/cothread/catools.py +++ b/cothread/catools.py @@ -58,6 +58,7 @@ from . import cothread from . import cadef from . import dbr +from . import py23 from .dbr import * from .cadef import * @@ -68,6 +69,7 @@ 'caget', # Read PVs from channel access 'camonitor', # Monitor PVs over channel access 'connect', # Establish PV connection + 'cainfo', # Returns ca_info describing PV connection ] + dbr.__all__ + cadef.__all__ @@ -84,6 +86,11 @@ def _check_env(name, default): CA_ACTION_STACK = _check_env('CATOOLS_ACTION_STACK', 0) +if sys.version_info < (3,): + pv_string_types = (str, unicode) +else: + pv_string_types = str + class ca_nothing(Exception): '''This value is returned as a success or failure indicator from caput, @@ -102,8 +109,9 @@ def __repr__(self): def __str__(self): return '%s: %s' % (self.name, cadef.ca_message(self.errorcode)) - def __nonzero__(self): + def __bool__(self): return self.ok + __nonzero__ = __bool__ # For python 2 def __iter__(self): '''This is *not* supposed to be an iterable object, but the base class @@ -145,8 +153,8 @@ def ca_timeout(event, timeout, name): ca_nothing timeout exception containing the PV name.''' try: return event.Wait(timeout) - except cothread.Timedout: - raise ca_nothing(name, cadef.ECA_TIMEOUT) + except cothread.Timedout as timeout: + py23.raise_from(ca_nothing(name, cadef.ECA_TIMEOUT), timeout) # ---------------------------------------------------------------------------- @@ -550,7 +558,7 @@ def camonitor(pvs, callback, **kargs): if notify_disconnect is False, and that if the PV subsequently connects it will update as normal. ''' - if isinstance(pvs, str): + if isinstance(pvs, pv_string_types): return _Subscription(pvs, callback, **kargs) else: return [ @@ -733,7 +741,7 @@ def caget(pvs, **kargs): The format of values returned depends on the number of values requested for each PV. If only one value is requested then the value is returned as a scalar, otherwise as a numpy array.''' - if isinstance(pvs, str): + if isinstance(pvs, pv_string_types): return caget_one(pvs, **kargs) else: return caget_array(pvs, **kargs) @@ -806,7 +814,7 @@ def caput_one(pv, value, datatype=None, wait=False, timeout=5, callback=None): def caput_array(pvs, values, repeat_value=False, **kargs): # Bring the arrays of pvs and values into alignment. - if repeat_value or isinstance(values, str): + if repeat_value or isinstance(values, pv_string_types): # If repeat_value is requested or the value is a string then we treat # it as a single value. values = [values] * len(pvs) @@ -877,7 +885,7 @@ def caput(pvs, values, **kargs): If caput completed succesfully then .ok is True and .name is the corresponding PV name. If throw=False was specified and a put failed then .errorcode is set to the appropriate ECA_ error code.''' - if isinstance(pvs, str): + if isinstance(pvs, pv_string_types): return caput_one(pvs, values, **kargs) else: return caput_array(pvs, values, **kargs) @@ -982,12 +990,18 @@ def connect(pvs, **kargs): connected to. If this is set to False then instead for each failing PV a sentinel value with .ok == False is returned. ''' - if isinstance(pvs, str): + if isinstance(pvs, pv_string_types): return connect_one(pvs, **kargs) else: return connect_array(pvs, **kargs) +def cainfo(pvs, **args): + '''Returns a ca_info structure for the given PVs. See the documentation + for connect() for more detail.''' + return connect(pvs, cainfo = True, wait = True, **args) + + # ---------------------------------------------------------------------------- # Final module initialisation @@ -1038,13 +1052,3 @@ def __call__(self): self._flush_io_event.Signal() _flush_io = _FlushIo() - - -# The value of the exception handler below is rather doubtful... -if False: - @exception_handler - def catools_exception(args): - '''print ca exception message''' - print('catools_exception:', args.ctx, cadef.ca_message(args.stat), - file = sys.stderr) - cadef.ca_add_exception_event(catools_exception, 0) diff --git a/cothread/coselect.py b/cothread/coselect.py index b87d804..d3e4801 100644 --- a/cothread/coselect.py +++ b/cothread/coselect.py @@ -68,7 +68,7 @@ def select_hook(): # A helpful routine to ensure that our select() behaves as much as possible # like the real thing! def _AsFileDescriptor(file): - if isinstance(file, (int, long)): + if isinstance(file, int): return file else: return file.fileno() @@ -248,6 +248,7 @@ def poll_list(event_list, timeout = None): constants). This routine will cooperatively block until any descriptor signals a selected event (or any event from HUP, ERR, NVAL) or until the timeout (in seconds) occurs.''' + cothread.cothread._validate_thread() poller = _Poller(event_list) cothread.cothread._scheduler.poll_until( poller, cothread.cothread.GetDeadline(timeout)) diff --git a/cothread/coserver.py b/cothread/coserver.py index cca8edc..07d0090 100644 --- a/cothread/coserver.py +++ b/cothread/coserver.py @@ -22,11 +22,20 @@ from the SocketServer and BaseHTTPServer modules """ -import SocketServer, BaseHTTPServer, SimpleHTTPServer +import sys -import cothread -import cosocket -import coselect +if sys.version_info < (3,): + from SocketServer import BaseServer, TCPServer, UDPServer, ThreadingMixIn + from BaseHTTPServer import HTTPServer, test as _test + from SimpleHTTPServer import SimpleHTTPRequestHandler + +else: + from socketserver import BaseServer, TCPServer, UDPServer, ThreadingMixIn + from http.server import HTTPServer, SimpleHTTPRequestHandler, test as _test + +from . import cothread +from . import cosocket +from . import coselect __all__ = [ 'BaseServer', @@ -60,7 +69,7 @@ def __init__(self, *args, **kws): self.__shut = cosocket.socketpair() if hasattr(cls, 'address_family'): - self.socket = cosocket.socket(None, None, None, self.socket) + self.socket = cosocket.cosocket(_sock = self.socket) if baact: self.server_bind() self.server_activate() @@ -101,13 +110,13 @@ def server_close(self): return WrappedServer -BaseServer = _patch(SocketServer.BaseServer) -TCPServer = _patch(SocketServer.TCPServer) -UDPServer = _patch(SocketServer.UDPServer) -HTTPServer = _patch(BaseHTTPServer.HTTPServer) +BaseServer = _patch(BaseServer) +TCPServer = _patch(TCPServer) +UDPServer = _patch(UDPServer) +HTTPServer = _patch(HTTPServer) -class CoThreadingMixIn(SocketServer.ThreadingMixIn): +class CoThreadingMixIn(ThreadingMixIn): def process_request(self, request, client_address): cothread.Spawn(self.process_request_thread, request, client_address) @@ -115,10 +124,9 @@ class CoThreadingUDPServer(CoThreadingMixIn, UDPServer): pass class CoThreadingTCPServer(CoThreadingMixIn, TCPServer): pass class CoThreadingHTTPServer(CoThreadingMixIn, HTTPServer): pass -def test(HandlerClass = SimpleHTTPServer.SimpleHTTPRequestHandler, - ServerClass = CoThreadingHTTPServer): - BaseHTTPServer.test(HandlerClass, ServerClass) - +def test(HandlerClass=SimpleHTTPRequestHandler, + ServerClass=CoThreadingHTTPServer): + _test(HandlerClass, ServerClass) if __name__ == '__main__': test() diff --git a/cothread/cosocket.py b/cothread/cosocket.py index da3545d..0252de2 100644 --- a/cothread/cosocket.py +++ b/cothread/cosocket.py @@ -30,6 +30,7 @@ standard socket module.''' import os +import sys import errno from . import coselect @@ -48,33 +49,44 @@ def socket_hook(): '''Replaces the blocking methods in the socket module with the non-blocking methods implemented here. Not safe to call if other threads need the original methods.''' - _socket.socket = socket + _socket.socket = cosocket _socket.socketpair = socketpair + def socketpair(*args): - # For unfathomable reasons socketpair() returns un-wrapped '_socket.socket' - # So they are only wrapped once. - return tuple(map(lambda S: socket(_sock = S), _socket_pair(*args))) -socketpair.__doc__ = _socket.socketpair.__doc__ + a, b = _socket_pair(*args) + # Now wrap them to make them co-operative if needed + if not isinstance(a, cosocket): + a = cosocket(_sock = a) + if not isinstance(b, cosocket): + b = cosocket(_sock = b) + return a, b +socketpair.__doc__ = _socket_pair.__doc__ + def create_connection(*args, **kargs): sock = _socket.create_connection(*args, **kargs) - return socket(_sock = sock) + return cosocket(_sock = sock) create_connection.__doc__ = _socket.create_connection.__doc__ -class socket(object): - __doc__ = _socket_socket.__doc__ - def wrap(fun): - fun.__doc__ = getattr(_socket_socket, fun.__name__).__doc__ - return fun +def wrap(fun): + fun.__doc__ = getattr(_socket_socket, fun.__name__).__doc__ + return fun + + +class cosocket(object): + __doc__ = _socket_socket.__doc__ def __init__(self, family=_socket.AF_INET, type=_socket.SOCK_STREAM, proto=0, - _sock=None): - + fileno=None, _sock=None): + # This is the real socket object we will defer all calls to if _sock is None: - _sock = _socket_socket(family, type, proto) + if fileno is not None: + _sock = _socket_socket(family, type, proto, fileno) + else: + _sock = _socket_socket(family, type, proto) self.__socket = _sock self.__socket.setblocking(0) self.__timeout = _socket.getdefaulttimeout() @@ -121,25 +133,23 @@ def connect_ex(self, address): except _socket.error as error: return error.errno - def __poll(self, event): if not coselect.poll_list([(self, event)], self.__timeout): raise _socket.error(errno.ETIMEDOUT, 'Timeout waiting for socket') - def __retry(self, poll, action, args): + def __retry(self, event, action, args): while True: try: return action(*args) except _socket.error as error: if error.errno != errno.EAGAIN: raise - self.__poll(poll) - + self.__poll(event) @wrap def accept(self): sock, addr = self.__retry(coselect.POLLIN, self.__socket.accept, ()) - return (socket(_sock = sock), addr) + return (cosocket(_sock = sock), addr) @wrap def recv(self, *args): @@ -174,20 +184,37 @@ def sendall(self, data, *flags): @wrap def dup(self): - return socket(None, None, None, self.__socket.dup()) - - @wrap - def makefile(self, *args, **kws): - # At this point the actual socket '_socket.socket' is wrapped by either - # two layers: 'socket.socket' and this class. or a single layer: this - # class. In order to handle close() properly we must copy all wrappers, - # but not the underlying actual socket. - sock = getattr(self.__socket, '_sock', None) - if sock: # double wrapped - copy0 = _socket_socket(None, None, None, sock) - copy1 = socket(None, None, None, copy0) - else: # single wrapped - copy1 = socket(None, None, None, self.__socket) - return _socket._fileobject(copy1, *args, **kws) - - del wrap + return cosocket(_sock=self.__socket.dup()) + + if sys.version_info < (3,): + @wrap + def makefile(self, *args, **kws): + # At this point the actual socket '_socket.socket' is wrapped by + # either two layers: 'socket.socket' and this class. or a single + # layer: this class. In order to handle close() properly we must + # copy all wrappers, but not the underlying actual socket. + sock = getattr(self.__socket, '_sock', None) + if sock: # double wrapped + copy0 = _socket_socket(None, None, None, sock) + copy1 = cosocket(None, None, None, copy0) + else: # single wrapped + copy1 = cosocket(None, None, None, self.__socket) + return _socket._fileobject(copy1, *args, **kws) + else: + @property + def _io_refs(self): + return self.__socket._io_refs + + @_io_refs.setter + def _io_refs(self, value): + self.__socket._io_refs = value + + # Can use the original makefile just so long as we provide the _io_refs + # property above. + makefile = _socket_socket.makefile + + +del wrap + +# Make an alias to it +socket = cosocket diff --git a/cothread/cothread.py b/cothread/cothread.py index 1e7fd9e..5375453 100644 --- a/cothread/cothread.py +++ b/cothread/cothread.py @@ -74,15 +74,21 @@ import bisect import traceback import collections -import thread +import threading from . import _coroutine +from . import py23 if os.environ.get('COTHREAD_CHECK_STACK'): _coroutine.enable_check_stack(True) from . import coselect +if sys.version_info >= (3,): + import _thread +else: + import thread as _thread + __all__ = [ 'Spawn', # Spawn new task @@ -92,6 +98,7 @@ 'Yield', # Suspend task for immediate resumption 'Event', # Event for waiting and signalling + 'RLock', # Recursive lock 'Pulse', # Event for dynamic condition variables 'EventQueue', # Queue of objects with event handling 'ThreadedEventQueue', # Event queue designed to work with threads @@ -108,6 +115,8 @@ 'Timer', # One-shot cancellable timer 'Callback', # Simple asynchronous synchronisation + 'CallbackResult', # Asynchronous synchronisation with result + 'scheduler_thread_id', # For checking we're in cothread's thread ] @@ -248,7 +257,7 @@ def wakeup(self, reason): self.__task = None # Each queue needs to be cancelled if it's not the wakeup reason. - # This test also properly deals with _WAKEUP_INTERRUPT, which + # This test also properly deals with interrupt wakeup, which # requires both queues to be cancelled. if reason != _WAKEUP_NORMAL and self.__queue: self.__queue.cancel() @@ -269,7 +278,8 @@ def woken(self): # Task wakeup reasons _WAKEUP_NORMAL = 0 # Normal wakeup _WAKEUP_TIMEOUT = 1 # Wakeup on timeout -_WAKEUP_INTERRUPT = 2 # Special: transfer scheduler exception to main +# A third reason, transfering exception to another cothread, is encoded as a +# tuple. # Important system invariants: @@ -324,9 +334,9 @@ def __scheduler(cls, main_task): if task is main_task: del self.__ready_queue[index] break - # All task wakeup entry points will interpret this as a - # request to re-raise the exception. - _coroutine.switch(main_task, _WAKEUP_INTERRUPT) + # All task wakeup entry points will interpret this as a request + # to re-raise the exception. Pass through the exception info. + _coroutine.switch(main_task, sys.exc_info()) def __init__(self): # List of all tasks that are currently ready to be dispatched. @@ -430,9 +440,9 @@ def poll_scheduler(self, ready_list): result = _coroutine.switch(self.__coroutine, ready_list) self.__poll_callback = None - if result == _WAKEUP_INTERRUPT: + if isinstance(result, tuple): # This case arises if we are main and the scheduler just died. - raise + py23.raise_with_traceback(result) else: return result @@ -477,12 +487,12 @@ def wait_until(self, until, suspend_queue, wakeup): # returned to __select(). This last case expects a list of ready # descriptors to be returned, so we have to be compatible with this! result = _coroutine.switch(self.__coroutine, []) - if result == _WAKEUP_INTERRUPT: + if isinstance(result, tuple): # We get here if main is suspended and the scheduler decides # to die. Make sure our wakeup is cancelled, and then # re-raise the offending exception. wakeup.wakeup(result) - raise + py23.raise_with_traceback(result) else: return result == _WAKEUP_TIMEOUT @@ -509,7 +519,7 @@ def __Wakeup(self, queue, until): return _Wakeup(self.__wakeup_task, queue, self.__timer_queue) def __wakeup_task(self, task, reason): - if reason != _WAKEUP_INTERRUPT: + if not isinstance(reason, tuple): self.__ready_queue.append((task, reason)) def __wakeup_poll(self, poll_result): @@ -683,9 +693,10 @@ def __run(self, _): # See wait_until() for an explanation of this return value. return [] - def __nonzero__(self): + def __bool__(self): '''Tests whether the event is signalled.''' return bool(self.__result) + __nonzero__ = __bool__ def Wait(self, timeout = None): '''Waits until the task has completed. May raise an exception if the @@ -700,7 +711,7 @@ def Wait(self, timeout = None): try: # Re-raise the exception that actually killed the task here # where it can be received by whoever waits on the task. - raise result[0], result[1], result[2] + py23.raise_with_traceback(result) finally: # In this case result and self.__result contain a traceback. To # avoid circular references which will delay garbage collection, @@ -741,9 +752,10 @@ def __init__(self, auto_reset = True): self.__value = () self.__auto_reset = auto_reset - def __nonzero__(self): + def __bool__(self): '''Tests whether the event is signalled.''' return bool(self.__value) + __nonzero__ = __bool__ def Wait(self, timeout = None): '''The caller will block until the event becomes true, or until the @@ -870,6 +882,7 @@ def __iter__(self): def next(self): return self.Wait() + __next__ = next class ThreadedEventQueue(object): @@ -895,7 +908,7 @@ def Wait(self, timeout = None): '''Waits for a value to be written to the queue. This can safely be called from either a cothread or another thread: the appropriate form of cooperative or normal blocking will be selected automatically.''' - if thread.get_ident() == _scheduler_thread_id: + if _thread.get_ident() == scheduler_thread_id: # Normal cothread case, use cooperative wait poll = coselect.poll_list else: @@ -911,7 +924,7 @@ def Signal(self, value): '''Posts a value to the event queue. This can safely be called from a thread or a cothread.''' self.__values.append(value) - os.write(self.__signal, '-') + os.write(self.__signal, b'-') @@ -958,7 +971,48 @@ def __call__(self, action, *args): self.values.append((action, args)) if self.waiting: self.waiting = False - os.write(self.signal, '-') + os.write(self.signal, b'-') + + +def CallbackResult(action, *args, **kargs): + '''Perform action in the main cothread and return a result.''' + callback = kargs.pop('callback_queue', Callback) + timeout = kargs.pop('callback_timeout', None) + spawn = kargs.pop('callback_spawn', True) + + if scheduler_thread_id == _thread.get_ident(): + return action(*args, **kargs) + else: + event = threading.Event() + action_result = [False, None] + def do_action(): + try: + action_result[0] = True + action_result[1] = action(*args, **kargs) + except: + action_result[0] = False + action_result[1] = sys.exc_info() + event.set() + + # Hand the action over to the cothread carrying thread for action and + # wait for the result. + if spawn: + callback(Spawn, do_action) + else: + callback(do_action) + if not event.wait(timeout): + raise Timedout('Timed out waiting for callback result') + + # Return result or raise caught exception as appropriate. + ok, result = action_result + if ok: + return result + else: + py23.raise_with_traceback(result) + + # Note: raising entire stack backtrace context might be dangerous, need + # to think about this carefully, particularly if the corresponding stack + # has been swapped out... class Timer(object): @@ -1090,14 +1144,14 @@ def quit(signum, frame): _scheduler = _Scheduler.create() # We hang onto the thread ID for the cothread thread (at present there can # only be one) so that we can recognise when we're in another thread. -_scheduler_thread_id = thread.get_ident() +scheduler_thread_id = _thread.get_ident() # Thread validation: ensure cothreads aren't used across threads! def _validate_thread(): - assert _scheduler_thread_id == thread.get_ident(), \ - 'Cannot use cothread with multiple threads. Consider using ' \ - 'Callback or ThreadedEventQueue if necessary.' + assert scheduler_thread_id == _thread.get_ident(), \ + 'Cannot call into cothread from another thread. Consider using ' \ + 'Callback or CallbackResult.' # This is the asynchronous callback method. Callback = _Callback() @@ -1119,3 +1173,50 @@ def Yield(timeout = 0): waiting to be run.''' _validate_thread() _scheduler.do_yield(GetDeadline(timeout)) + + +class RLock(object): + """A reentrant lock.""" + + __slots__ = [ + '__event', # Underlying event object + '__owner', # The coroutine that has locked + '__count', # The number of times the owner has locked + ] + + def __init__(self): + self.__event = Event() + # Start off with the event set so acquire will not block + self.__event.Signal() + self.__owner = None + self.__count = 0 + + def acquire(self, timeout=None): + """Acquire the lock if necessary and increment the recursion level.""" + # Inspired by threading.RLock + me = _coroutine.get_current() + if self.__owner and _coroutine.is_equal(self.__owner, me): + # if we are the owner then just increment the count + self.__count += 1 + else: + # otherwise wait until it is unlocked + self.__event.Wait(timeout=timeout) + self.__owner = me + self.__count = 1 + + def release(self): + """Release a lock, decrementing the recursion level.""" + assert self.__owner and _coroutine.is_equal( + self.__owner, _coroutine.get_current()), \ + "cannot release un-acquired lock" + self.__count -= 1 + if self.__count == 0: + self.__owner = None + # Wakeup one cothread waiting on acquire() + self.__event.Signal() + + # Needed to make it a context manager + __enter__ = acquire + + def __exit__(self, t, v, tb): + self.release() diff --git a/cothread/dbr.py b/cothread/dbr.py index 1c152dc..51906de 100644 --- a/cothread/dbr.py +++ b/cothread/dbr.py @@ -31,11 +31,13 @@ header file db_access.h ''' +import sys import ctypes import numpy import datetime from . import cadef +from . import py23 __all__ = [ @@ -52,6 +54,7 @@ 'DBR_CHAR_STR', # Long strings as char arrays 'DBR_CHAR_UNICODE', # Long unicode strings as char arrays 'DBR_ENUM_STR', # Enums as strings, default otherwise + 'DBR_CHAR_BYTES', # Long byte strings as char arrays 'DBR_PUT_ACKT', # Configure global alarm acknowledgement 'DBR_PUT_ACKS', # Acknowledge global alarm @@ -161,11 +164,29 @@ class ca_str(str): def __pos__(self): return str(self) -class ca_unicode(unicode): - __doc__ = ca_doc_string - datetime = timestamp_to_datetime - def __pos__(self): - return unicode(self) + +# Overlapping handling for python 2 and python 3. We have three types with two +# different semantics: str, bytes, unicode. In python2 str is bytes, while in +# python3 str is unicode. We walk a delicate balancing act to get the right +# behaviour in both environments! +if sys.version_info < (3,): + ca_bytes = ca_str + class ca_unicode(bytes): + __doc__ = ca_doc_string + datetime = timestamp_to_datetime + def __pos__(self): + return unicode(self) + str_char_code = 'S' +else: + class ca_bytes(bytes): + __doc__ = ca_doc_string + datetime = timestamp_to_datetime + def __pos__(self): + return bytes(self) + ca_unicode = ca_str + str_char_code = 'U' + unicode = str + class ca_int(int): __doc__ = ca_doc_string @@ -220,7 +241,7 @@ def copy_attributes_ctrl(self, other): other.status = self.status other.severity = self.severity - other.units = ctypes.string_at(self.units) + other.units = py23.decode(ctypes.string_at(self.units)) other.upper_disp_limit = self.upper_disp_limit other.lower_disp_limit = self.lower_disp_limit other.upper_alarm_limit = self.upper_alarm_limit @@ -409,7 +430,9 @@ class dbr_ctrl_enum(ctypes.Structure): def copy_attributes(self, other): other.status = self.status other.severity = self.severity - other.enums = map(ctypes.string_at, self.raw_strs[:self.no_str]) + other.enums = [ + py23.decode(ctypes.string_at(s)) + for s in self.raw_strs[:self.no_str]] class dbr_ctrl_char(ctypes.Structure): dtype = numpy.uint8 @@ -516,6 +539,7 @@ def copy_attributes(self, other): # Special value for DBR_CHAR as str special processing. DBR_ENUM_STR = 996 +DBR_CHAR_BYTES = 997 DBR_CHAR_UNICODE = 998 DBR_CHAR_STR = 999 @@ -566,7 +590,7 @@ def copy_attributes(self, other): 'i': DBR_LONG, # intc = int32 'f': DBR_FLOAT, # single = float32 'd': DBR_DOUBLE, # float_ = float64 - 'S': DBR_STRING, # str_ + 'S': DBR_STRING, # bytes_ # The following type codes are weakly supported by pretending that # they're related types. @@ -606,9 +630,11 @@ def _datatype_to_dbr(datatype): # Rely on numpy for generic datatype recognition and conversion together # with filtering through our array of acceptable types. return NumpyCharCodeToDbr[numpy.dtype(datatype).char] - except: - raise InvalidDatatype( - 'Datatype "%s" not supported for channel access' % datatype) + except Exception as error: + py23.raise_from( + InvalidDatatype( + 'Datatype "%s" not supported for channel access' % datatype), + error) def _type_to_dbrcode(datatype, format): '''Converts a datatype and format request to a dbr value, or raises an @@ -623,7 +649,7 @@ def _type_to_dbrcode(datatype, format): - FORMAT_CTRL: retrieve limit and control data ''' if datatype not in BasicDbrTypes: - if datatype in [DBR_CHAR_STR, DBR_CHAR_UNICODE]: + if datatype in [DBR_CHAR_STR, DBR_CHAR_BYTES, DBR_CHAR_UNICODE]: datatype = DBR_CHAR # Retrieve this type using char array elif datatype in [DBR_STSACK_STRING, DBR_CLASS_NAME]: return datatype # format is meaningless in this case @@ -649,7 +675,7 @@ def _type_to_dbrcode(datatype, format): raise InvalidDatatype('Format not recognised') -# Helper functions for string arrays used in _convert_str_{str,unicode} below. +# Helper functions for string arrays used in _convert_str_{str,bytes} below. def _make_strings(raw_dbr, count): p_raw_value = ctypes.pointer(raw_dbr.raw_value[0]) return [ctypes.string_at(p_raw_value[n]) for n in range(count)] @@ -678,25 +704,38 @@ def _string_at(raw_value, count): # Conversion from char array to strings def _convert_char_str(raw_dbr, count): - return ca_str(_string_at(raw_dbr.raw_value, count)) + return ca_str(py23.decode(_string_at(raw_dbr.raw_value, count))) + +# Conversion from char array to bytes strings +def _convert_char_bytes(raw_dbr, count): + return ca_bytes(_string_at(raw_dbr.raw_value, count)) # Conversion from char array to unicode strings def _convert_char_unicode(raw_dbr, count): return ca_unicode(_string_at(raw_dbr.raw_value, count).decode('UTF-8')) + # Arrays of standard strings. def _convert_str_str(raw_dbr, count): - return ca_str(_make_strings(raw_dbr, count)[0]) + return ca_str(py23.decode(_make_strings(raw_dbr, count)[0])) def _convert_str_str_array(raw_dbr, count): + strings = [py23.decode(s) for s in _make_strings(raw_dbr, count)] + return _string_array(strings, count, str_char_code) + +# Arrays of bytes strings. +def _convert_str_bytes(raw_dbr, count): + return ca_bytes(_make_strings(raw_dbr, count)[0]) +def _convert_str_bytes_array(raw_dbr, count): return _string_array(_make_strings(raw_dbr, count), count, 'S') # Arrays of unicode strings. def _convert_str_unicode(raw_dbr, count): - return ca_unicode(_make_strings(raw_dbr, count)[0].decode('UTF-8')) + return ca_str(_make_strings(raw_dbr, count)[0].decode('UTF-8')) def _convert_str_unicode_array(raw_dbr, count): strings = [s.decode('UTF-8') for s in _make_strings(raw_dbr, count)] return _string_array(strings, count, 'U') + # For everything that isn't a string we either return a scalar or a ca_array def _convert_other(raw_dbr, count): # Single elements are always returned as scalars. @@ -745,13 +784,18 @@ def type_to_dbr(channel, datatype, format): if dtype is numpy.uint8 and datatype == DBR_CHAR_STR: # Conversion from char array to strings convert = _convert_char_str + elif dtype is numpy.uint8 and datatype == DBR_CHAR_BYTES: + # Conversion from char array to bytes strings + convert = _convert_char_bytes elif dtype is numpy.uint8 and datatype == DBR_CHAR_UNICODE: # Conversion from char array to unicode strings convert = _convert_char_unicode else: if dtype is str_dtype: # String arrays, either unicode or normal. - if isinstance(datatype, type) and issubclass(datatype, unicode): + if isinstance(datatype, type) and issubclass(datatype, bytes): + convert = (_convert_str_bytes, _convert_str_bytes_array) + elif isinstance(datatype, type) and issubclass(datatype, unicode): convert = (_convert_str_unicode, _convert_str_unicode_array) else: convert = (_convert_str_str, _convert_str_str_array) @@ -820,7 +864,7 @@ def value_to_dbr(channel, datatype, value): # If no datatype specified then use the target datatype. if datatype is None: - if isinstance(value, (str, unicode)): + if isinstance(value, (str, bytes, unicode)): # Give strings with no datatype special treatment, let the IOC do # the decoding. It's safer this way. datatype = DBR_STRING diff --git a/cothread/input_hook.py b/cothread/input_hook.py index e65e196..3de4fc0 100644 --- a/cothread/input_hook.py +++ b/cothread/input_hook.py @@ -90,7 +90,7 @@ def timeout(): # Set up a timer so that Qt polls cothread. All the timer needs to do # is to yield control to the coroutine system. - from PyQt4 import QtCore + from .qt import QtCore timer = QtCore.QTimer() timer.timeout.connect(timeout) timer.start(poll_interval * 1e3) @@ -114,7 +114,7 @@ def iqt(poll_interval = 0.05, run_exec = True, argv = None): '''Installs Qt event handling hook. The polling interval is in seconds.''' - from PyQt4 import QtCore, QtGui + from .qt import QtCore, QtWidgets global _qapp, _timer # Importing PyQt4 has an unexpected side effect: it removes the input hook! @@ -131,11 +131,11 @@ def iqt(poll_interval = 0.05, run_exec = True, argv = None): if _qapp is None: if argv is None: argv = sys.argv - _qapp = QtGui.QApplication(argv) + _qapp = QtWidgets.QApplication(argv) # Arrange to get a Quit event when the last window goes. This allows the # application to simply rest on WaitForQuit(). - _qapp.lastWindowClosed.connect(cothread.Quit) + _qapp.aboutToQuit.connect(cothread.Quit) # Create timer. Hang onto the timer to prevent it from vanishing. _timer = _timer_iqt(poll_interval) diff --git a/cothread/load_ca.py b/cothread/load_ca.py index 1184ad7..022aaac 100644 --- a/cothread/load_ca.py +++ b/cothread/load_ca.py @@ -51,6 +51,31 @@ lib_files = ['libca.so'] +# Mapping from host architecture to EPICS host architecture name can be done +# with a little careful guesswork. As EPICS architecture names are a little +# arbitrary this isn't guaranteed to work. +_epics_system_map = { + ('Linux', '32bit'): 'linux-x86', + ('Linux', '64bit'): 'linux-x86_64', + ('Darwin', '32bit'): 'darwin-x86', + ('Darwin', '64bit'): 'darwin-x86', + ('Windows', '32bit'): 'win32-x86', + ('Windows', '64bit'): 'windows-x64', # Not quite yet! +} + +def _get_arch(): + import os + try: + return os.environ['EPICS_HOST_ARCH'] + except KeyError: + import platform + system = platform.system() + bits = platform.architecture()[0] + return _epics_system_map[(system, bits)] + +epics_host_arch = _get_arch() + + def _libca_path(load_libca_path): # We look for libca in a variety of different places, searched in order: # @@ -84,19 +109,6 @@ def _libca_path(load_libca_path): # No local install, no local configuration, no override. Try for standard # environment variable configuration instead. epics_base = os.environ['EPICS_BASE'] - # Mapping from host architecture to EPICS host architecture name can be done - # with a little careful guesswork. As EPICS architecture names are a little - # arbitrary this isn't guaranteed to work. - system_map = { - ('Linux', '32bit'): 'linux-x86', - ('Linux', '64bit'): 'linux-x86_64', - ('Darwin', '32bit'): 'darwin-x86', - ('Darwin', '64bit'): 'darwin-x86', - ('Windows', '32bit'): 'win32-x86', - ('Windows', '64bit'): 'windows-x64', # Not quite yet! - } - bits = platform.architecture()[0] - epics_host_arch = system_map[(system, bits)] return os.path.join(epics_base, 'lib', epics_host_arch) diff --git a/cothread/pv.py b/cothread/pv.py index 07fd575..640c8ec 100644 --- a/cothread/pv.py +++ b/cothread/pv.py @@ -28,21 +28,32 @@ class PV(object): '''PV wrapper class. Wraps access to a single PV as a persistent object with simple access methods. Always contains the latest PV value. - WARNING! This API is a work in progress and will change in future releases + WARNING! This API is a work in progress and may change in future releases in incompatible ways.''' - def __init__(self, pv, on_update = None, timeout = 5, **kargs): + def __init__(self, pv, + on_update = None, initial_value = None, caput_wait = False, + initial_timeout = (), **kargs): + assert isinstance(pv, str), 'PV class only works for one PV at a time' self.name = pv - self.__event = cothread.Event() - self.__value = None + self.__value_event = cothread.Event() + self.__sync = cothread.Event(auto_reset = False) + self.__value = initial_value + self.caput_wait = caput_wait + self.datatype = kargs.get('datatype', None) + self.format = kargs.get('format', catools.FORMAT_RAW) + + self.__deadline_set = initial_timeout != () + assert initial_value is None or not self.__deadline_set, \ + 'Cannot specify initial value and initial timeout' + if self.__deadline_set: + self.__deadline = cothread.AbsTimeout(initial_timeout) + self.on_update = on_update self.__monitor = catools.camonitor( pv, _WeakMethod(self, '_on_update'), **kargs) - self.on_update = on_update - - self.__deadline = cothread.AbsTimeout(timeout) def __del__(self): self.close() @@ -52,32 +63,43 @@ def close(self): def _on_update(self, value): self.__value = value - self.__event.Signal(value) + self.__value_event.Signal(value) + self.__sync.Signal() if self.on_update: self.on_update(self) + def sync(self, timeout = ()): + '''Blocks until at least one update has been seen.''' + if timeout == (): + assert self.__deadline_set, 'Must specify sync timeout' + timeout = self.__deadline + catools.ca_timeout(self.__sync, timeout, self.name) + def get(self): '''Returns current value.''' - if self.__value is None: - return self.get_next(self.__deadline) - else: - return self.__value + if self.__value is None and self.__deadline_set: + catools.ca_timeout(self.__sync, self.__deadline, self.name) + return self.__value def get_next(self, timeout = None, reset = False): '''Returns current value or blocks until next update. Call .reset() first if more recent value required.''' if reset: self.reset() - return self.__event.Wait(timeout) + return catools.ca_timeout(self.__value_event, timeout, self.name) def reset(self): '''Ensures .get_next() will block until an update occurs.''' - self.__event.Reset() + self.__value_event.Reset() def caput(self, value, **kargs): + kargs.setdefault('wait', self.caput_wait) + kargs.setdefault('datatype', self.datatype) return catools.caput(self.name, value, **kargs) def caget(self, **kargs): + kargs.setdefault('datatype', self.datatype) + kargs.setdefault('format', self.format) return catools.caget(self.name, **kargs) value = property(get, caput) @@ -92,7 +114,8 @@ class PV_array(object): in incompatible ways.''' def __init__(self, pvs, - dtype = float, count = 1, on_update = None, **kargs): + dtype = float, count = 1, on_update = None, caput_wait = False, + **kargs): assert not isinstance(pvs, str), \ 'PV_array class only works for an array of PVs' @@ -101,6 +124,7 @@ def __init__(self, pvs, self.on_update = on_update self.dtype = dtype self.count = count + self.caput_wait = caput_wait if count == 1: self.shape = len(pvs) @@ -156,6 +180,7 @@ def sync(self, timeout = 5, throw = True): self._update_one(value, index) def caput(self, value, **kargs): + kargs.setdefault('wait', self.caput_wait) return catools.caput(self.names, value, **kargs) value = property(get, caput) diff --git a/cothread/py23.py b/cothread/py23.py new file mode 100644 index 0000000..4543873 --- /dev/null +++ b/cothread/py23.py @@ -0,0 +1,56 @@ +# Some simple python2/python3 compatibility tricks. +# +# Some of this is drastically reduced from six.py + +import sys +import ctypes + + +# Exception handling +if sys.version_info < (3,): + def raise_from(exception, source): + raise exception + + exec(''' +def raise_with_traceback(result): + raise result[0], result[1], result[2] +''') + +else: + exec(''' +def raise_from(exception, source): + try: + raise exception from source + finally: + exception = None +''') + + def raise_with_traceback(result): + raise result[1].with_traceback(result[2]) + + +# c_char_p conversion +if sys.version_info < (3,): + auto_encode = ctypes.c_char_p + def auto_decode(result, func, args): + return result + def decode(s): + return s + +else: + class auto_encode(ctypes.c_char_p): + @classmethod + def from_param(cls, value): + if value is None: + return value + else: + return value.encode('UTF-8') + + def auto_decode(result, func, args): + if result is None: + return result + else: + return result.decode('UTF-8') + + def decode(s): + return s.decode('UTF-8') diff --git a/cothread/qt.py b/cothread/qt.py new file mode 100644 index 0000000..574a0fa --- /dev/null +++ b/cothread/qt.py @@ -0,0 +1,33 @@ +import sys + + +qts = ['PyQt5', 'PyQt4'] ## ordered by preference + +# check if PyQt alredy imported +QT_LIB = None +for lib in qts: + if lib in sys.modules: + QT_LIB = lib + break + +# if not imported let's try to import any +if QT_LIB is None: + for lib in qts: + try: + __import__(lib) + QT_LIB = lib + break + except ImportError: + pass +if QT_LIB is None: + ImportError("PyQt not found") + +# now some PyQt is imported + +if QT_LIB == 'PyQt5': + from PyQt5 import QtCore, QtWidgets + + +elif QT_LIB == 'PyQt4': + from PyQt4 import QtCore, QtGui + QtWidgets = QtGui diff --git a/cothread/tools/pvtree.py b/cothread/tools/pvtree.py index 2e6c4a9..f303aff 100755 --- a/cothread/tools/pvtree.py +++ b/cothread/tools/pvtree.py @@ -1,4 +1,4 @@ -#!/bin/env dls-python +#!/usr/bin/env python # Simple tool for viewing the chain of PV dependencies. @@ -6,9 +6,9 @@ import sys import re +import os if __name__ == '__main__': - import os sys.path.append( os.path.join(os.path.dirname(__file__), '../..')) try: diff --git a/cothread/version.py b/cothread/version.py new file mode 100644 index 0000000..11a4806 --- /dev/null +++ b/cothread/version.py @@ -0,0 +1 @@ +__version__ = '2.15' diff --git a/docrequirements.txt b/docrequirements.txt new file mode 100644 index 0000000..24ce15a --- /dev/null +++ b/docrequirements.txt @@ -0,0 +1 @@ +numpy diff --git a/docs/catools.rst b/docs/catools.rst index 056153c..d09a8ad 100644 --- a/docs/catools.rst +++ b/docs/catools.rst @@ -425,6 +425,13 @@ Functions ``.ok==False`` is returned. +.. function:: cainfo(pvs, timeout=5, throw=True) + + This is an alias for :func:`connect` with `cainfo` and `wait` set to + ``True``. Returns a :class:`ca_info` structure containing information about + the connected PV or a list of structures, as appropriate. + + .. _Values: Working with Values @@ -587,12 +594,13 @@ used to control the type of the data returned: 4. Any :class:`numpy.dtype` compatible with any of the above values. - 5. One of the special values :const:`DBR_CHAR_STR` or - :const:`DBR_CHAR_UNICODE`. This is used to request a char array which - is then converted to a Python string or Unicode string on receipt. It - is not sensible to specify `count` with this option. The option - :const:`DBR_CHAR_UNICODE` is meaningless and not supported - for :func:`caput`. + 5. One of the special values :const:`DBR_CHAR_STR`, + :const:`DBR_CHAR_UNICODE`, or :const:`DBR_CHAR_BYTES`. This is used to + request a char array which is then converted to a Python :class:`str` + :class:`unicode` or :class:`bytes` string on receipt. It is not + sensible to specify `count` with this option. The options + :const:`DBR_CHAR_BYTES` and :const:`DBR_CHAR_UNICODE` are meaningless + and not supported for :func:`caput`. Note that if the PV name ends in ``$`` and `datatype` is not specified then :const:`DBR_CHAR_STR` will be used. @@ -894,13 +902,32 @@ deleted. change in future releases. -.. class:: PV(pv, on_update=None, timeout=5, **kargs) +.. class:: PV(pv, on_update=None, initial_value=None, caput_wait=False, \ + [initial_timeout], **kargs) Creates a wrapper to monitor *pv*. If an *on_update* function is passed it will be called with the class instance as argument after each update to the - instance. The *timeout* is used the first time the class is interrogated to - check whether a connection has been established. The *kargs* are passed - through to the called :func:`camonitor`. + instance. The *kargs* are passed through to the called :func:`camonitor`. + The flag *caput_wait* can be set to change the default behaviour of + :meth:`caput`. + + The behaviour of the first call to :meth:`get` is affected by two arguments, + *initial_value* and *initial_timeout*, at most one of which can be + specified. If *initial_timeout* is specified then the first call to + :meth:`get` will block until this timeout expires or a valid PV value is + available. Otherwise *initial_value* can be set to specify a value to + return until the PV has updated. + + .. note:: + + This is an incompatible change from cothread versions 2.11 and 2.12. In + these versions the *initial_timeout* argument is named *timeout*, + defaults to 5, and cannot be unset. + + Note that blocking on a PV object for the initial update cannot be safely + done from within a camonitor callback, as in this case the blocking + operation is waiting for a camonitor callback to occur, and only one + camonitor callback is processed at a time. .. method:: close() @@ -908,6 +935,13 @@ deleted. Note that it is sufficient to drop all references to the class, it will then automatically call :meth:`close`. + .. method:: sync([timeout]) + + This call will block until the :class:`PV` object has seen at least one + update. If *initial_timeout* was specified in the constructor then its + associated deadline can be used as a default timeout, otherwise a + *timeout* must be specified. + .. method:: get() Returns the current value associated with the PV. This will be the most @@ -941,7 +975,9 @@ deleted. .. method:: caput(value, ** kargs) Directly calls :func:`caput` on the underlying PV with the given - arguments. + arguments. If *caput_wait* was set in the original :class:`PV` + constructor then by default :func:`caput` is called with ``wait=True``, + otherwise :func:`caput` is non blocking. .. attribute:: name @@ -955,14 +991,17 @@ deleted. new_value)``. -.. class:: PV_array(pvs, dtype=float, count=1, on_update=None, **kargs) +.. class:: PV_array(pvs, dtype=float, count=1, on_update=None, \ + caput_wait=False, **kargs) Uses *pvs* to create an aggregate array containing the value of all specified PVs aggregated into a single :mod:`numpy` array. The type of all the elements is specified by *dtype* and the number of points contributed by each PV is given by *count*. If *count* is 1 the generated array is one dimensional of shape ``(len(pvs),)``, otherwise the shape is - ``(len(pvs),count)``. + ``(len(pvs),count)``. The flag *caput_wait* can be set to change the + default behaviour of :meth:`caput`. + At the same time arrays of length ``len(pvs)`` are created for the connection status, timestamp and severity of each PV. @@ -990,7 +1029,11 @@ deleted. .. method:: caput(value, ** args) - Directly calls :func:`caput` on the stored list of PVs. + Directly calls :func:`caput` on the stored list of PVs. If *caput_wait* + was set in the original :class:`PV` constructor then by default + :func:`caput` is called with ``wait=True``, otherwise :func:`caput` is + non blocking. + .. method:: sync(timeout=5, throw=False) diff --git a/docs/conf.py b/docs/conf.py index 66fe0e3..9de59f2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,8 +18,11 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) -from pkg_resources import require -require('numpy') +try: + from pkg_resources import require + require('numpy') +except: + pass sys.path.append(os.path.abspath('..')) # -- General configuration ----------------------------------------------------- @@ -49,7 +52,7 @@ # General information about the project. project = u'Cothread' -copyright = u'2011, Michael Abbott' +copyright = u'2007-2015, Michael Abbott, Diamond Light Source Ltd' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/cothread.rst b/docs/cothread.rst index 06b4f86..ec9d587 100644 --- a/docs/cothread.rst +++ b/docs/cothread.rst @@ -142,10 +142,11 @@ not to suspend): This is always a suspension point. :func:`catools.caput` - This routine will normally cause the caller to suspend. To avoid - suspension, only put to one PV, use ``wait=False``, and ensure that the - channel is already connected -- this will be the case if it has already - been successfully used in any :mod:`catools` method. + This routine may cause the caller to suspend. To avoid suspension, put to + only one PV, use ``wait=False`` (the default), and ensure that the channel + is already connected -- this will be the case if it has already been + successfully used in any :mod:`catools` method. To ensure suspension use + ``wait=True``. The :mod:`cothread.cosocket` module makes most socket operations into suspension points when the corresponding socket operation is not yet ready. @@ -352,6 +353,59 @@ and :class:`EventQueue` objects. A :class:`Pulse` holds no values, an written to the event. +.. class:: RLock() + + The :class:`RLock` is a reentrant lock that can be used to protect access + or modification of variables by two cothreads at the same time. It is + reentrant because once it is acquired by a cothread, that same cothread + may acquire it again without blocking. This same cothread must release it + once for each time it has acquired it. + + It can be used as a context manager to acquire that lock and guarantee that + release will be called even if an exception is raised. For example:: + + lock = RLock() + x, y = 0, 0 + + with lock: + x = 1 + some_function_that_yields_control() + y = 1 + + Now as long as any other function that uses x and y also protects access + with this same lock, x and y will always be in a consistent state. It also + means that some_function_that_yields_control() can also acquire the lock + without causing a deadlock. + + The following methods are supported: + + .. method:: acquire(timeout=None) + + Acquire the lock if necessary and increment the recursion level. + + If this cothread already owns the lock, increment the recursion level + by one, and return immediately. Otherwise, if another cothread owns the + lock, block until the lock is unlocked. Once the lock is unlocked (not + owned by any cothread), then grab ownership, set the recursion level to + one, and return. If more than one thread is blocked waiting until the + lock is unlocked, only one at a time will be able to grab ownership of + the lock. + + .. method:: release() + + Release a lock, decrementing the recursion level + + If after the decrement it is zero, reset the lock to unlocked (not owned + by any cothread), and if any other cothreads are blocked waiting for the + lock to become unlocked, allow exactly one of them to proceed. If after + the decrement the recursion level is still nonzero, the lock remains + locked and owned by the calling cothread. + + Only call this method when the calling cothread owns the lock. An + AssertionError is raised if this method is called when the lock is + unlocked or the cothread doesn't own the lock. + + .. class:: Pulse() Pulse objects have no state and all cothreads waiting on a Pulse object will @@ -509,6 +563,34 @@ and :class:`EventQueue` objects. A :class:`Pulse` holds no values, an ``action()`` should return as soon as possible to avoid blocking subsequent callbacks -- if more work needs to be done, call ``Spawn()``. +.. function:: CallbackResult(action, *args, **kargs, \ + callback_queue=Callback, callback_timeout=None, callback_spawn=True) + + This is similar to :func:`Callback`: this can be called from any Python + thread, and ``action(*args, **kargs)`` will be called in cothread's own + thread. The difference is that the this function will block until + `action` returns, and the result will be returned as the result from + :func:`CallbackResult`. For example, the following can be used to perform + channel access from an arbitrary thread:: + + value = CallbackResult(caget, pv) + + The following arguments are processed by :func:`CallbackResult`, all others + are passed through to `action`: + + `callback_queue` + By default the standard :func:`Callback` queue is used for dispatch to + the cothread core, but a separate callback method can be specified here. + + `callback_timeout` + By default the thread will block indefinitely until `action` completes, + or a specific timeout can be specified here. + + `callback_spawn` + By default a new cothread will be spawned for each callback; this can + help to avoid interlock problems as mentioned above under + :func:`Callback`, but adds overhead. + .. function:: iqt(poll_interval=0.05, run_exec=True, argv=None) diff --git a/docs/training/.gitignore b/docs/training/.gitignore new file mode 100644 index 0000000..005121b --- /dev/null +++ b/docs/training/.gitignore @@ -0,0 +1 @@ +/cothread.html diff --git a/docs/training/Makefile b/docs/training/Makefile new file mode 100644 index 0000000..38e4cff --- /dev/null +++ b/docs/training/Makefile @@ -0,0 +1,18 @@ +RST2S5 = rst2s5.py + +# Need to explicitly point Python to our Pygments install +TOOLS = /dls_sw/prod/tools/RHEL6-x86_64 +LIB_PATH = prefix/lib/python2.7/site-packages +export PYTHONPATH = $(TOOLS)/Pygments/1-4/$(LIB_PATH)/Pygments-1.4-py2.7.egg + +%.html: %.rst + $(RST2S5) $< $@ + +default: cothread.html + +cothread.html: $(wildcard styles/*) + +clean: + rm -r cothread.html + +.PHONY: default clean diff --git a/docs/training/cothread.rst b/docs/training/cothread.rst new file mode 100644 index 0000000..2c6527b --- /dev/null +++ b/docs/training/cothread.rst @@ -0,0 +1,684 @@ +.. include:: + +.. role:: prettyprint + :class: prettyprint + +.. |emdash| unicode:: U+02014 .. EM DASH + + +================================ +Channel Access with ``cothread`` +================================ + +:Author: Michael Abbott + +Documentation: + .. class:: small + + file:///dls_sw/prod/common/python/RHEL6-x86_64/cothread/2-13/docs/html/index.html + + http://www.cs.diamond.ac.uk/docs/docshome/cothread/ + + +Cothread +======== + +The ``cothread`` library provides EPICS "Channel Access" bindings for Python. +The library comprises two parts: + +* ``cothread`` itself: "cooperative threads". + +* ``cothread.catools`` provides channel access bindings. + +Because EPICS involves communication with other machines events may happen at +any time. The ``cothread`` library provides a mechanism for managing these +updates with the minimum of interference with the rest of the program. + + +Cothread catools bindings +========================= + +The EPICS Channel Access Python interface consists of three functions: + +``caget(pvs, ...)`` + Retrieves value from a single PV or a list of PVs. + +``caput(pvs, values, ...)`` + Writes value or values to a single PV or list of PVs. + +``camonitor(pvs, callback, ...)`` + Creates "subscription" with updates every time a PV changes: ``callback`` is + called with a new value every time any listed PV updates. + + +Preliminaries +============= + +Need to import ``cothread``, all of the examples will start with +the following code, 2.13 is the current release: + +.. code:: python + + from pkg_resources import require + require('cothread==2.13') + + import cothread + from cothread.catools import * + +Channel access waveform data is returned as ``numpy`` arrays, so it will be +convenient to include this in our list of imports: + +.. code:: python + + import numpy + + +Example: Printing a PV +====================== + +Calling ``caget`` with a PV name returns the value of the PV. + +.. code:: python + + print caget('SR-DI-DCCT-01:SIGNAL') + +Calling ``caget`` with a list of PV names returns a list of PV values. + +.. code:: python + + bpms = ['SR%02dC-DI-EBPM-%02d:SA:X' % (c+1, n+1) + for c in range(24) for n in range(7)] + sax = caget(bpms) + print sax + +Note that calling ``caget`` with a long list is potentially *much* faster than +calling ``caget`` on each element of the list in turn, as passing a list to +``caget`` allows all fetches to proceed concurrently. + + +Testing Speed of ``caget`` +========================== + +Compare: + +.. code:: python + + import time + start = time.time(); caget(bpms); print time.time() - start + +with + +.. code:: python + + start = time.time(); [caget(bpm) for bpm in bpms]; print time.time() - start + +I tend to get a difference of a factor of around 50 between these two tests. + + +Exercise: Timing test +===================== + +Exercise: put everything in a file and run this test standalone. Try adding +more PVs and making the test controlled by a command line parameter. + + +Shape Polymorphism +================== + +As we've already seen, the behaviour of the three channel access functions +varies depending on whether the first argument is a string or a list of strings, +for example: + +.. code:: python + + print caget('SR-DI-DCCT-01:SIGNAL') + print caget(['SR-DI-DCCT-01:SIGNAL', 'SR-DI-DCCT-01:LIFETIME']) + +Similarly ``caput`` can write to multiple pvs, in which case a single value or a +list of values can be passed. + +.. code:: python + + caput(['TEST:PV1', 'TEST:PV2'], 3) # Writes 3 to both + caput(['TEST:PV1', 'TEST:S'], [10, 'testing']) # Writes different values + + # To write a repeated array to multiple TEST:PVs need to use repeat_value + caput(['TEST:WF1', 'TEST:WF2'], [1, 2], repeat_value = True) + +.. container:: incremental + + Note: this is the *only* case where ``repeat_value=True`` is needed. + + +Example: Monitoring a PV +======================== + +Updates to a PV result in a callback function being called. + +.. code:: python + + def print_update(value): + print value.name, value + + m = camonitor('SR-DI-DCCT-01:SIGNAL', print_update) + cothread.Sleep(10) + m.close() + +Updates arrive in the background until we close the monitor, but for normal +applications we leave the monitor open until the application exits. + + +``camonitor`` for Lists of PVs +============================== + +If ``camonitor`` is passed a list of PVs it expects the update function to take +a second argument which is used as an index. + +.. code:: python + + def print_update(value, index): + print value.name, index, value + + mm = camonitor(bpms, print_update) + cothread.Sleep(1) + for m in mm: + m.close() + +.. container:: incremental + + If the index is not needed there is no particular benefit to calling + ``camonitor`` on lists of PVs, unlike for ``caget`` and (it depends) + ``caput``. + + +Exercise: ``camonitor`` and ``caput`` +===================================== + +Use ``camonitor`` and ``caput`` to monitor ``TEST:PV1`` and add 1 to it after a +couple of seconds. + +Use ``cothread.Sleep(...)`` for sleeping. + +.. Warning:: + + Don't use ``time.sleep(...)`` when using cothread: this will prevent updates + from taking place! + +Use ``cothread.WaitForQuit()`` at the end of your script if there's nothing else +to do while cothread does its work. + + +A note on the last exercise +=========================== + +The obvious answer is to call ``cothread.Sleep`` in the camonitor callback +function, eg: + +.. code:: python + + def do_update(value): + cothread.Sleep(1) + caput(value.name, value + 1) + + m = camonitor('TEST:PV1', do_update) + cothread.Sleep(10) + m.close() + +Unfortunately doing this has the unfortunate side effect of blocking +all other camonitor updates during the ``Sleep``. + +Only one camonitor callback function is processed at a time. If you need to do +long lasting work in response to a PV update, push the processing somewhere else +with ``Spawn`` or ``Event``. + + +Augmented Values +================ + +Values returned by ``caget`` and delivered by ``camonitor`` are "augmented" by +extra information. The following two fields are always present: + +``.name`` + Contains the full name of the requested PV. + +``.ok`` + Will be ``True`` for values fetched without trouble, ``False`` if value is + not really a value! + +For example: + +.. code:: python + + v = caget('SR-DI-DCCT-01:SIGNAL') + print v.name, v.ok, v + + +Augmented Values are Ordinary +============================= + +Note that ``v`` (from above) is an ordinary number: + +.. code:: python + + print isinstance(v, float) + +However, it's not completely ordinary: + +.. code:: python + + print type(v), type(1.0) + +.. container:: incremental + + It will behave just like an ordinary float, but can be made completely + ordinary with the ``+`` operator: + + :: + + print +v, type(+v) + + However you should not normally need to use this! + + +Getting Values with Timestamps +============================== + +It is possible to get timestamp information with a retrieved or monitored PV, +but it needs to be requested: + +.. code:: python + + v = caget('SR-DI-DCCT-01:SIGNAL', format = FORMAT_TIME) + print v.name, v, v.datetime + +The timestamp is also available in raw Unix format (seconds since 1970): + +.. code:: python + + print v.timestamp + + +Example: Gathering Updates +========================== + +We'll monitor a requested number of updates and gather them into a list. For +this example the state is held in the local variables of the ``gather`` +function: note the use of a nested function. + +.. code:: python + + def gather(pv, count): + values = [] + done = cothread.Event() + def update(value): + values.append(value) + if len(values) >= count: + done.Signal() + m = camonitor(pv, update) + done.Wait() + m.close() + return values + + print gather('SR21C-DI-DCCT-01:SIGNAL', 10) + + +Example: Circular Updates Buffer +================================ + +Let's try a different version with a circular buffer. In this case we need a +class because the buffer will remain in existence for longer. + +.. code:: python + + class Gather: + def __init__(self, pv, count): + self.count = count + self.values = [0] * count + self.inptr = 0 + camonitor(pv, self.update) + def update(self, value): + self.values[self.inptr] = value + self.inptr = (self.inptr + 1) % self.count + def get(self): + return self.values[self.inptr:] + self.values[:self.inptr] + + buf = Gather('SR21C-DI-DCCT-01:SIGNAL', 10) + cothread.Sleep(1) + print buf.get() + cothread.Sleep(5) + print buf.get() + +This buffer can now safely be left running and will at any time return the last +``count`` values received. + + +Example: Gathering Arrays +========================= + +Working with ``numpy`` arrays can be much more efficient than working with +Python lists of values: + +.. code:: python + + x = numpy.array(caget(bpms)) + print x.mean(), x.std() + print x - x.mean() + +However, when it matters, timestamp information and other extended attributes +are lost when gathering values into arrays, so if the timestamps are needed a +little more care is required: + +.. code:: python + + rawx = caget(bpms, format = FORMAT_TIME) + x = numpy.array(rawx) + tx = numpy.array([v.timestamp for v in rawx]) + print tx.max() - tx.min() # Check spread of timestamps + + +Example: Default Error Handling +=============================== + +For ``caget`` we only really need to worry about fetching PVs that don't exist, +for ``camonitor`` we may also need to pay attention to PVs becoming +disconnected. The default behaviour of ``cothread`` produces sensible results, +but this can be overridden. + + +This behaviour of raising an exception when ``caget`` and ``caput`` fails is the +best default behaviour, because in routine naive use if a PV is unavailable then +this is an unrecoverable error. However, this isn't always what we want. + + +Adjusting the Timeout +===================== + +.. code:: python + + caget('bogus') + +This raises an exception after five seconds. The timeout can be adjusted with +an explicit argument: + +.. code:: python + + caget('bogus', timeout = 1) + +Alternatively, when fetching very large numbers of PVs through the gateway it +can happen that the default five second timeout isn't long enough. + + +Catching Errors from ``caget`` +============================== + +We can ask ``caget`` to return an error value instead of raising an exception: + +.. code:: python + + v1, v2 = caget(['bogus', 'SR-DI-DCCT-01:SIGNAL'], throw = False) + print v1.ok, v2.ok + +Note that if a pv is not ``ok`` we can't test for things like timestamps: + +.. code:: python + + v = caget('bogus', format = FORMAT_TIME, throw = False) + print v.name, v.ok + print v.datetime + +This raises an exception when trying to interrogate the ``datetime`` field on a +PV that never arrived! + + +Catching Errors from ``caput`` +============================== + +The same applies to ``caput``. We'll try writing to a PV we can write to, one +we can't, and one that doesn't exist: + +.. code:: python + + pvs = ['TEST:PV1', 'bogus', 'SR-DI-DCCT-01:SIGNAL'] + results = caput(pvs, 1, timeout = 1, throw = False) + for result in results: + print result.name, result.ok + print result + +Note that a complete description of the error is in the failing results: in this +case each result is a catchable exception object with a descriptive error +message as its string representation. + + +Cothread and Qt +=============== + +The cothread library relies on cooperative transfer of control between +cothreads, and similarly Qt has its own mechanism of events and notifications. +For cothread and Qt to work together, these two libraries need to be introduced +to each other. + +Fortunately this is easy. Run: + +.. code:: python + + cothread.iqt() + +It's safest to run this before importing any Qt dependent libraries. + +This function will create and return the Qt application object if you need it. + + +Plotting: Preamble +================== + +A certain amount of boilerplate preamble is required to get interactive plotting +working with ``dls-python``. We'll show the complete set: + +.. code:: python + + from pkg_resources import require + require('cothread==2.13') + require('matplotlib') + + import cothread + from cothread.catools import * + + import numpy + + cothread.iqt() + + import pylab + pylab.ion() + + +Plotting: An Example +==================== + +Now we can fetch and plot a waveform: + +.. code:: python + + wfx = caget('SR-DI-EBPM-01:SA:X') + ll = pylab.plot(wfx) + +Now let's make it update continuously: + +.. code:: python + + def update_ll(wfx): + ll[0].set_ydata(wfx) + pylab.draw() + + m = camonitor('SR-DI-EBPM-01:SA:X', update_ll) + +Exercise: Plot both X and Y on the same graph. Hint: ``SR-DI-EBPM-01:BPMID`` +contains a good x-axis. + + +Advanced Topics +=============== + + +Advanced Topic: Cothreads +========================= + +We've already seen cothreads: ``camonitor`` callbacks occur "in the background", +really they occur on a dedicated cothread. + +Cothreads are *cooperative* "threads", which means a cothread will run until it +deliberately relinquishes control. This has advantages and disadvantages: + +Advantage + No locking is required, a cothread will not run when it's not expected. + +Disadvantage + A cothread that won't relinquish control will block all other cothreads. + +Note that Python can't make use of multiple cores. + + +Creating a Cothread +=================== + +Creating a cothread is very easy: just define the function to run in the +cothread and call ``cothread.Spawn``: + +.. code:: python + + running = True + def background(name, sleep=1): + while running: + print 'Hello from', name + cothread.Sleep(sleep) + + cothread.Spawn(background, 'test1') + cothread.Spawn(background, 'test2', sleep = 2) + + cothread.Sleep(10) + running = False + + cothread.Sleep(3) + + +Communicating with Cothreads +============================ + +Communicate using event objects and queues: + +``cothread.Event()`` + Creates an event object which is either ready or not ready. When it's ready + it has a value. + +``cothread.EventQueue()`` + Almost exactly like an event object, but multiple values can be waiting. + +Both objects support two methods: + +event\ ``.Signal(value)`` + Makes event object ready with given value. + +event\ ``.Wait(timeout=None)`` + Suspends cothread until object is ready, consumes and returns value. + + +Example: Using ``Event`` +======================== + +.. code:: python + + class PV: + def __init__(self, pv): + self.event = cothread.Event() + camonitor(pv, self.__on_update, format = FORMAT_TIME) + def __on_update(self, value): + self.event.Signal(value) + def get(self, timeout=None): + return self.event.Wait(timeout) + + import time + pv = PV('SR21C-DI-DCCT-01:SIGNAL') + for n in range(5): + v = pv.get() + print v.timestamp, time.time() + cothread.Sleep(1) + +Note that we always get the latest value, even though the PV updates at 5Hz. + +Cothread already implements a fuller featured version of this class available as +``cothread.pv.PV``, and another variant ``cothread.pv.PV_array``. + + +Cothread Suspension Points +========================== + +The following functions are cothread suspension points (where control can be +yielded to another cothread): + +* ``Sleep()``, ``SleepUntil()``, ``Yield()`` +* event\ ``.Wait()`` +* ``caget()`` +* ``caput()`` most of the time (see documentation to avoid suspension). + +The following cothread modules provide extra cothread aware suspension points, +see documentation for details: + +* ``cothread.coselect``: provides ``select()`` and ``poll`` functionality. +* ``cothread.cosocket``: provides cothread aware socket API. + + +Cothreads and Real Threads +========================== + +Python threads are created with the ``threading.Thread`` class. A Python thread +cannot safely call cothread methods ... except for ``cothread.Callback()``, +which arranges for its argument to be called in a cothread: + +.. code:: python + + def callback_code(n): + print 'cothread tick', n + + import time + def thread_code(count): + for n in range(count): + print 'thread tick', n + cothread.Callback(callback_code, n) + time.sleep(0.5) + + import thread + thread.start_new_thread(thread_code, (5,)) + cothread.Sleep(5) + + +Slightly more Realistic Version +=============================== + +.. code:: python + + def consumer(event): + while True: + n = event.Wait() + print 'consumed', n + + import time + def producer(event, count): + for n in range(count): + print 'thread tick', n + # event.Signal(n) + cothread.Callback(lambda n: event.Signal(n), n) + time.sleep(0.5) + + import thread + event = cothread.Event() + cothread.Spawn(consumer, event) + thread.start_new_thread(producer, (event, 5)) + cothread.Sleep(5) + +Try replacing the ``Callback`` call with a direct ``Signal`` call and see what +happens. + +Bonus question: what's wrong with this? + +.. code:: python + + cothread.Callback(lambda: event.Signal(n)) diff --git a/docs/training/docutils.conf b/docs/training/docutils.conf new file mode 100644 index 0000000..a2b3b29 --- /dev/null +++ b/docs/training/docutils.conf @@ -0,0 +1,8 @@ +# Docutils configuration + +[html4css1 writer] +stylesheet-path: ./styles/html4css1.css,styles/pygments.css +embed-stylesheet: no + +[s5_html writer] +theme-url: ./styles diff --git a/docs/training/ioc.db b/docs/training/ioc.db new file mode 100644 index 0000000..71ce7fd --- /dev/null +++ b/docs/training/ioc.db @@ -0,0 +1,23 @@ +record(ai, "TEST:PV1") +{ +} + +record(ai, "TEST:PV2") +{ +} + +record(stringin, "TEST:S") +{ +} + +record(waveform, "TEST:WF1") +{ + field(NELM, 10) + field(FTVL, "FLOAT") +} + +record(waveform, "TEST:WF2") +{ + field(NELM, 10) + field(FTVL, "FLOAT") +} diff --git a/docs/training/styles/blank.gif b/docs/training/styles/blank.gif new file mode 100644 index 0000000000000000000000000000000000000000..75b945d2553848b8b6f41fe5e24599c0687b8472 GIT binary patch literal 49 zcmZ?wbhEHbWMp7unE0RJ|Ns9C3=9Vj8~~DvKUo+V7?>DzfNY>Fh|Ltj$Y2csQN9XW literal 0 HcmV?d00001 diff --git a/docs/training/styles/dlsfooterpad.png b/docs/training/styles/dlsfooterpad.png new file mode 100644 index 0000000000000000000000000000000000000000..ead60954ef720a7c9978531b0b62542f7e973133 GIT binary patch literal 165745 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