Skip to content

feat(functions): add opt-in durable HTTP failure retries - #377

Open
wangbill (YunchuWang) wants to merge 1 commit into
mainfrom
yunchuwang-durable-http-retries
Open

wangbill (YunchuWang) wants to merge 1 commit into
mainfrom
yunchuwang-durable-http-retries

Conversation

@YunchuWang

Copy link
Copy Markdown
Member

Summary

What changed?

  • Add HttpRetryOptions and callHttp({ retryOptions }), snapshotting the policy and status list into the durable request.
  • Reuse the core activity retry engine and durable timers for selected HTTP statuses and activity/transport failures. No second retry engine or core changes.
const retryOptions = new df.HttpRetryOptions(1000, 4);
retryOptions.backoffCoefficient = 2;
retryOptions.maxRetryIntervalInMilliseconds = 10000;
retryOptions.retryTimeoutInMilliseconds = 60000;
retryOptions.statusCodesToRetry = [429, 500, 503];
const response = yield context.df.callHttp({ method: "GET", url, retryOptions });

Why is this change needed?

Previously callHttp returned error status responses without durable failure retries. With explicit options it now retries selected responses, returns excluded statuses, and throws TaskFailedError on exhaustion rather than returning the last failed response.

The reference is the .NET in-process Durable Functions extension, not the generic .NET SDK:

  • HttpRetryOptions: backoff 1, six-day interval cap, unlimited retry timeout by default.
  • TaskHttpActivityShim: omitted/empty status lists use non-2xx semantics (including 4xx/5xx and unfollowed 3xx); explicit lists are exact, including 202.
  • CreateLocationPollRequest does not propagate retry options. This PR matches that boundary: initial requests retry; subsequent 202 Location polls do not. Initial 503 -> 202 -> poll 503 therefore returns the poll's 503 without retrying it.

Omitting options preserves current behavior. Existing polling, redirects, credential stripping, and URL validation are unchanged. README documents JavaScript millisecond/-1 conventions, core retry-timeout boundaries, idempotency considerations, and versioning/draining in-flight instances before enabling retries.

Issues / work items


Project checklist

  • Release notes are not required for the next release
    • Otherwise: Notes added to packages/azure-functions-durable/CHANGELOG.md
  • Backport is not required
    • Otherwise: Backport tracked by issue/PR (not assessed)
  • All required tests have been added/updated (unit tests, E2E tests)
  • Breaking change?
    • If yes:
      • Impact: No behavior change without explicit retry options.
      • Migration guidance: Version or drain in-flight instances before changing their retry behavior.

AI-assisted code disclosure (required)

Was an AI tool used? (select one)

  • No
  • Yes, AI helped write parts of this PR (e.g., GitHub Copilot)
  • Yes, an AI agent generated most of this PR

If AI was used:

  • Tool(s): GitHub Copilot.
  • AI-assisted areas/files: HTTP retry implementation, tests, README, changelog.
  • What you changed after AI output: Agent iterations corrected test-harness issues, added runtime null-list coverage, and type-checked the README example. Human review remains pending.

AI verification (required if AI was used):

  • I understand the code and can explain it
  • I verified referenced APIs/types exist and are correct
  • I reviewed edge cases/failure paths (timeouts, retries, cancellation, exceptions)
  • I reviewed concurrency/async behavior
  • I checked for unintended breaking or behavior changes

Testing

Automated tests

  • Passed: 108 tests across the seven scoped HTTP/context/retry/worker suites, with real loopback HTTP and both protobuf replay and in-memory execution. New behavior was observed failing before implementation.
  • Passed: npm run build -w durable-functions (includes core build), scoped ESLint, Prettier with baseline CRLF, and git diff --check.
  • Passed: strict TypeScript compilation of the README example and normal Husky/lint-staged commit checks.
  • Coverage includes retry/success/exhaustion, excluded/default/explicit-202 statuses, transport errors, snapshot validation, backoff/cap/timeout boundaries, six-day timer segmentation, replay, caller-visible exceptions, and polling interaction.

Manual validation (only if runtime/behavior changed)

  • Environment (OS, Node.js version, components): Windows, Node.js 24.14.0; local loopback HTTP, real Functions worker protocol and in-memory backend.
  • Steps + observed results: Automated scenarios above exercised runtime behavior; no separate manual or cloud execution.
  • Evidence (optional): Local Functions-host/Azurite E2E was not run: prerequisite check reported Azurite unavailable at 127.0.0.1:10000. No environment was provisioned. Full-repository suites, sidecar/Azure tests, and hosted CI are not claimed as passed.

Notes for reviewers

  • The initial-request-only retry boundary is intentional source parity, not per-poll retry support. This is narrower than treating every 202 hop as a fresh retry budget.
  • Human verification checkboxes remain unchecked.

Add HttpRetryOptions and serialize its policy and status-code snapshot for the built-in HTTP activity. Reuse durable RetryPolicy execution, preserve calls without options, and match .NET in-process initial-request-only retry semantics.

Closes #367

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e0af01a5-0dfa-4e71-a660-c4186e65d7e0
Copilot AI balanced review requested due to automatic review settings September 29, 2026 16:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Retriable responses are fully buffered before status evaluation, which can delay or prevent durable retries for large or non-terminating bodies.

Review effort: Balanced
Findings: 1 High severity

Open (1)
What changed in this PR

Adds opt-in durable HTTP retries to classic callHttp, reusing core activity retries and preserving separate 202 polling behavior.

Changes:

  • Adds and exports HttpRetryOptions.
  • Applies durable retries to configured HTTP statuses and transport failures.
  • Documents behavior and adds replay, timeout, polling, and exhaustion tests.
File Description
src/​http/​models.ts Defines HTTP retry options and payload fields.
src/​http/​builtin.ts Applies retry policies to initial HTTP requests.
src/​orchestration-context.ts Snapshots retry configuration into durable requests.
src/​index.ts Exports HttpRetryOptions.
test/​unit/​http-retry.spec.ts Tests HTTP retry execution and replay.
test/​unit/​orchestration-context.spec.ts Tests policy validation and serialization.
README.md Documents usage and semantics.
CHANGELOG.md Records the feature.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 304 to +314
const content = await response.text();

if (request.retryOptions !== undefined) {
const codes = request.retryOptions.statusCodesToRetry;
const shouldRetry = codes?.length
? codes.includes(response.status)
: response.status < 200 || response.status >= 300;
if (shouldRetry) {
throw new Error(`HTTP request failed with status code ${response.status}.`);
}
}
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 HTTP-specific retry options to durable-functions v4 callHttp

2 participants