Skip to content

fix: stop retry work on caller cancellation - #1766

Merged
kang-heewon merged 4 commits into
trunkfrom
fix/1712-abortable-retry
Aug 7, 2026
Merged

fix: stop retry work on caller cancellation#1766
kang-heewon merged 4 commits into
trunkfrom
fix/1712-abortable-retry

Conversation

@kang-heewon

@kang-heewon kang-heewon commented Aug 5, 2026

Copy link
Copy Markdown
Member

Outcome

Caller-provided AbortSignal values now stop retry execution before an attempt or during backoff. Built-in backoff timers and the testing virtual clock release abort listeners and scheduled work promptly.

Custom backoff policies and injected sleepers must explicitly advertise abort support. When cancellation is requested for an unsupported implementation, retry execution fails before invoking the callback with the stable retry-core/backoff-cancellation-unsupported Problem code. Calls without a signal retain their previous behavior.

@Retryable supports both a fixed signal and a per-invocation signalResolver, so concurrent method calls can be cancelled independently.

Fixes #1712

Verification

  • pnpm --filter @croco/retry-core test — 286 passed
  • pnpm --filter @croco/testing test — 168 passed
  • retry-core and testing lint, typecheck, and build passed
  • pre-push full repository test — 234 tasks passed
  • pre-push full repository typecheck — 233 tasks passed
  • public API snapshot — 115 packages matched
  • Problem registry — 577 codes matched
  • package manifests — 115 packages normalized
  • API documentation triggers and changeset requirement passed
  • isolated TypeDoc output matches all changed retry-core API pages

pnpm docs:api:check continues to report unrelated repository-wide documentation drift already present on trunk; no changed retry-core page remains divergent from the isolated generated output.

Review gates

  • Correctness: PASS — covers pre-aborted signals, abort during backoff, listener cleanup and registration races, no later attempts, distinct decorator invocations, cooperative custom policies, unsupported capability preflight, and unchanged no-signal behavior.
  • API, security, and release: PASS — additive typed surface, safe Problem details, generated public API and Problem registry artifacts, documentation, and changesets for retry-core, problems-core, and testing.
  • Maintainability: PASS — one explicit capability boundary prevents silent cancellation bypasses across built-in and custom policies.
  • Independent adversarial review: PASS after resolving timer cleanup, per-invocation decorator signals, custom-policy capability enforcement, injected-sleeper capability claims, and testing runtime cancellation.

Summary by CodeRabbit

  • 새 기능
    • 재시도 작업에 AbortSignal을 전달해 첫 시도 전이나 백오프 대기 중에도 즉시 취소할 수 있습니다.
    • 재시도 가능한 메서드별로 취소 신호를 지정하거나 호출 정보에 따라 동적으로 선택할 수 있습니다.
    • 취소를 지원하지 않는 백오프 정책에는 명확한 오류가 제공됩니다.
  • 문서
    • 재시도 취소 옵션, 백오프 정책 설정 및 관련 오류 코드 문서를 추가·보완했습니다.
  • 버그 수정
    • 취소 시 대기 타이머와 가상 작업이 정리되도록 개선했습니다.
    • 선택적 스모크 테스트 아티팩트가 없어도 검증이 정상적으로 완료됩니다.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kang-heewon, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 10 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ee29203d-fe6d-4bed-a087-e53ae4d4037b

📥 Commits

Reviewing files that changed from the base of the PR and between c165ec3 and 6ad0e44.

⛔ Files ignored due to path filters (1)
  • packages/problems-core/src/generated/problem-code-registry.ts is excluded by !**/generated/**
📒 Files selected for processing (8)
  • .changeset/abort-retry-work.md
  • docs/problem-code-registry.json
  • packages/docs/src/content/docs/api/retry-core/src/classes/RetryCancellationUnsupportedProblem.md
  • packages/docs/src/content/docs/en/reference/problem-recovery-cookbook.md
  • packages/retry-core/src/tests/RetryEngine.spec.ts
  • packages/retry-core/src/tests/RetryTemplate.spec.ts
  • packages/retry-core/src/tests/Retryable.spec.ts
  • public-api-surface.snapshot.json
📝 Walkthrough

Walkthrough

재시도 API가 AbortSignal을 받아 시도 전과 백오프 중 취소를 처리합니다. 백오프 capability와 취소 오류가 추가되었습니다. Retryable은 호출별 signal을 resolve합니다. Generated-app smoke 검증은 선택적 journey artifact를 조건부로 처리합니다.

Changes

재시도 취소 지원

Layer / File(s) Summary
Abort-aware backoff 계약과 구현
packages/retry-core/src/libs/BackoffPolicy.ts, packages/retry-core/src/libs/errors/*, packages/testing/src/libs/TestRuntime.ts, packages/docs/src/content/docs/api/retry-core/src/classes/*, packages/docs/src/content/docs/api/retry-core/src/interfaces/*
백오프와 sleep이 선택적 AbortSignal을 받습니다. supportsAbortSignalsleepSupportsAbortSignal capability가 추가되었습니다. 내장 대기와 테스트 런타임은 abort 시 타이머와 리스너를 정리합니다. RetryCancellationUnsupportedProblem이 추가되었습니다.
재시도 경로의 signal 전달
packages/retry-core/src/libs/RetryEngine.ts, packages/retry-core/src/libs/RetryOrchestrator.ts, packages/retry-core/src/libs/RetryTemplate.ts, packages/retry-core/src/libs/Retryable.ts, packages/retry-core/README.md
RetryEngine은 시도 전과 백오프 중 abort를 검사합니다. RetryTemplate, RetryOrchestrator, Retryable은 signal을 전달합니다. signalResolver는 호출 컨텍스트에서 signal을 선택합니다.
취소 동작 검증과 API 기록
packages/retry-core/src/tests/*, packages/testing/src/tests/*, packages/retry-core/src/index.ts, public-api-surface.snapshot.json, docs/problem-code-registry.json, packages/docs/src/content/docs/en/reference/problem-recovery-cookbook.md, .changeset/*
첫 시도 전 취소, 백오프 중 취소, 리스너 정리, 미지원 백오프 거부, 호출별 signalResolver를 검증합니다. 새 오류와 타입을 공개 API 및 문제 코드 문서에 기록합니다.

Generated-app smoke 증거 처리

Layer / File(s) Summary
선택적 journey artifact 검증
scripts/release-spine-evidence.mts, scripts/verification-manifest.mts, scripts/tests/release-spine-evidence.spec.ts, scripts/tests/verification-manifest.spec.ts
spine-blocking-journeys 복사본은 존재할 때만 검증합니다. artifact는 전체 smoke 실행에서만 required로 표시됩니다. 누락된 선택적 bundle은 오류 없이 기록됩니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Retryable
  participant RetryOrchestrator
  participant RetryEngine
  participant BackoffPolicy

  Caller->>Retryable: 호출 인자와 AbortSignal 전달
  Retryable->>RetryOrchestrator: signalResolver 결과 전달
  RetryOrchestrator->>RetryEngine: signal을 포함한 retry loop 실행
  RetryEngine->>BackoffPolicy: wait(attempt, signal)
  BackoffPolicy-->>RetryEngine: 정상 완료 또는 abort reason
  RetryEngine-->>Caller: 결과 또는 RetryAbortedProblem
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 재시도 취소와 직접 관련 없는 generated-app smoke evidence 및 verification manifest 변경이 포함되어 범위를 벗어납니다. scripts/release-spine-evidence.mts 및 관련 테스트와 verification-manifest 변경을 별도 PR로 분리하거나 관련 이슈를 연결하십시오.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 호출자 취소 시 재시도 작업을 중단하는 핵심 변경을 정확하고 간결하게 설명합니다.
Linked Issues check ✅ Passed AbortSignal 전달, 취소 시점 검사, 중단 가능한 백오프, RetryAbortedProblem, 테스트 및 기존 동작 보존 요구를 충족합니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1712-abortable-retry

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

📊 Benchmark Results

✅ All benchmarks passed

Benchmark p75 Threshold Baseline vs Baseline Status Notes
CrocoApp constructor 43.3μs 30.0ms 8.2μs +430.2% -
CrocoApp lambdaHandler (10 controllers) 2.4ms 50.0ms 258.4μs +843.0% -
Lambda cold-start simulation 1.9ms 80.0ms 418.1μs +346.6% -
Lambda cold-start with headers 1.6ms 80.0ms 369.7μs +323.2% -
Lambda cold-start with binary body 1.5ms 80.0ms 339.1μs +347.3% -
Lambda cold-start with query params 1.5ms 80.0ms 301.3μs +408.4% -
Lambda cold-start with authorizer context 1.5ms 80.0ms 299.8μs +394.2% -
Lambda cold-start realistic scenario 1.5ms 80.0ms 299.2μs +393.8% -
EventBusConfig.start (10 handlers) 1.7μs 10.0ms 1.4μs +16.1% -
EventPublisher.publishNow single event 1.9μs 2.0ms 1.7μs +12.5% -
DefaultHandlerResolver.resolve × 10 0.1μs 5.0ms 0.1μs +0.0% -
Container.get singleton (cold) 87.3μs 5.0ms 70.3μs +24.3% -
Container.register × 50 components 3.4ms 10.0ms 3.2ms +4.3% -
Container.validate (50 components) 3.8ms 20.0ms 3.4ms +13.4% -
Container.get singleton (warm) 1.7μs 500.0μs 1.6μs +2.4% -
TelemetryRuntime.init (lambda preset) 2.2μs 200.0ms 1.1ms -99.8% -
lambdaPreset config creation 1.5μs 2.0ms 1.4μs +3.5% -

Updated: 2026-08-07T15:15:31.115Z · Commit: a52a305

@kang-heewon
kang-heewon force-pushed the fix/1712-abortable-retry branch from a2eb675 to db45505 Compare August 5, 2026 17:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/abort-retry-work.md:
- Line 7: Update the release note in abort-retry-work.md to document the
cancellation contract for custom backoff policies and injected sleepers: they
must declare cancellation support, otherwise an already-cancelled call fails
with retry-core/backoff-cancellation-unsupported before the callback runs.
Include the migration guidance for existing custom implementations.

In `@packages/retry-core/src/libs/BackoffPolicy.ts`:
- Around line 140-147: ExponentialBackoff.wait()의 signal 조건 분기를 제거하고, delayMs가
양수일 때 선택적 signal을 그대로 this.sleep(delayMs, signal)에 전달하십시오. 동일한 중복 로직이 있는
FixedBackoff.wait()에도 같은 변경을 적용하고, 나머지 대기 동작은 유지하십시오.

In `@packages/retry-core/src/tests/Retryable.spec.ts`:
- Around line 65-66: Retryable.spec.ts의 비동기 오류 검증을
rejects.toBeInstanceOf(RetryAbortedProblem)에서
rejects.toThrow(RetryAbortedProblem)로 변경하세요. 언급된 두 테스트 구간의 RetryAbortedProblem
클래스 검증만 업데이트하고, callback이 호출되지 않았는지 확인하는 expect(callback).not.toHaveBeenCalled()
검증은 그대로 유지하세요.

In `@packages/retry-core/src/tests/RetryEngine.spec.ts`:
- Around line 185-189: Update each cancellation-error assertion in RetryEngine
tests to include await expect(execution).rejects.toThrow(...) with the expected
error message. Preserve the existing code and methodName checks in the first
test, and retain any other current assertions.
- Around line 15-25: Replace the definite-assignment assertion on resolve in
createDeferred with an implementation that initializes the resolver without
non-null assertions, while preserving the existing promise and resolve API used
by the tests.

In `@packages/retry-core/src/tests/RetryTemplate.spec.ts`:
- Around line 46-55: Update the async rejection assertion in the “forwards
caller cancellation to the retry engine” test to use rejects.toThrow with
RetryAbortedProblem instead of rejects.toBeInstanceOf. Keep the callback
non-invocation assertion unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cb5289fc-cb72-422c-a8cb-dd300d705135

📥 Commits

Reviewing files that changed from the base of the PR and between cfe0d14 and c165ec3.

⛔ Files ignored due to path filters (1)
  • packages/problems-core/src/generated/problem-code-registry.ts is excluded by !**/generated/**
📒 Files selected for processing (36)
  • .changeset/abort-retry-work.md
  • docs/problem-code-registry.json
  • packages/docs/src/content/docs/api/retry-core/src/classes/ExponentialBackoff.md
  • packages/docs/src/content/docs/api/retry-core/src/classes/FixedBackoff.md
  • packages/docs/src/content/docs/api/retry-core/src/classes/NoBackoff.md
  • packages/docs/src/content/docs/api/retry-core/src/classes/RetryAbortedProblem.md
  • packages/docs/src/content/docs/api/retry-core/src/classes/RetryCancellationUnsupportedProblem.md
  • packages/docs/src/content/docs/api/retry-core/src/functions/executeRetryLoop.md
  • packages/docs/src/content/docs/api/retry-core/src/interfaces/BackoffDependencies.md
  • packages/docs/src/content/docs/api/retry-core/src/interfaces/BackoffPolicy.md
  • packages/docs/src/content/docs/api/retry-core/src/interfaces/RetryTemplateOptions.md
  • packages/docs/src/content/docs/api/retry-core/src/interfaces/RetryableOptions.md
  • packages/docs/src/content/docs/api/retry-core/src/type-aliases/RetryOrchestratorOptions.md
  • packages/docs/src/content/docs/api/retry-core/src/type-aliases/RetrySignalResolverContext.md
  • packages/docs/src/content/docs/en/reference/problem-recovery-cookbook.md
  • packages/retry-core/README.md
  • packages/retry-core/src/index.ts
  • packages/retry-core/src/libs/BackoffPolicy.ts
  • packages/retry-core/src/libs/RetryEngine.ts
  • packages/retry-core/src/libs/RetryOrchestrator.ts
  • packages/retry-core/src/libs/RetryTemplate.ts
  • packages/retry-core/src/libs/Retryable.ts
  • packages/retry-core/src/libs/errors/RetryAbortedProblem.ts
  • packages/retry-core/src/libs/errors/RetryCancellationUnsupportedProblem.ts
  • packages/retry-core/src/libs/errors/index.ts
  • packages/retry-core/src/tests/BackoffPolicy.spec.ts
  • packages/retry-core/src/tests/RetryEngine.spec.ts
  • packages/retry-core/src/tests/RetryTemplate.spec.ts
  • packages/retry-core/src/tests/Retryable.spec.ts
  • packages/testing/src/libs/TestRuntime.ts
  • packages/testing/src/tests/TestRuntime.spec.ts
  • public-api-surface.snapshot.json
  • scripts/release-spine-evidence.mts
  • scripts/tests/release-spine-evidence.spec.ts
  • scripts/tests/verification-manifest.spec.ts
  • scripts/verification-manifest.mts

Comment thread .changeset/abort-retry-work.md Outdated
Comment thread packages/retry-core/src/libs/BackoffPolicy.ts
Comment thread packages/retry-core/src/tests/Retryable.spec.ts Outdated
Comment thread packages/retry-core/src/tests/RetryEngine.spec.ts
Comment thread packages/retry-core/src/tests/RetryEngine.spec.ts
Comment thread packages/retry-core/src/tests/RetryTemplate.spec.ts
@kang-heewon
kang-heewon force-pushed the fix/1712-abortable-retry branch from c165ec3 to 6ad0e44 Compare August 7, 2026 15:05
@kang-heewon

Copy link
Copy Markdown
Member Author

@coderabbitai resolve

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

@kang-heewon
kang-heewon merged commit 6795b4d into trunk Aug 7, 2026
12 checks passed
@kang-heewon
kang-heewon deleted the fix/1712-abortable-retry branch August 7, 2026 17:03
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.

[retry-core] Offer AbortSignal-aware retry and backoff cancellation

1 participant