Skip to content

Raise Exceptions on Hardware I/O to Set Record Alarms#389

Open
shihab-dls wants to merge 16 commits into
mainfrom
investigate_error_prop
Open

Raise Exceptions on Hardware I/O to Set Record Alarms#389
shihab-dls wants to merge 16 commits into
mainfrom
investigate_error_prop

Conversation

@shihab-dls

@shihab-dls shihab-dls commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Closes #303

This depends on a new release of softioc.

This PR sets and resets alarm states on failed and successful PV puts for CA and PVA.

This addresses the underlying bug in ophyd-async, where a failed command does not raise an error. However, only PVA allows these errors to immediately propagate up to the signal call, otherwise requiring a check on the PV status after the put on CA. Therefore, fastcs-eiger/odin must be converted to use PVA to close the ophyd-async bug.

Summary by CodeRabbit

Summary

  • Bug Fixes
    • Attribute put failures and command executions now properly propagate errors instead of being silently ignored.
    • EPICS Channel Access and PVAccess now clear alarms on success and set major alarms on failures.
    • Failed command/put error messages are more consistent and include exception details.
    • Improved controller sub-item PV key casing for generated raw values.
  • Packaging / Chores
    • Updated formatting and the source used for an optional EPICS-related extra install.
  • Tests
    • Added and expanded coverage for alarm behavior and error propagation across transports.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Attribute write failures now propagate, and EPICS CA/PVA write and command handlers report failures through operation errors and major alarm states. Typed PVA alarm models and datatype-aware posting are added, with tests covering the updated behavior.

Changes

EPICS error handling

Layer / File(s) Summary
Failure contract and test fixtures
pyproject.toml, src/fastcs/attributes/attr_w.py, tests/example_softioc.py, tests/test_attributes.py
AttrW.put() re-raises callback failures; fixtures provide an asynchronously failing command, and assertions verify propagated exceptions.
EPICS CA alarm handling
src/fastcs/transports/epics/ca/ioc.py, tests/transports/epics/ca/test_softioc_system.py
CA writes and commands clear alarms on success and set MAJOR_ALARM on failure.
EPICS PVA value and alarm representation
src/fastcs/transports/epics/pva/types.py, src/fastcs/transports/epics/pva/pvi.py, src/fastcs/transports/epics/pva/_pv_handlers.py
PVA alarm data uses typed models and serialized payloads, with datatype-specific posting and lowercased sub-controller keys.
EPICS PVA operation errors
src/fastcs/transports/epics/pva/_pv_handlers.py, tests/transports/epics/pva/test_p4p.py
Failed writes and blocking commands pass formatted errors to op.done(error=...), post major alarms, and are covered by PVA tests.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

EPICS CA failure flow

sequenceDiagram
  participant Client
  participant EPICS_CA
  participant AttrW
  participant EPICS_Record
  Client->>EPICS_CA: write PV or command PV
  EPICS_CA->>AttrW: put attribute or run method
  AttrW-->>EPICS_CA: exception
  EPICS_CA->>EPICS_Record: set current value and MAJOR_ALARM
Loading

EPICS PVA failure flow

sequenceDiagram
  participant Client
  participant WritePvHandler
  participant AttrW
  participant PVA
  Client->>WritePvHandler: put value
  WritePvHandler->>AttrW: await put
  AttrW-->>WritePvHandler: exception
  WritePvHandler->>PVA: post major alarm
  WritePvHandler-->>Client: op.done(error)
Loading

Suggested reviewers: coretl, gilesknap

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: surfacing hardware I/O failures as exceptions to drive record alarms.
Linked Issues check ✅ Passed The PR implements error propagation for failed put operations on command and write PVs as requested in #303.
Out of Scope Changes check ✅ Passed The dependency bump and formatting updates are incidental to the alarm/error-handling work and do not appear unrelated.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch investigate_error_prop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@shihab-dls
shihab-dls force-pushed the investigate_error_prop branch from 25e95c9 to b0466d2 Compare July 15, 2026 14:06
@shihab-dls shihab-dls changed the title wip: add alarms Raise Exceptions on Hardware I/O to Set Record Alarms Jul 15, 2026
@shihab-dls
shihab-dls force-pushed the investigate_error_prop branch from b0466d2 to b2b5e9b Compare July 15, 2026 14:08
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.52941% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.41%. Comparing base (8c54354) to head (debb24b).

Files with missing lines Patch % Lines
src/fastcs/transports/epics/pva/_pv_handlers.py 96.29% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #389      +/-   ##
==========================================
+ Coverage   91.25%   91.41%   +0.16%     
==========================================
  Files          72       72              
  Lines        2892     2935      +43     
==========================================
+ Hits         2639     2683      +44     
+ Misses        253      252       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shihab-dls
shihab-dls force-pushed the investigate_error_prop branch from e2fb84c to 48791cc Compare July 15, 2026 15:51
@shihab-dls
shihab-dls marked this pull request as ready for review July 15, 2026 16:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/transports/epics/pva/test_p4p.py (1)

215-245: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider using an async def test function.

Since other tests in this file are async (e.g., test_command_method), you can define this test as async def to avoid manually interacting with the event loop. This is more idiomatic and prevents potential RuntimeError issues with get_event_loop() in Python 3.12+.

♻️ Proposed refactor to make the test natively async
-def test_alarms_set_if_put_fails(caplog):
+async def test_alarms_set_if_put_fails(caplog):
     `@dataclass`
     class SimpleAttributeIORef(AttributeIORef):
         pass
 
     class SimpleAttributeIO(AttributeIO[DType_T, SimpleAttributeIORef]):
         async def send(self, attr: AttrW[DType_T, SimpleAttributeIORef], value):
             raise ValueError("Failed")
 
     class SomeController(Controller):
         a = AttrW(Int(), io_ref=SimpleAttributeIORef())
 
     controller = SomeController(ios=[SimpleAttributeIO()])
     pv_prefix = str(uuid4())
     fastcs = make_fastcs(pv_prefix, controller)
 
     async def put_pvs():
         await asyncio.sleep(0.1)
         ctxt = Context("pva")
         await ctxt.put(f"{pv_prefix}:A", 1)
 
-    serve = asyncio.ensure_future(fastcs.serve(interactive=False))
-    try:
-        asyncio.get_event_loop().run_until_complete(
-            asyncio.wait_for(asyncio.gather(serve, put_pvs()), timeout=1)
-        )
-    except RuntimeError as exc:
-        assert str(exc) == "Exception raised during put operation: ValueError('Failed')"
-    serve.cancel()
+    serve = asyncio.create_task(fastcs.serve(interactive=False))
+    try:
+        await asyncio.wait_for(asyncio.gather(serve, put_pvs()), timeout=1)
+    except RuntimeError as exc:
+        assert str(exc) == "Exception raised during put operation: ValueError('Failed')"
+    finally:
+        serve.cancel()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/transports/epics/pva/test_p4p.py` around lines 215 - 245, Convert
test_alarms_set_if_put_fails to an async test and await the concurrent serve and
put_pvs tasks directly, following the pattern used by test_command_method.
Remove the manual get_event_loop().run_until_complete and wait_for usage while
preserving the existing timeout, exception assertion, and serve cancellation
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pyproject.toml`:
- Around line 37-40: Update the epicsca dependency list in pyproject.toml to
replace the softioc Git master reference with a released softioc version
specifier, preserving the existing pvi dependency and ensuring installs use the
stable published package.

In `@src/fastcs/transports/epics/ca/ioc.py`:
- Around line 231-239: Update both write PV and command PV callbacks in
src/fastcs/transports/epics/ca/ioc.py at lines 231-239 and 291-299: bind caught
exceptions explicitly, log each with the existing logger and relevant PV context
before setting the MAJOR_ALARM record state, and avoid blind exception catches.

---

Nitpick comments:
In `@tests/transports/epics/pva/test_p4p.py`:
- Around line 215-245: Convert test_alarms_set_if_put_fails to an async test and
await the concurrent serve and put_pvs tasks directly, following the pattern
used by test_command_method. Remove the manual
get_event_loop().run_until_complete and wait_for usage while preserving the
existing timeout, exception assertion, and serve cancellation behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3502c74e-caf1-455e-af90-0199f1affa9b

📥 Commits

Reviewing files that changed from the base of the PR and between 8c54354 and ed6398c.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • pyproject.toml
  • src/fastcs/attributes/attr_w.py
  • src/fastcs/transports/epics/ca/ioc.py
  • src/fastcs/transports/epics/pva/_pv_handlers.py
  • tests/example_softioc.py
  • tests/test_attributes.py
  • tests/transports/epics/ca/test_softioc_system.py
  • tests/transports/epics/pva/test_p4p.py

Comment thread pyproject.toml
Comment thread src/fastcs/transports/epics/ca/ioc.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/fastcs/transports/epics/pva/_pv_handlers.py (1)

51-64: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not clear alarms before the hardware put succeeds.

The new default p4p_alarm_states() is posted before await self._attr_w.put(cast_value), so an existing major alarm is temporarily cleared while I/O is still pending. A stalled or cancelled operation can leave the PV falsely healthy; post the value/reset alarm only in the success path, or preserve the current alarm until the operation completes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/fastcs/transports/epics/pva/_pv_handlers.py` around lines 51 - 64, Move
the normal value/alarm post in the handler around the put operation so it occurs
only after await self._attr_w.put(cast_value) succeeds. Preserve the existing
alarm state while the put is pending or fails, and keep the exception path’s
error alarm posting and op.done(error=...) behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/fastcs/transports/epics/ca/ioc.py`:
- Around line 234-235: Update the write PV on_update callback at
src/fastcs/transports/epics/ca/ioc.py lines 234-235 to bind the caught exception
and log its details before setting the MAJOR alarm. Apply the same explicit
exception binding and logging in the command PV wrapped_method callback at lines
290-291, preserving the existing alarm behavior.

---

Outside diff comments:
In `@src/fastcs/transports/epics/pva/_pv_handlers.py`:
- Around line 51-64: Move the normal value/alarm post in the handler around the
put operation so it occurs only after await self._attr_w.put(cast_value)
succeeds. Preserve the existing alarm state while the put is pending or fails,
and keep the exception path’s error alarm posting and op.done(error=...)
behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3f2f9465-f2a4-4206-a3bb-21b55a1f826e

📥 Commits

Reviewing files that changed from the base of the PR and between ed6398c and 494eed7.

📒 Files selected for processing (4)
  • src/fastcs/transports/epics/ca/ioc.py
  • src/fastcs/transports/epics/pva/_pv_handlers.py
  • src/fastcs/transports/epics/pva/pvi.py
  • src/fastcs/transports/epics/pva/types.py

Comment thread src/fastcs/transports/epics/ca/ioc.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/fastcs/transports/epics/pva/_pv_handlers.py`:
- Around line 192-199: Update _set_alarm to avoid mutating alarm_states["alarm"]
in place by copying the nested alarm dictionary. Only remove "status" from that
copy for Table or Waveform datatypes, then call pv.post with one positional,
properly nested dictionary containing the value and alarm structure; preserve
the existing alarm_states structure for other datatypes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6e97e013-1f54-4c51-b55d-88e09ab22a43

📥 Commits

Reviewing files that changed from the base of the PR and between 494eed7 and a0be859.

📒 Files selected for processing (1)
  • src/fastcs/transports/epics/pva/_pv_handlers.py

Comment thread src/fastcs/transports/epics/pva/_pv_handlers.py Outdated

@olliesilvester olliesilvester left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not finished the review just yet but some initial comments

Comment thread src/fastcs/transports/epics/pva/_pv_handlers.py Outdated
Comment thread src/fastcs/transports/epics/ca/ioc.py Outdated
Comment thread src/fastcs/transports/epics/ca/ioc.py Outdated
Comment thread src/fastcs/transports/epics/ca/ioc.py
Comment thread src/fastcs/transports/epics/pva/_pv_handlers.py

@olliesilvester olliesilvester left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have permission to actually approve anymore, but I would have

Left a bunch of comments but the fix works, thanks!

Comment thread tests/transports/epics/pva/test_p4p.py
Comment thread tests/transports/epics/pva/test_p4p.py Outdated
Comment thread tests/transports/epics/pva/test_p4p.py Outdated
Comment thread tests/transports/epics/pva/test_p4p.py Outdated
Comment thread tests/example_softioc.py Outdated
Comment thread pyproject.toml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/fastcs/transports/epics/ca/ioc.py`:
- Around line 351-363: The _run_and_set_alarm exception path must preserve
command failures instead of silently swallowing them. Bind the caught exception
and log it with the relevant record/PV context, while retaining the MAJOR_ALARM
update; either narrow the handler to the project-specific hardware-I/O exception
or explicitly justify the deliberate broad CA boundary.

In `@src/fastcs/transports/epics/pva/types.py`:
- Around line 138-151: The scalar record-payload callsites in
cast_to_p4p_value() must serialize the P4PAlarmState returned by
p4p_alarm_states() before merging it into record_fields. Update both numeric
alarm-check and untyped scalar payload paths to dump the model into the nested
{"alarm": {...}} dictionary expected by Value(...), while leaving other payload
handling unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 32fc1095-ca3d-46d1-b075-3a3b7013fcba

📥 Commits

Reviewing files that changed from the base of the PR and between a0be859 and 44b1bec.

📒 Files selected for processing (4)
  • src/fastcs/transports/epics/ca/ioc.py
  • src/fastcs/transports/epics/pva/_pv_handlers.py
  • src/fastcs/transports/epics/pva/pvi.py
  • src/fastcs/transports/epics/pva/types.py

Comment thread src/fastcs/transports/epics/ca/ioc.py
Comment thread src/fastcs/transports/epics/pva/types.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/fastcs/transports/epics/pva/_pv_handlers.py (1)

60-65: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Publish the failure alarm before completing the operation.

op.done(error=...) currently returns the failed put response before _post_with_alarm_states() publishes the major-alarm state, so a client can immediately read/reset the PV with the reset alarm still visible. Post the failure state first, then complete the operation.

Proposed fix
         except Exception as e:
             error_msg = f"Exception raised during put operation: {e!r}"
-            op.done(error=error_msg)
             alarm_states = p4p_alarm_states(Severity.MAJOR, Status.RECORD, error_msg)
             # Raise alarm on failed put
             _post_with_alarm_states(pv, datatype, raw_value, alarm_states)
+            op.done(error=error_msg)

[data_integrity_and_inspection]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/fastcs/transports/epics/pva/_pv_handlers.py` around lines 60 - 65,
Reorder the failed-put handling in the exception path so _post_with_alarm_states
publishes the MAJOR alarm before op.done(error=error_msg) completes the
operation. Keep the existing error message and alarm-state construction
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/transports/epics/pva/test_p4p.py`:
- Around line 233-234: Update the test around Context and the failed put to read
or monitor ${pv_prefix}:A after the operation, then assert the published alarm
severity, status, and message in addition to the existing exception assertion.
Use the test’s established alarm fields and expected MAJOR/error values.
- Around line 236-242: Update the test’s serve task cleanup so cancellation
always occurs in a finally block, including when the assertion or timeout fails.
After calling serve.cancel(), drive the task to completion by awaiting it and
handle the expected cancellation, ensuring the FastCS/PVA server task cannot
leak between tests.
- Around line 231-234: Update put_pvs to wait for a bounded PVA readiness signal
before creating the Context and issuing the put; replace the zero-duration sleep
with the existing server/transport readiness mechanism, or expose one if needed.
Ensure the wait completes only after P4P providers are initialized while
retaining a timeout so startup failures do not hang the test.

---

Outside diff comments:
In `@src/fastcs/transports/epics/pva/_pv_handlers.py`:
- Around line 60-65: Reorder the failed-put handling in the exception path so
_post_with_alarm_states publishes the MAJOR alarm before
op.done(error=error_msg) completes the operation. Keep the existing error
message and alarm-state construction unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0977cde3-5e67-4315-9785-8bff74e8affa

📥 Commits

Reviewing files that changed from the base of the PR and between 44b1bec and 562859d.

📒 Files selected for processing (5)
  • src/fastcs/transports/epics/ca/ioc.py
  • src/fastcs/transports/epics/pva/_pv_handlers.py
  • src/fastcs/transports/epics/pva/types.py
  • tests/example_softioc.py
  • tests/transports/epics/pva/test_p4p.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/example_softioc.py
  • src/fastcs/transports/epics/ca/ioc.py
  • src/fastcs/transports/epics/pva/types.py

Comment thread tests/transports/epics/pva/test_p4p.py
Comment thread tests/transports/epics/pva/test_p4p.py
Comment thread tests/transports/epics/pva/test_p4p.py Outdated
@shihab-dls
shihab-dls force-pushed the investigate_error_prop branch from 0fcb4b3 to 179a49d Compare July 24, 2026 15:26
@shihab-dls

Copy link
Copy Markdown
Contributor Author

@coretl Silvester doesn't have approval permission, so I'm requesting you as a second reviewer.

@shihab-dls
shihab-dls requested a review from coretl July 24, 2026 16:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement op.done(error="msg") for failed put operations on command and write PVs

2 participants