Skip to content

Retry, dependency gating, and richer AsyncResult tracking for Queueable#38

Merged
Mateusz7410 merged 7 commits into
mainfrom
feature/async-0002
Jun 8, 2026
Merged

Retry, dependency gating, and richer AsyncResult tracking for Queueable#38
Mateusz7410 merged 7 commits into
mainfrom
feature/async-0002

Conversation

@Mateusz7410

Copy link
Copy Markdown
Collaborator

Summary

  • Retry & backoffretry(n) (capped at 10, throws above), backoff(...) strategies via a new top-level Backoff class, retryOn(...), with QueueableJobSetting__mdt defaults.
  • Dependency gatingdependsOn(Async.after(result|id) / afterPrevious().succeeded()/.failed()/.finished()) skips a job and its dependents when a dependency's outcome doesn't match. Targets are referenced by customJobId.
  • Chain control & failure handling — imperative Async.stopChain() / Async.skipJob(customJobId) from a finalizer; outcome reconciled from FinalizerContext so uncatchable failures are caught; rollbackOnJobExecuteFail fixed (was a no-op on its own — behavior change to that flag).
  • Observability — an AsyncResult__c row per job (ran or skipped) with status, chain id, class name, exception detail, skip reason, retry count, and a DependsOnResult__c self-lookup. New AsyncResultAccess permission set grants read FLS (these fields previously had none).
  • Docs updated (queueable.md, getting-started.md).

Test plan

  • RunLocalTests + coverage, non-namespaced scratch — 133 pass, 93%
  • RunLocalTests + coverage, btcdev-namespaced scratch — 133 pass, 93%
  • Real-org E2E: enqueued a dependency chain via anonymous Apex, polled until the async queue drained, verified Account markers + AsyncResult__c rows (status, exception, skip reason, dependency lookup, shared ChainId)
  • prettier --check passes on all changed files

Notes

  • rollbackOnJobExecuteFail now actually rolls back and lets the chain continue (previously a no-op unless paired with continueOnJobExecuteFail) — the one behavior change to a released flag.
  • Whole-repo prettier:verify reports pre-existing drift on files this branch does not touch (e.g. BatchableManager.cls); left out of scope.

Retry & backoff:
- retry(n), capped at 10 (throws above the cap instead of silently clamping)
- Backoff strategies (fixed / exponential / exponential-with-jitter) as a
  top-level Backoff class; retryOn(...) to scope retried exceptions
  (every exception retried by default), with QueueableJobSetting__mdt defaults

Dependency gating & chain control:
- dependsOn(Async.after(result|customJobId) / afterPrevious() +
  succeeded()/failed()/finished()) skips a job and its dependents when a
  dependency's outcome doesn't match; identity is the auto-generated customJobId
- imperative Async.stopChain() / Async.skipJob(customJobId), usable from a finalizer
- reconcile job outcome from FinalizerContext so uncatchable failures are detected
- fix rollbackOnJobExecuteFail to roll back and continue (was a no-op on its own)

Observability:
- one AsyncResult__c row per job (ran or skipped) with Status, ChainId, ClassName,
  exception detail, skip reason, retry count, and a DependsOnResult self-lookup
- AsyncResultAccess permission set granting read FLS (these fields had none)

Docs updated. RunLocalTests passes at 93% coverage on the non-namespaced and
btcdev-namespaced scratch orgs.
@vercel

vercel Bot commented May 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
async-lib Ready Ready Preview, Comment Jun 8, 2026 12:22pm

Request Review

@Mateusz7410 Mateusz7410 linked an issue May 31, 2026 that may be closed by this pull request
@github-actions

github-actions Bot commented May 31, 2026

Copy link
Copy Markdown

🧪 Apex Test Results

✅ All Tests Passed

==========================================
     APEX TEST EXECUTION SUMMARY
==========================================

📊 Total Tests: 153
✅ Passed: 153
❌ Failed: 0
⏭️  Skipped: 0


🎉 All tests passed successfully!

📦 Download Full Test Results & Logs


📊 Stats: 153 total | ✅ 153 passed | ❌ 0 failed
🤖 Automated comment by Salesforce CI

@codecov

codecov Bot commented May 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.02439% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.16%. Comparing base (c2e94c0) to head (83ccfc2).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
...ce-app/main/default/classes/queue/QueueableJob.cls 95.38% 3 Missing ⚠️
...-app/main/default/classes/queue/QueueableChain.cls 99.59% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #38      +/-   ##
==========================================
+ Coverage   96.52%   99.16%   +2.63%     
==========================================
  Files          14       15       +1     
  Lines         576      956     +380     
==========================================
+ Hits          556      948     +392     
+ Misses         20        8      -12     
Flag Coverage Δ
Apex 99.16% <99.02%> (+2.63%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Mateusz7410 Mateusz7410 linked an issue May 31, 2026 that may be closed by this pull request
6 tasks
Let jobs classify failures per-exception and reset transient state before
a retry, addressing cases where type-only filtering and shallow cloning fall
short.

- isRetryable(Exception): overridable veto evaluated where the live exception
  exists (catch site for handled failures, finalizer getException() for
  uncatchable ones). AND-composed with retryOn(types): both gates must pass.
- resetForRetry(): overridable hook run on the retry clone to recreate or clear
  transient state (e.g. a Unit of Work) that a shallow clone would otherwise
  carry over.
- Replace failedExceptionType/Message strings with a FailureInfo value; the
  retry decision is now a stored boolean, the captured metadata is audit-only.
- Null-exception fallback: retry only when no retryOn filter is set; a throwing
  override is treated as not-retryable and recorded in RetryHistory.
…f for packaging

- Bump apiVersion 65.0 -> 66.0 on all 17 Apex class meta files
- Add AsyncTest cases covering retry/dependency/builder/manager edge paths
  (test-run coverage 99%, org-wide 96%); clears the codecov patch gate
- Add Backoff.cls to create-unlocked-package-version.sh TARGET_FILES so it
  globalizes for the namespaced unlocked package (consumers reference it via
  the builder backoff(...) API)
- Use sed -i.bak helper so the script runs on GNU sed (Linux/CI), not just BSD/macOS
- Revert the cloneJob() guidance literal that the public->global pass corrupts
  ('public override' -> 'global override') so consumers get correct syntax
New global retry/dependency-gating API warrants a minor bump; 2.5.0 is already released.
@Mateusz7410 Mateusz7410 merged commit 3a91184 into main Jun 8, 2026
3 checks passed
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.

Add opt-in retry mechanism to QueueableJob` Retry support for failed Apex async jobs (e.g., Queueable)

1 participant