fix(devices): fail a running laser delay move when the device is destroyed

This commit is contained in:
wyzula_j committed 2026-10-08 21:30:01 +02:00
1 parent 874845c5f9
commit 700134c495
2 files changed
+38 -5

No files matched your search

+13 -5
View File
@@ -637,15 +637,23 @@ class LaserDelay(PSIDeviceBase, PositionerBase):
def on_stop(self) -> None:
"""Interrupt a running phase ramp between two steps and fail its status."""
self._interrupt_move(DeviceStoppedError(f"Device {self.name} has been stopped"))
def on_destroy(self) -> None:
"""Interrupt a running move and fail its status before the base class kills the task.
The base class shuts the task handler down by raising an exception in the task thread. That
can land anywhere in the task, including after the ramp but before the status is resolved,
so the status is failed here first; whoever waits on it (a scan, ``umv``) is released.
"""
self._interrupt_move(DeviceStoppedError(f"Device {self.name} has been destroyed"))
def _interrupt_move(self, exc: Exception) -> None:
with self._move_lock:
self._move_stop.set()
status = self._move_status
if status is not None:
self._finish_status(status, DeviceStoppedError(f"Device {self.name} has been stopped"))
def on_destroy(self) -> None:
"""Interrupt a running move; the task handler is shut down by the base class."""
self._move_stop.set()
self._finish_status(status, exc)
# ----------------------------------------------------------------------------------------- #
# Internals #
+25
View File
@@ -504,6 +504,31 @@ def test_destroy_ends_a_running_move():
assert not status.success
def test_destroy_fails_the_move_status_even_if_the_task_hangs():
"""destroy() (config reload, device server shutdown) must release whoever waits on the move,
also when the move task is stuck in a slow CA put and the thread kill does not reach it."""
with patched_device(LaserDelay, name="laser_delay", step_sleep=0.0) as device:
set_hardware(device)
device.calibrate(SCOPE)
blocker = threading.Event()
original_put = device.phase_v1._write_pv.put
def hung_put(value, *args, **kwargs):
blocker.wait(timeout=5)
return original_put(value, *args, **kwargs)
device.phase_v1._write_pv.put = hung_put
status = device.set(-4.9)
assert wait_until(device._hw_lock.locked)
with mock.patch.object(device.task_handler, "shutdown"):
device.destroy()
assert status.done and not status.success
with pytest.raises(DeviceStoppedError, match="destroyed"):
status.wait(timeout=0)
blocker.set()
assert wait_until(lambda: not device._hw_lock.locked())
def test_stored_vs_readback_reference_when_voltages_were_touched(calibrated):
calibrated.set(1.0).wait(timeout=10)
# somebody turns the phase shifter by hand