Skip to content

Commit 7668e7a

Browse files
Prevent Eiger from being disarmed multiple times at once (#1719)
* Eiger changes from i04 beamline * Add test for two eiger stops being called at the same time * Revert async arming changes * Fix tests --------- Co-authored-by: Dominic Oram <[email protected]>
1 parent b539ac0 commit 7668e7a

File tree

2 files changed

+39
-18
lines changed

2 files changed

+39
-18
lines changed

src/dodal/devices/eiger.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,12 @@ def set(self, value, *, timeout=None, settle_time=None, **kwargs):
6464
arming_status = Status()
6565
arming_status.set_finished()
6666

67-
disarming_status = Status()
68-
disarming_status.set_finished()
69-
7067
def __init__(self, beamline: str = "i03", *args, **kwargs):
7168
super().__init__(*args, **kwargs)
7269
self.beamline = beamline
7370
# using i03 timeouts as default
7471
self.timeouts = AVAILABLE_TIMEOUTS.get(beamline, AVAILABLE_TIMEOUTS["i03"])
72+
self.disarming_status = None
7573

7674
@classmethod
7775
def with_params(
@@ -106,6 +104,7 @@ def set_detector_parameters(self, detector_params: DetectorParams):
106104
raise Exception("\n".join(errors))
107105

108106
def async_stage(self):
107+
self.disarming_status = None
109108
self.odin.nodes.clear_odin_errors()
110109
status_ok, error_message = self.odin.wait_for_odin_initialised(
111110
self.timeouts.general_status_timeout
@@ -170,22 +169,31 @@ def unstage(self) -> bool:
170169
def stop(self, *args):
171170
"""Emergency stop the device, mainly used to clean up after error."""
172171
LOGGER.info("Eiger stop() called - cleaning up...")
173-
if not self.disarming_status.done:
172+
if self.disarming_status and not self.disarming_status.done:
174173
LOGGER.info("Eiger still disarming, waiting on disarm")
175174
self.disarming_status.wait(self.timeouts.arming_timeout)
175+
elif not self.disarming_status:
176+
self.disarming_status = Status()
177+
try:
178+
self.wait_on_arming_if_started()
179+
stop_status = self.odin.stop()
180+
self.odin.file_writer.start_timeout.set(1).wait(
181+
self.timeouts.general_status_timeout
182+
)
183+
self.disarm_detector()
184+
stop_status &= self.disable_roi_mode()
185+
LOGGER.info("Waiting on stop status")
186+
stop_status.wait(self.timeouts.general_status_timeout)
187+
# See https://github.com/DiamondLightSource/hyperion/issues/1395
188+
LOGGER.info("Turning off Eiger dev/shm streaming")
189+
self.odin.fan.dev_shm_enable.set(0).wait(
190+
self.timeouts.general_status_timeout
191+
)
192+
LOGGER.info("Eiger has successfully been stopped")
193+
finally:
194+
self.disarming_status.set_finished()
176195
else:
177-
self.wait_on_arming_if_started()
178-
stop_status = self.odin.stop()
179-
self.odin.file_writer.start_timeout.set(1).wait(
180-
self.timeouts.general_status_timeout
181-
)
182-
self.disarm_detector()
183-
stop_status &= self.disable_roi_mode()
184-
stop_status.wait(self.timeouts.general_status_timeout)
185-
# See https://github.com/DiamondLightSource/hyperion/issues/1395
186-
LOGGER.info("Turning off Eiger dev/shm streaming")
187-
self.odin.fan.dev_shm_enable.set(0).wait()
188-
LOGGER.info("Eiger has successfully been stopped")
196+
LOGGER.info("Already disarmed, doing nothing")
189197

190198
def disable_roi_mode(self):
191199
return self.change_roi_mode(False)
@@ -194,13 +202,13 @@ def enable_roi_mode(self):
194202
return self.change_roi_mode(True)
195203

196204
def change_roi_mode(self, enable: bool) -> StatusBase:
205+
LOGGER.info(f"Changing ROI mode to {enable}")
197206
assert self.detector_params is not None
198207
detector_dimensions = (
199208
self.detector_params.detector_size_constants.roi_size_pixels
200209
if enable
201210
else self.detector_params.detector_size_constants.det_size_pixels
202211
)
203-
204212
status = self.cam.roi_mode.set(
205213
1 if enable else 0, timeout=self.timeouts.general_status_timeout
206214
)
@@ -216,7 +224,6 @@ def change_roi_mode(self, enable: bool) -> StatusBase:
216224
status &= self.odin.file_writer.num_col_chunks.set(
217225
detector_dimensions.width, timeout=self.timeouts.general_status_timeout
218226
)
219-
220227
return status
221228

222229
def set_cam_pvs(self) -> AndStatus:
@@ -296,6 +303,7 @@ def set_mx_settings_pvs(self):
296303
self.detector_params.omega_increment,
297304
timeout=self.timeouts.general_status_timeout,
298305
)
306+
299307
return status
300308

301309
def set_detector_threshold(self, energy: float, tolerance: float = 0.1) -> Status:

tests/devices/test_eiger.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# type: ignore # Eiger will soon be ophyd-async https://github.com/DiamondLightSource/dodal/issues/700
2+
import asyncio
23
import threading
34
from unittest.mock import ANY, MagicMock, Mock, call, create_autospec, patch
45

@@ -724,3 +725,15 @@ def test_given_eiger_is_disarming_when_eiger_is_stopped_then_wait_for_disarming_
724725

725726
disarming_status.wait.assert_called_once()
726727
fake_eiger.disarm_detector.assert_not_called()
728+
729+
730+
async def test_multiple_stops_disarms_eiger_once(fake_eiger: EigerDetector):
731+
fake_eiger.disarming_status = None
732+
fake_eiger.disarm_detector = MagicMock()
733+
734+
async def do_stop():
735+
await asyncio.sleep(0.01)
736+
fake_eiger.stop()
737+
738+
await asyncio.gather(do_stop(), do_stop())
739+
fake_eiger.disarm_detector.assert_called_once()

0 commit comments

Comments
 (0)