You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Raised at the request of @tomkane-dls, in the spec attached to this comment on #424: introspection is "a larger design problem than it looked and belongs in its own PR". #424 removes it; this issue is where it comes back. Part of the #388 series.
What was there, and is now gone
Connection was generic in T, and connect() returned a value describing the device — for EigerConnection, a DetectorInfo carrying the API version and the tuple of parameter keys the detector reports.
At startup, the runner opened each connection, kept what connect() returned, and passed it to Controller.build() for the controllers holding that connection. build() used it to create structure that cannot be known from config — for Eiger, one attribute per reported parameter key. A controller that did not introspect declared build(self) with no parameter, and the framework inspected the signature to decide whether to pass one.
On every reconnect, the runner called connect() again and compared the new value against the one from startup. If they differed, the device had come back describing itself differently. build() cannot re-run — the controller tree is fixed after startup, and pythonSoftIOC cannot create records after iocInit — so there was no way to accommodate the new shape, and the application exited rather than continuing against a structure that no longer matched the hardware.
The methodology above is an open question, not a plan to re-apply. It is recorded here so nothing is lost.
The open problem
A connection whose existence depends on introspection cannot be declared in fastcs.yaml, because it does not exist at config-parse time. A device reporting seven sub-devices, each needing its own link, has no way to express them.
Supporting that means giving back the things #424 was able to delete:
collecting connections from the tree after the build phase, rather than the runner's list being exactly what the launcher built;
deduplicating them by identity;
opening them mid-walk rather than all up front, in dependency order;
accepting that they take framework defaults and cannot declare depends_on.
No known device at Diamond needs this — the multiplexed alternative, one link with the sub-device as a command parameter, is what temperature_attr.py already does for its ramps.
Where things stand meanwhile
A controller that must ask the device what it has still can, and the Eiger demo still does: build runs with every connection already open, so the walk is ordinary driver code in EigerDetector.build rather than a framework mechanism. What is no longer available is the framework carrying that answer or comparing it on reconnect.
Two smaller pieces would also want revisiting here:
ControllerRunner.fatal_error. The channel is still there, but nothing in the framework sets it now: its one producer was the introspection mismatch. Whatever this issue lands will most likely be its producer again.
The comparison itself.!= requires the result to compare to a single bool, which a dataclass of plain fields does and an array of values does not. If the mechanism returns, that constraint needs stating in the design rather than discovered by a driver.
Questions to settle
Does the framework carry the description at all, or does build simply ask the device itself (as it does today) and introspection become a documented pattern rather than a mechanism?
Raised at the request of @tomkane-dls, in the spec attached to this comment on #424: introspection is "a larger design problem than it looked and belongs in its own PR". #424 removes it; this issue is where it comes back. Part of the #388 series.
What was there, and is now gone
Connectionwas generic inT, andconnect()returned a value describing the device — forEigerConnection, aDetectorInfocarrying the API version and the tuple of parameter keys the detector reports.At startup, the runner opened each connection, kept what
connect()returned, and passed it toController.build()for the controllers holding that connection.build()used it to create structure that cannot be known from config — for Eiger, one attribute per reported parameter key. A controller that did not introspect declaredbuild(self)with no parameter, and the framework inspected the signature to decide whether to pass one.On every reconnect, the runner called
connect()again and compared the new value against the one from startup. If they differed, the device had come back describing itself differently.build()cannot re-run — the controller tree is fixed after startup, and pythonSoftIOC cannot create records afteriocInit— so there was no way to accommodate the new shape, and the application exited rather than continuing against a structure that no longer matched the hardware.The methodology above is an open question, not a plan to re-apply. It is recorded here so nothing is lost.
The open problem
A connection whose existence depends on introspection cannot be declared in
fastcs.yaml, because it does not exist at config-parse time. A device reporting seven sub-devices, each needing its own link, has no way to express them.Supporting that means giving back the things #424 was able to delete:
depends_on.No known device at Diamond needs this — the multiplexed alternative, one link with the sub-device as a command parameter, is what
temperature_attr.pyalready does for its ramps.Where things stand meanwhile
A controller that must ask the device what it has still can, and the Eiger demo still does:
buildruns with every connection already open, so the walk is ordinary driver code inEigerDetector.buildrather than a framework mechanism. What is no longer available is the framework carrying that answer or comparing it on reconnect.Two smaller pieces would also want revisiting here:
ControllerRunner.fatal_error. The channel is still there, but nothing in the framework sets it now: its one producer was the introspection mismatch. Whatever this issue lands will most likely be its producer again.!=requires the result to compare to a single bool, which a dataclass of plain fields does and an array of values does not. If the mechanism returns, that constraint needs stating in the design rather than discovered by a driver.Questions to settle
buildsimply ask the device itself (as it does today) and introspection become a documented pattern rather than a mechanism?Connection.connect()'s return value, or a property on the connection the runner reads (floated in the review of controllers: ControllerRunner, plus native timestamps and severity on attributes #420, and it avoids the two-signatures-for-buildwart)?