bluesky / ophyd-async

Hardware abstraction for bluesky written using asyncio
https://blueskyproject.io/ophyd-async
BSD 3-Clause "New" or "Revised" License
11 stars 24 forks source link

Cannot connect devices from both ophyd and ophyd-async while a plan is running #548

Closed callumforrester closed 2 weeks ago

callumforrester commented 2 months ago

Issue encountered on I24

If a plan is running and I attempt to connect ophyd and ophyd-async devices within it, the connection freezes, code to reproduce below:

from bluesky.run_engine import RunEngine
from ophyd import Device, Component
from ophyd.signal import EpicsSignalRO
from ophyd_async.core import StandardReadable, DeviceCollector
from ophyd_async.epics.signal import epics_signal_r
import bluesky.plans as bp
from softioc import softioc, builder, asyncio_dispatcher
from multiprocessing.pool import Pool
import signal

def make_ioc() -> None:
    builder.SetDeviceName("FAKEIOC")
    builder.aIn("FOO", initial_value=5.0)
    builder.LoadDatabase()
    softioc.iocInit(asyncio_dispatcher.AsyncioDispatcher())

def run_ioc_in_subprocess() -> None:
    subprocess = Pool(
        initializer=lambda: signal.signal(signal.SIGINT, signal.SIG_IGN), processes=1
    )
    subprocess.apply(make_ioc)

class OphydAsyncDevice(StandardReadable):
    def __init__(self, prefix: str, name: str = "") -> None:
        self.foo = epics_signal_r(float, prefix + "FOO")
        super().__init__(name)

class OphydDevice(Device):
    foo = Component(EpicsSignalRO, "FOO")

    def __init__(self, prefix: str = "", *, name: str, **kwargs):
        super().__init__(prefix, name=name, **kwargs)

RE = RunEngine()

def ophyd_device() -> OphydDevice:
    od = OphydDevice(prefix="FAKEIOC:", name="od")
    od.wait_for_connection()
    print("connected ophyd device")
    return od

def ophyd_async_device() -> OphydAsyncDevice:
    with DeviceCollector():
        oad = OphydAsyncDevice(prefix="FAKEIOC:")
    print("connected ophyd-async device")
    return oad

def my_plan():
    od = ophyd_device()
    oad = ophyd_async_device()
    yield from bp.count([od, oad])
    print("counted devices")

if __name__ == "__main__":
    make_ioc()
    # od = ophyd_device()
    # oad = ophyd_async_device()
    RE(my_plan())

If I comment out these lines in the plan

    od = ophyd_device()
    oad = ophyd_async_device()

...and uncomment them in main, outside where the plan is running, everything works.

I suspect this is more interplay issues between pyepics and aioca. See https://github.com/bluesky/ophyd-async/issues/89 and https://github.com/bluesky/ophyd-async/issues/291. Also I don't think we should be making blocking calls to connect inside a plan in the first place, but this is what dodal potentially does if you have, say, i24.pmac() inside your plan. Ideally we should defer to the RunEngine, maybe with an ensure_connected() plan?

coretl commented 2 months ago

Does it fail in the same way with https://github.com/DiamondLightSource/aioca/tree/contexts?

callumforrester commented 2 months ago

The example above fails in exactly the same way with that branch, yes