Skip to content

fix(executor): the failure commit is retried like the success commit (#641) - #645

Merged
oluciano merged 4 commits into
developfrom
fix/641-failure-commit-retry
Oct 19, 2026
Merged

oluciano merged 4 commits into
developfrom
fix/641-failure-commit-retry

Conversation

@oluciano

Copy link
Copy Markdown
Owner

Summary

The commit of a failed (or deferred) job was a single call. If it threw, the dead-letter handler was skipped and the dispatcher logged Unhandled error while executing job. Both commits now share one helper (TryCommitAsync, same three retries, about 2.6 s). When the retries run out the job stays Processing, the executor does not call the dead-letter handler, and the orphan watcher decides, as after a crash. All four branches that use CommitFailureAsync benefit: job failure, foreign job, throttle deferral and shutdown requeue.

Type of change

  • Bug fix

Red-first proof

  • Commit 28709e4, run against the unfixed code: the four new JobExecutorStorageErrorTests failed with the storage exception escaping ExecuteJobAsync (Did not expect any exception ... but found System.InvalidOperationException: commit down; blip in the retry cases).
  • Commit eb800a8 reworked the shared reliability scenario. Without the fix on Postgres: Expected plan.Injected to be 3 ... but found 1 and to be 4 ... but found 1. Guard, already green: 0 failures.
  • With the fix: CommitFailureTests 24 passed across PostgreSQL, SQL Server, MongoDB and Redis.

Verification Checklist

  • dotnet format --verify-no-changes passed
  • dotnet build -c Release passed, 0 warnings
  • NexJob.Tests 1206 passed on net8.0 and net10.0; core integration subset 154 passed on both
  • 3N: N1 fails once then saves (handler once), N2 always fails (no throw, 4 calls, no handler), N3 foreign-job branch retries and an empty delay list is a single call
  • No assertion removed or weakened: the only removed test lines are the signature and CommitRetryDelays line of the Build helper, which gained an optional parameter
  • No storage interface or public API changed; protected core file changed (src/NexJob/Internal/JobExecutor.cs) by the core owner
  • DocsTruth --strict: 0 drift; delivery-guarantees has a row for the failure commit
  • CHANGELOG.md updated under [Unreleased] / Fixed

Notes

  • The reliability scenario cannot observe the orphan watcher's dead-letter in the 4-failure case: the fault-injecting decorator hides IOrphanedJobReporter from the watcher. That path is covered by OrphanScenarios.
  • The new row in delivery-guarantees.md is tagged (v6.6.1), the milestone of fix(executor): a failed commit of a job failure is not retried and skips the dead-letter dispatch #641; change it if the release is numbered differently.
  • Batch acknowledgment (EnableBatchAcknowledgment) is unchanged and its flush failure policy was not part of this issue.

Closes #641

…t, red (#641)

Red (unit, JobExecutorStorageErrorTests): FailureCommitFailsOnce_IsRetried_AndDeadLetterRunsOnce, FailureCommitAlwaysFails_RetriesWithinBudget_NoThrow_NoDeadLetter, ForeignJobCommitFailsOnce_IsRetried_WithRefund and FailureCommit_WithNoRetryDelays_IsASingleCall_AndDoesNotThrow fail with the storage exception escaping ExecuteJobAsync.
Red (Postgres, CommitFailureScenarios): failures 3 and 4 never reach the dead-letter handler within 40 s. Guard already green: failures 0.
…atcher's dead-letter (#641)

The fault-injecting decorator hides IOrphanedJobReporter from the orphan watcher, so the watcher's dead-letter cannot be observed here (OrphanScenarios covers it). Red without the fix: Postgres failures 3 and 4 find plan.Injected 1.
…641)

CommitFailureAsync was a single call: a storage error at the moment a job failed skipped the dead-letter dispatch and reached the dispatcher as an unhandled error. Both commits now share TryCommitAsync (same delays). When the retries are exhausted the job stays Processing, the dead-letter dispatch is skipped and the orphan watcher decides.
@sonarqubecloud

Copy link
Copy Markdown

@oluciano
oluciano merged commit d4d2049 into develop Oct 19, 2026
39 checks passed
@oluciano
oluciano deleted the fix/641-failure-commit-retry branch October 19, 2026 17:54
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.

fix(executor): a failed commit of a job failure is not retried and skips the dead-letter dispatch

1 participant