diff --git a/ophyd/status.py b/ophyd/status.py index 99f8896e0..436de87d2 100644 --- a/ophyd/status.py +++ b/ophyd/status.py @@ -114,10 +114,13 @@ def __init__(self, *, timeout=None, settle_time=0, done=None, success=None): DeprecationWarning, ) - self._callback_thread = threading.Thread( - target=self._run_callbacks, daemon=True, name=self._tname - ) - self._callback_thread.start() + if timeout is None: + self._callback_thread = None + else: + self._callback_thread = threading.Thread( + target=self._run_callbacks, daemon=True, name=self._tname + ) + self._callback_thread.start() if done: if success: @@ -326,6 +329,9 @@ def set_exception(self, exc): self._exception = exc self._settled_event.set() + if self._callback_thread is None: + self._run_callbacks() + def set_finished(self): """ Mark as finished successfully. @@ -344,10 +350,21 @@ def set_finished(self): # same thread. This just sets an Event, either from this thread (the # one calling set_finished) or the thread created below. if self.settle_time > 0: - threading.Timer(self.settle_time, self._settled_event.set).start() + if self._callback_thread is None: + + def settle_done(): + self._settled_event.set() + self._run_callbacks() + + threading.Timer(self.settle_time, settle_done).start() + else: + threading.Timer(self.settle_time, self._settled_event.set).start() else: self._settled_event.set() + if self._callback_thread is None: + self._run_callbacks() + def _finished(self, success=True, **kwargs): """ Inform the status object that it is done and if it succeeded. diff --git a/ophyd/tests/test_status.py b/ophyd/tests/test_status.py index 775b27cbb..75dc71558 100644 --- a/ophyd/tests/test_status.py +++ b/ophyd/tests/test_status.py @@ -445,6 +445,29 @@ def test_status_timeout_with_settle_time(): st.wait(2) +def test_status_timeout_infinite_with_settle_time(): + """ + When no timeout is specified, a "_run_callbacks" thread is not started, + it is called upon termination of the status ("set_finished" or exception). + However, if settle_time is given the callbacks will be called only + after the settle time has expired (from a timer thread). + """ + cb = Mock() + st = StatusBase(settle_time=1) + st.add_callback(cb) + + # there is no timeout, explicitely set finished ; + # the callback should be called after "settle_time" + st.set_finished() + + assert cb.call_count == 0 + with pytest.raises(WaitTimeoutError): + # not ready yet + st.wait(0.5) + st.wait(0.6) + cb.assert_called_once() + + def test_external_timeout(): """ A TimeoutError is raised, not StatusTimeoutError or WaitTimeoutError,