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
fix(jobs): report a timed-out source as Failed, keep a deadline as Cancelled (#240)
* fix(jobs): report a timed-out source as Failed, keep a deadline as Cancelled
The worker's `catch (OperationCanceledException)` was unfiltered, so it also
swallowed an OCE raised by something other than the run's own token — most
commonly HttpClient.Timeout, whose TaskCanceledException carries an internal
token and which the runner deliberately lets past its batch handlers (they
filter on `ex is not OperationCanceledException`). A genuine failure was
recorded as "Cancelled." with its error detail discarded and, because that path
does not rethrow, Hangfire recorded the background job as succeeded.
Filtering on `cancellationToken.IsCancellationRequested` alone would have broken
the documented Deadline contract: a deadline cancels through a LINKED token, so
the caller's token is not cancelled for it either, and the job would have been
downgraded to Failed — which rethrows, handing it to Hangfire's automatic retry
to re-run the whole report up to ten more times. The runner therefore now reports
a deadline as ReportDeadlineExceededException (an OperationCanceledException
subclass, so every existing catch site is unaffected) and the worker treats that,
plus its own token, as cancellation — recording a reason that says which.
Also fixes the same fixed-temp-name collision in both file stores: concurrent
saves of one report's schedule override (or config) shared `{name}.json.tmp` and
failed with a sharing violation or a FileNotFound. Both now go through the shared
AtomicFileWrite, whose temp name is unique per write and is cleaned up on failure.
Both regressions are covered by tests that were each verified to FAIL without
their fix (timeout → Cancelled; deadline → Failed).
* test(core): cover the atomic-write cleanup path
The Sonar gate flagged new_coverage (60.7%): AtomicFileWrite's failure branch —
the one that deletes the unique temp file so a failed save leaves no orphan —
had no test. Exercise it through the public store API: a directory planted where
the store wants its file makes the final move fail after the temp is written.
Also assert a successful save leaves no temp behind.
* style(jobs): address the analyzer findings this PR introduced
S6667 (log in a catch should pass the caught exception) fired on the two
cancellation logs — the deadline warning now has the exception in scope, so pass
it; it also records where the run was when it was cut off. CA1861 (constant array
argument) on the new store assertion. The remaining Sonar issues on
ReportRunner.cs (S107/S125/IDE0008) sit on pre-existing lines this PR never
touched and are left alone.
0 commit comments