Skip to content

OP-02: Run the default ping pipeline for one target

Field Value
Operation ID OP-02
Trigger Invoked once per target from OP-01 step 5; implementers may also call PingAsync directly.
Inputs guid (run id), logger (ISaifLogService, may be null).
Outputs A Task that completes successfully, or faults. In the common case the fault carries the original exception thrown by Ping, but an IsThrowablePingError/OnPingFailure override that itself throws replaces it, and an OnPingSuccess override that throws can fault the task even when Ping succeeded or its failure was swallowed — see Failure and edge-case behavior below.
Primary components IPingableService.PingAsync (default interface implementation)
Entry point IPingableService.PingAsync

Steps

  1. Inside Task.Run, execute Ping(guid, logger); the default implementation is a no-op, and implementers re-implement it to test connectivity or other health signals (BR-07). If an implementer re-implements PingAsync itself instead of Ping, none of these steps apply.*
  2. On exception, call IsThrowablePingError(guid, ex, logger); the default returns true (BR-07).
  3. If throwable, call OnPingFailure(guid, ex, logger) (default no-op); provided OnPingFailure completes without itself throwing, the catch block's bare throw; then rethrows the original exception unchanged to fault the task (BR-07). If OnPingFailure itself throws, that new exception propagates from within the catch block instead, and throw; is never reached — the original exception is not rethrown at all.*
  4. If not throwable, the exception is swallowed and execution falls through to step 5 (BR-08).
  5. Call OnPingSuccess(guid, logger) (default no-op) — this runs both on a clean success and on a swallowed non-throwable failure (BR-08, BR-09).

Process flow

flowchart TD
    A[PingAsync starts in Task.Run] --> B[Execute Ping - default no-op]
    B --> C{Exception thrown?}
    C -->|No| D[OnPingSuccess runs]
    C -->|Yes| E[IsThrowablePingError classifies]
    E --> F{Throwable?}
    F -->|Yes| G["OnPingFailure runs, then rethrow - task faults"]
    F -->|No| H[Exception swallowed]
    H --> D

Failure and edge-case behavior

  • ISaifLogService is only ever passed through to the hooks; the library itself never calls a member on it, so a null logger never causes a failure here.
  • An implementer that overrides PingAsync directly bypasses this entire pipeline, including the IsThrowablePingError/OnPingFailure/OnPingSuccess hook contract.*
  • The task does not necessarily fault with the original Ping exception: if IsThrowablePingError or OnPingFailure itself throws, that new exception propagates instead of the original one, not in addition to it — the throw; statement that would rethrow the original is never reached, so the original exception does not propagate at all once a hook (or the classifier) throws from within the catch block. Similarly, OnPingSuccess runs outside the try/catch entirely, so an override that throws faults the task with that new exception even on a clean success or a swallowed non-throwable failure.

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.

Sources

  • src/SAIF.NetCore.PingableService/SAIF.NetCore.PingableService/IPingableService.cs