Skip to content

fix(isotp): fault a waiting receive when the service goes, guard a send after dispose, validate the protocol timers - #261

Merged
dborgards merged 11 commits into
mainfrom
fix/isotp-dispose-and-timer-validation
Oct 3, 2026
Merged

dborgards merged 11 commits into
mainfrom
fix/isotp-dispose-and-timer-validation

Conversation

@dborgards

Copy link
Copy Markdown
Owner

What does this change?

Closes three ISO-TP defects from the 2026-09-30 deep review (I6, I7, I8; mechanism and file/line are in the review). A receive waiting on a channel whose shared service is disposed under it now faults instead of waiting for ever, a send can no longer begin on a channel disposed between the check and the post, and non-positive NAs/NBs/NCr are rejected at Open.

Type of change

  • feat — new behaviour (minor release)
  • fix / perf — bug or performance fix (patch release)
  • docs / test / refactor / chore / ci — no release
  • Breaking change (! in the title, plus a BREAKING CHANGE: footer explaining the migration)

Checklist

  • dotnet build CanKit.Pro.sln -c Release succeeds (with -p:CI=true)
  • dotnet test CanKit.Pro.sln -c Release passes (net10.0, --no-build)
  • Public API changes are documented with XML comments — none
  • New behaviour is covered by a test — one per fix, each seen to fail with that fix removed
  • The requirement or ADR this relates to is referenced — FR-TP-016 (lifecycle), FR-RAW-020

Also run: dotnet format --verify-no-changes, dotnet pack + eng/verify-packages.py.

Notes

  • Behaviour change to be aware of: NAs, NBs or NCr equal to zero were accepted before and fired on the next loop iteration; they are now rejected with ArgumentOutOfRangeException, as are negative values (the same call as for J1939-TP's timers in fix(j1939tp): bind BAM steps to their session, keep T3 on a CTS hold, validate timers #258).
  • The I7 test uses an actor double that runs Dispose at exactly the point between the disposed check and the post; the race cannot be hit deterministically any other way.

🤖 Generated with Claude Code

…nd after dispose, validate the protocol timers

Three defects from the 2026-09-30 deep review (I6, I7, I8).

A channel opened over a shared service with leaveOpen outlived the service when its
owner disposed it first. The reader task ended quietly, and a ReceiveAsync waiting on
the inbox waited for ever, while a send failed at once. The channel now puts the reason
(an ObjectDisposedException for the bus service) into the inbox as a fault, raises it on
BackgroundExceptionOccurred and completes the inbox behind it.

Dispose can fall between SendAsync's disposed check and the post of the send to the
actor. Its cleanup is then queued ahead of the send, finds nothing in flight, and the
send that runs afterwards began on a disposed channel: a frame on the bus and, on an
owned actor that is already gone, a call nobody completes. BeginSendOnLoop now has the
guard HandleReceivedFrame already had.

NAs, NBs and NCr are validated where LocalStMin already was: a negative one made the
deadline scheduler throw after the send was on the wire or the reception begun, leaving
it without a deadline. They must be greater than zero.

Each fix has a regression test that fails with that fix removed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes receive and channel-lifecycle semantics (buffered PDUs then fault, timer validation is a breaking config change for zero timers) in core ISO-TP concurrency paths, though behavior is heavily regression-tested.

Overview
Fixes three ISO-TP lifecycle bugs: waiting receives no longer hang when a shared ICanBusService is disposed under an open channel (leaveOpen), sends are rejected on the actor if Dispose wins the race after SendAsync’s upfront check, and NAs / NBs / NCr must be > 0 at open (zero/negative values now throw ArgumentOutOfRangeException).

Receive paths (ReceiveAsync, ReceiveWithArrivalAsync, ReceiveAllAsync) go through new TakeNextAsync: the PDU inbox stays open on bus loss; _inboxLost records the failure, wakes waiters, and throws only after the buffer is drained (with _discarding so a mid-discard empty inbox is not mistaken for end-of-stream). The subscription reader posts EndInboxAfterSubscriptionLoss under _pumpGate so a caller-side pump cannot lose a frame to the failure. In-flight reassembly is withdrawn without enqueueing a fault PDU.

Tests cover multi-waiter faulting, buffered-then-loss ordering, discard races, cancellation without consuming buffered PDUs, subscription faults, and a DisposeOnFirstPostActor harness for the send/dispose race; StarvedReaderBusService gains hooks for subscription end, reader fault, and pump gaps.

Reviewed by Cursor Bugbot for commit 4c3b569. Bugbot is set up for automated code reviews on this repo. Configure here.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-03T06:33:54.276345Z 4c3b569 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89ce021c49

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/CanKit.Pro.IsoTp/IsoTpChannel.cs Outdated
@codecov

codecov Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Review on #261: when the shared service went, the reason was enqueued as a fault item.
In the bounded DropOldest inbox that item could push the oldest finished PDU out, and
with several blocked receivers only one saw the real failure while the others got the
generic completion error. The inbox is now completed with the error: buffered PDUs stay
readable, then every receiver, present and later, gets the ObjectDisposedException. A
reassembly under way is dropped without an item.

Also takes the service in the test under a using, as the code scanner asked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
dborgards and others added 2 commits October 3, 2026 06:19
…y maps its variable

Code scanning (cs/linq/missed-select) on #261.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…hat decided nothing

Codecov on #261. The reader failing, an injected actor that is already gone, and a
reassembly under way when the service goes now have tests; the disposed guard in the
actor callback and the special case in the catch did not change any outcome and are gone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b49889287

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/CanKit.Pro.IsoTp/IsoTpChannel.cs Outdated
Review on #261: DiscardPendingPdus(arrivedBefore) drains the inbox and writes the items from
after its stamp back; once the inbox is completed with the loss that write fails and the
retained PDUs were silently dropped, although the UDS client calls it exactly then. They
now move to a fresh inbox completed with the same failure. The actor in the test is a
using declaration, as code scanning asked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ae8e1fd96

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/CanKit.Pro.IsoTp/IsoTpChannel.cs Outdated

@cursor cursor 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.

Stale Bugbot comment from a previous run.

Reviews on #261: a reader meeting the old, drained and failure-completed inbox in the
middle of a discard threw the loss before the retained PDUs arrived. The writable
replacement is now published first and completed with the failure after the retained items
are in. The discard test waits for the loss to be reported before it discards: it raced the
reader noticing the service went, and the interleaving where the discard came first passed
without exercising the replacement at all.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e43b4bbf08

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/CanKit.Pro.IsoTp/IsoTpChannel.cs Outdated

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e43b4bb. Configure here.

Comment thread src/CanKit.Pro.IsoTp/IsoTpChannel.cs Outdated
…ing it

Reviews on #261: moving the retained PDUs to a replacement inbox after a service loss
needed the handoff serialised against readers and against Dispose, and each fix opened the
next race. The inbox is no longer completed when the service goes (only Dispose completes
it). The failure is recorded and the receivers are woken; a receive takes what is buffered
first and throws the loss only on an empty inbox, for every receiver. DiscardPendingPdus is
back to its original form: the inbox stays open, so what it retains is written back as before.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2aa5c96ba3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/CanKit.Pro.IsoTp/IsoTpChannel.cs Outdated
…retained PDU first

Review on #261: a discard takes the PDUs it retains out of the inbox and writes them back.
After a service loss a receiver meeting the inbox empty in that gap threw the loss ahead of
them. A flag marks the gap; a receiver waits it out and looks once more before it throws.
The streaming receive's end on dispose is covered as well.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 18712a878d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/CanKit.Pro.IsoTp/IsoTpChannel.cs
dborgards and others added 2 commits October 3, 2026 08:01
Review on #261: the receive loop read the inbox before it reached any wait on the token, so
a token that was already canceled still returned a buffered PDU (and the streaming receive
drained them), where the wait on the token threw before. The token is checked before each
read.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… second loop pass

Codecov on #261: the second read was only reachable through a race. As a second pass of the
loop it gives the same guarantee and is run by every receive that ends in the loss.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: adf5b1d932

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/CanKit.Pro.IsoTp/IsoTpChannel.cs Outdated
Review on #261: a caller pumping the subscription (GetReceptionsInProgress, a settle, a
discard) holds the pump lock from taking a frame to posting it; the reader's post of the loss
ran without it and could overtake that frame, so a receiver threw the loss before a PDU that
had been received. The loss is posted under the same lock.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c3b56953a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/CanKit.Pro.IsoTp/IsoTpChannel.cs
@dborgards
dborgards merged commit 76ce1b3 into main Oct 3, 2026
14 checks passed
@dborgards
dborgards deleted the fix/isotp-dispose-and-timer-validation branch October 3, 2026 06:48
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.

2 participants