Repository navigation
Conversation
Wait returned nil immediately when retryCount was 0, without checking ctx.Err(). A context canceled before the first Cost Explorer call was therefore passed through to concurrency.Acquire and the SDK call; with no shared semaphore attached, the SDK call ran on a dead context. Check ctx.Err() before the first-attempt fast path so every entry point reports cancellation. Closes LeanerCloud#16 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 2 billable files and costs up to $0.50. Or wait 46 minutes for your next included review. View limit details
Comment |
|
Independent adversarial review: CLEAN at
Still needed before merge: owner approval of the fork CI runs and green CI on this SHA, and the author marking the PR ready. Labels mirrored from the linked issue. CodeRabbit skipped this draft; CR skipped: quota/draft, so the merge comment will carry |
Root cause
RateLimiter.Wait(providers/aws/recommendations/ratelimiter.go) returnednilright away whenretryCount == 0, without checkingctx.Err(). The context was only checked inside the backoffselect, which runs on retries. So a context canceled before the first call went straight on toconcurrency.Acquireand the Cost Explorer SDK call. When no shared semaphore is attached to ctx,concurrency.Acquiredoes nothing, and the SDK call ran on a dead context.Fix
Waitnow returnsctx.Err()before the first-attempt fast path. This is a 3-line change. All seven callers (fetchRIPageWithRetry, SP, coverage, utilization, on-demand series and the two SP coverage/utilization loops) already wrap and return theWaiterror, so this needs no caller changes. Retry state is not touched.Closes #16
Verification
All of this is unit/mock evidence. No real AWS calls were made.
New tests in
providers/aws/recommendations/ratelimiter_test.go:TestWait_FirstAttemptHonorsCanceledContext: a fresh limiter (retryCount == 0) with a canceled context returnscontext.Canceled, and with an expired deadline returnscontext.DeadlineExceeded. The retry count stays at 0.TestFetchRIPageWithRetry_PreCanceledContextSkipsCostExplorer: caller-level check with no semaphore on ctx. A pre-canceled ctx returnscontext.Canceled, and the mock Cost Explorer records zeroGetReservationPurchaseRecommendationcalls.Fail-before (test commit on top of unmodified
ratelimiter.go):Pass-after:
Other checks (macOS):
cd providers/aws && go build ./... && go vet ./... && GOWORK=off go test -race -short -count=1 ./...passes for every package.golangci-lint run ./recommendations/...(v2.10.1): 0 issues.gocyclo -over 10 providers/aws/recommendations/ratelimiter.go: no output.gofmt -l: clean.gosecandpre-commitare not installed locally, so the gosec hook was left to CI.All tests ran with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null, because the default AWS profile on this machine is invalid. The tests need no credentials.Real-cloud gap: this was not exercised against live Cost Explorer. The change only adds a local context check that runs before any SDK call.
Out of scope
concurrency_cap_test.goandTestFetchCoveragePaged_CtxCancelReturnsError) still pass unchanged. Their error now comes from the rate limiter rather than fromAcquireor the SDK mock. They still assertErrorIs(context.Canceled), so they are not affected.Maintainer: please mirror the issue's triage labels on this PR:
triaged,impact/internal,priority/p3,severity/low,urgency/this-quarter,effort/s,type/bug.🤖 Generated with Claude Code