How to invoke a long running command

To initiate the LRC you need to call the corresponding Tango command which returns a result code indicating whether the command was accepted, and a unique command id (if the command was not rejected). This command id should be stored and used to keep track of the LRC’s progress and eventual result. To be notified when the LRC finishes you must subscribe to CHANGE_EVENT’s from the longRunningCommandResult attribute, and use this command id to determine if the event corresponds to your command.

If the LRC implements progress reporting, you can also optionally subscribe to the longRunningCommandProgress attribute to receive progress updates, filtering by command id in the same way. For example, to invoke the On LRC using a tango.DeviceProxy and wait for the result, you would do the following:

def invoke_lrc_example(device: tango.DeviceProxy) -> ResultCode:
    """ An example function invoking the On LRC on device.

    :return: ResultCode.OK if the command succeed, ResultCode.FAILED
      otherwise.
    """

    # Initiate the LRC
    try:
        ret, command_id = device.On()
    except DevFailed as ex:
        # handle error
        return ResultCode.FAILED

    if ret == ResultCode.REJECTED:
        # handle rejection
        return ResultCode.FAILED

    # Subscribe to the "longRunningCommandResult" attribute to get notified
    # when the LRC has completed

    done = threading.Event()
    result = []
    def callback(event):
        if not event.err:
            id = event.attr_value.value[0]
            # Compare the command id in the callback with the unique id
            # returned to us when we invoked the LRC
            if id == command_id:
                for r in json.loads(event.attr_value.value[1]):
                    result.append(r)
                done.set()

    device.subscribe_event(
        "longRunningCommandResult",
        tango.EventType.CHANGE_EVENT,
        callback)

    if not done.wait(timeout=10):
        # handle timeout
        return ResultCode.FAILED

    # result has been populated here as done is set
    if ResultCode(result[0]) != ResultCode.OK:
        # handle failure
        return ResultCode.FAILED

    return ResultCode.OK

Warning

The above example has a subtle race condition, the LRC could complete (and update) longRunningCommandResult before the Tango command which initiated the LRC has completed. If another LRC also finishes and overwrites the longRunningCommandResult attribute we may never see the result for our command.

The issue is that we do not know the command ID until the Tango command has returned.

The solution is to subscribe to the CHANGE_EVENT before we invoke the initiating Tango command, and queue up any events we receive before we know the command ID. Once we have received the command ID, we can look through the queue to check if any of the events are for our command.

In practice, this race condition is unlikely to manifest as it requires multiple long running commands to complete very quickly, so for now we recommend to not worry about this issue.

A future release of ska-tango-base will include a function for invoking LRCs which does not suffer from this race condition.