Repository navigation
fix(server): pin the rate-limiter cleanup start in reinitializeAfterConnect - #1216
Conversation
…onnect No test exercised the startRateLimiterCleanup call site, so reverting it to dbRL.StartCleanupWorker(ctx) brought back LeanerCloud#269 (worker dies with the request ctx) with every test green. Add a test that drives reinitializeAfterConnect, cancels the request ctx, and asserts the real DBRateLimiter worker goroutine keeps running until Close. Also start the worker last, after every step that can fail: ensureDB closes the pool on a reinitializeAfterConnect error, and a worker started earlier kept ticking against the closed pool until a retry or Close stopped it. A second test pins that a failed reinit leaves no worker running. Closes LeanerCloud#1137 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 59 minutes for your next included review. View limit details
Comment |
|
Independent review 2 (claude-fleet, cold): APPROVE 1bb6a9a Findings (none blocking):
Root cause:
Attacked:
Verified locally, on the tree merged with origin/main (00a3f40, which includes f5ad8c6), with GOTOOLCHAIN=go1.26.10 and no cloud credentials, under the heavy lock:
Reviewed by an independent agent with no author context (second reviewer). Review only: nothing was pushed, and the GitHub review state is unchanged. |
|
VERDICT: APPROVE 1bb6a9a Independent adversarial review of #1216 (closes #1137) at 1bb6a9a, base main. The diff is a 7-line move in internal/server/app.go plus two tests in internal/server/app_rl_cleanup_test.go. It covers both parts of #1137:
Findings: no blocking findings.
Attacked:
|
|
Local verification at |
|
Merged by IAmJSD's agent at IAmJSD's request (IAmJSD is acting owner while the maintainers are away), under the bar IAmJSD set: 1 approving independent review(s) on |
Root cause
reinitializeAfterConnect(internal/server/app.go) starts the DB rate limiter's cleanup worker throughstartRateLimiterCleanup. That function runs the worker on the app's lifetime context, so the worker no longer dies with the request context that triggered the lazy DB connect (#269, fixed in #1134). The existingTestStartRateLimiterCleanup_SurvivesRequestCtxAndStopsOnCloseonly callsstartRateLimiterCleanupdirectly. Nothing covered the call site, so changing it back todbRL.StartCleanupWorker(ctx)reintroduced #269 and every test still passed.Fix
TestReinitializeAfterConnect_CleanupWorkerOutlivesRequestCtxbuilds a pre-DBApplicationwithNewApplicationFromDepsand callsreinitializeAfterConnectwith a cancelable request ctx and a zerodatabase.Connection. No step touches the pool during wiring, the encryption key comes fromCREDENTIAL_ENCRYPTION_KEY, and the AWS config loads from/dev/null. The test then cancels the request ctx. It checks the realapi.DBRateLimiterworker goroutine directly: it counts goroutines whose "created by" frame is(*DBRateLimiter).StartCleanupWorkerin aruntime.Stackdump. Exactly one worker must start. It must still be running 300 ms after the request ctx is canceled. It must exit afterClose. No production seam was added.app.startRateLimiterCleanup(dbRL)moves from right after the limiter is built to the end ofreinitializeAfterConnect, after every step that can fail. Before this change, if the AWS config load or the analytics collector or API handler construction failed,ensureDBclosed the pool while the worker kept ticking against it until a retry replaced it orClosestopped it.TestReinitializeAfterConnect_NoCleanupWorkerWhenLaterStepFailsforces the AWS config load to fail with a missingAWS_PROFILE. It asserts the error is that one, so the test can't pass by failing at an earlier step, and that no worker is left running.app.RateLimiter = dbRLstays where it was, so nothing else changes.Closes #1137
Verification
All evidence is unit-level: in-process
Application, no Postgres, no AWS calls. Real-Postgres coverage of the rate limiter is stillTestApplicationRateLimiter(integration tag). I didn't run it here.Mutation probe, the exact revert from the issue (
app.startRateLimiterCleanup(dbRL)->dbRL.StartCleanupWorker(ctx)):Failure-path test, fail before / pass after the move:
Commands (run with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/nullbecause this machine's default AWS profile is broken; the new tests also set both to/dev/nullthemselves):Because disk space on this machine is tight, only
./internal/serverand./internal/mockswere run, not the whole repo.Notes / out of scope
credentials.LoadKeymemoizes once per process. The new tests are the first unit tests ininternal/serverto load the key, and both use the same env. A future unit test in this package that loads the key under a different env could be affected depending on test order. This is unchanged pre-existing behavior.created by ...(*DBRateLimiter).StartCleanupWorkerframe. If that method is renamed, the test fails loudly at "must start one cleanup worker" rather than passing vacuously.Maintainer: please mirror the issue's triage labels on this PR:
impact/internal,priority/p3,severity/low,urgency/this-quarter,effort/xs,type/bug.🤖 Generated with Claude Code