ufo-kit/concert — pull request #332 by tomago (2014-10-07 12:49:24+00:00)
Title: Emergency stop and parameter cancelling

This PR addresses #330 and more safety related issues:

* parameter setting cancellation (both synchronous and asynchronous)
* killing futures on `KeyboardInterrupt`
* aborting device activity
* aborting activity of all devices in the session

## Parameter setting interruption

If there is a `KeyboardInterrupt` while a parameters is being set synchronously, the code is interrupted and if there is a cancelling routine for that parameter available, it is invoked. Simple code interruption wouldn't work because the command for setting something on a device is most probably already executed when we interrupt, thus the effect would be nothing, consider this:

```python
def _set_position(self, position):
self._connection.send(foo + str(position))
# here the interrupt comes
# this is not executed, but we don't care because the device is already moving
self.wait_movedone()
```

So with the current approach, one sets the `_cancel_position` method and it is executed on interruption of the `position` parameter setting, thus e.g. a motor really stops.

## Aborting

### Device

There is an `abort` method now which sets the device to some safe state. The parameter cancellation mentioned above could be implemented by this but in case there are different parameters with different rules for being cancelled the `abort` wouldn't be enough.

### Many devices

1. via `concert.devices.base.abort`:

```python
abort([dev_foo, dev_bar])
```

2. via `concert.session.util.abort`:

```python
# Kill 'em all
abort()
```

## Future interruption

If one calls `join` on a future and interrupts, its execution is killed just like when one calls `cancel`. Moreover, there are cleanup actions possible via `cancel_operation` attribute which are necessary for interrupting futures which set some parameter. This way, the synchronous and asynchronous versions of parameter setting are consistent, e.g.:

```python
concert > motor.position = 1 * q.mm
# KeyboardInterrupt
# _cancel_position is called
concert >

concert > motor.set_position(1 * q.mm).join()
# KeyboardInterrupt
# _cancel_position is called as well
concert >
```

The problem is that with the thread-based futures the execution is not really killed and the `_cancel_position` is called only *after* the whole code in `_set_position` is finished, which might be too late. But since `join` on a normal thread blocks until it is finished no matter what, this is probably unsolvable now.