Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to Cothread v2.15 #4

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include context/*
include README.rst

14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
50 changes: 50 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -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
49 changes: 49 additions & 0 deletions context/_coroutine.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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. */
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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\
Expand Down
14 changes: 14 additions & 0 deletions context/switch-arm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)

Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion context/tests/coco.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/dls_sw/tools/python2.4-debug/bin/python2.4
#!/usr/bin/env python

from __future__ import print_function

Expand Down
2 changes: 1 addition & 1 deletion context/tests/leak.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/dls_sw/tools/python2.4-debug/bin/python2.4
#!/usr/bin/env python

from __future__ import print_function

Expand Down
1 change: 1 addition & 0 deletions cothread/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion cothread/cadef.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@

import ctypes
from .load_ca import libca
from . import py23



Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
Loading