Skip to content

Actor/Reliability: minor findings and untested paths from the 2026-10-21 deep review (A-2..A-8) #351

Closed

Description

@dborgards

Collected from docs/reviews/2026-10-21-deep-review/actor-reliability.md; file:line against main @ 36c97ca. The Important finding A-1 (second Dispose does not join) has its own issue.

  • A-2 (docs/minor) ClockTimeout is orphaned by actor disposal: FinalDrain discards not-yet-due timers, so the token never cancels and WaitUntilAsync never completes unless the caller's token does (ClockTimeout.cs:16-45). Both consumers link a lifetime token today (UdsClientImpl.cs:374-376, LssMaster.cs:377-390); nothing documents the requirement. Add a <remarks>.
  • A-3 (minor, carry-over) BusStateMonitor.PostRecheck leaves _recheckPending latched when Post throws anything but ObjectDisposedException (BusStateMonitor.cs:235-248); unreachable with ProtocolActor, reachable with a foreign IProtocolActor. Release the gate in a rethrowing catch.
  • A-4 (docs, carry-over) The dispose-timeout TimeoutException is raised on the disposing thread (ProtocolActor.cs:510-520), concurrently with the callback still running; IProtocolActor.cs:65-73 and README.md:78-82 do not say so, and a subscriber written for the loop then mutates actor state from a second thread.
  • A-5 (minor/docs, carry-over) WaitForLoopTask (ProtocolActor.cs:529-542): the catch (AggregateException) is dead code with a false comment (the loop catches its own OperationCanceledException at :593-596); a genuinely faulted loop would be reported as "joined". Delete the catch or route the inner exception through RaiseBackgroundException, and fix the comment.
  • A-6 (minor, unreachable) TickMath.DueAt saturation check ticks >= long.MaxValue - now overflows for a negative now (TickMath.cs:32).
  • A-7 (docs) ProtocolActorTests.cs:154-155 gives a stale reason for WithinTenSeconds (Polyfill provides WaitAsync on every target).
  • A-8 (docs) ProtocolActor.cs:88-115: the <summary> written for TimeSource is attached to RunningLoopCount.
  • Test gaps (reliability-M3/M4/M5 from the previous review, re-run as actor-M4/M5/M6): no FaultOccurred hint test, no throwing-BusState test (the try/finally in PollTick is unexercised), no hint after Dispose (handler detach unexercised). Results of the re-run are in the review's mutation table.
  • Timing: DeadlineTests.Rearm_After_The_Deadline_Already_Resolved_Returns_False is the one wall-clock margin in this area (500 ms real deadline, test-thread stall); move it onto VirtualClock.
  • Open question: FinalDrain fires due timers after the last mailbox item (ProtocolActor.cs:630); every deadline that re-arms in that pass produces an ObjectDisposedException on BackgroundExceptionOccurred during an orderly shutdown. Acceptable noise, or discard due timers like not-yet-due ones?

🤖 Generated with Claude Code

Activity

  1. added
    area: actorCanKit.Pro.Actor — threading and scheduling
    type: bugSomething behaves differently than documented
    area: reliabilityCanKit.Pro.Reliability — deadlines, bus state
    on Oct 21, 2026
  2. dborgards commented on Oct 27, 2026

    @dborgards
    OwnerAuthor

    Compatibility assessment of the remaining shutdown-policy question, against main d80bf71:

    The requested policy is: complete accepted mailbox work, discard every timer callback that has not started once disposal begins. Replacing the current default with this policy is a behavioral breaking change. The published Actor README promises completion of accepted work and specifically discards only not-yet-due scheduled callbacks; FinalDrain deliberately still invokes due timers. Schedule callbacks can complete a caller's task or perform final protocol work. Suppressing them changes that observable result even though no C# signature changes.

    A standalone public-API probe was run for DedicatedThread and ThreadPool against the current Actor assembly (Actor source identical to origin/main). Inside one actor callback it queues an accepted PostAsync, schedules a zero-delay callback, then disposes reentrantly. An external Dispose joins the actor as the positive completion witness. Both modes produce:

    • accepted-mailbox-completed
    • due-timer-fired
    • ObjectDisposedException reported when that timer attempts to re-arm.

    There are no timing sleeps or negative observation windows in this probe. Thus the shutdown noise is reproduced, and removing the due callback would also remove documented user work, not just the exception.

    Per the user's condition “only without a breaking change”, the existing default is left unchanged. Marked BREAKING and left open for a major-version policy change; the previously completed minor fixes are not reimplemented. An additive, explicitly selected shutdown policy could preserve the legacy default, but would be a separate public API/design choice rather than silently changing existing actors. No blanket exception suppression, no PR or release was introduced. Local probe: /private/tmp/cankit-351-shutdown-compat/.

  3. dborgards commented on Oct 27, 2026

    @dborgards
    OwnerAuthor

    The user has now explicitly authorized the additive opt-in alternative while preserving the existing default. Implemented in #488: ActorShutdownPolicy.DiscardPendingTimers through a new three-argument constructor; the old constructor and DrainDueTimers behavior remain unchanged. Removed BREAKING for this compatible scope. Full local gates are green (3125 tests, net48, format, nine packages, engineering and traceability); generated API is additive, 18 changed executable entries have no missing/partials, and eight source mutations failed at runtime/restored. Review/CI/Codecov and the unchanged ten-minute quiet period are still required before the authorized merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: actorCanKit.Pro.Actor — threading and schedulingarea: reliabilityCanKit.Pro.Reliability — deadlines, bus statetype: bugSomething behaves differently than documentedtype: docsDocumentation only

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions