Repository navigation
fix(server): thread one IsLambda value through every Application consumer - #1027
Conversation
…umer
NewApplicationFromDeps built the pre-DB scheduler with a live
runtime.IsLambda() instead of the ApplicationConfig it was handed, while
the post-DB scheduler (ensureDB) used app.appConfig.IsLambda. A caller
passing ApplicationConfig{IsLambda: true} without AWS_LAMBDA_RUNTIME_API
got a container-mode scheduler until ensureDB succeeded (permanently if
the database stayed unreachable), so ListRecommendations could spawn the
detached 5-minute background refresh inside a Lambda invocation.
The API handler had the same split: the RI utilization SWR gate and the
stale-execution sweep each read runtime.IsLambda() live.
Now ApplicationConfig.IsLambda (captured once by LoadApplicationConfig)
is the only source: the pre-DB scheduler takes cfg.IsLambda, and a new
HandlerConfig.IsLambda carries it into both NewHandler calls, where the
SWR cache and the sweep read h.isLambda. Scheduler.IsLambda() and
Handler.IsLambda() expose the constructed mode for wiring assertions.
Tests set the Lambda env vars to the opposite of the config and assert
the scheduler and handler follow the config, pre-DB (unit) and after
ensureDB (integration).
Closes LeanerCloud#110
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cristim
left a comment
There was a problem hiding this comment.
Independent review of e4d9fb7: draft: blocks ready
No code defects found. The only blockers to ready are gate items that are still open (CI not finished, no PR-side verification record from an independent party until this comment).
Findings
None BLOCKING on correctness or security.
NON-BLOCKING (follow-up, already acknowledged in the PR body):
- cmd/server/main.go:117 and internal/server/app.go:317.
--mode=lambdaorRUNTIME_MODE=lambdaoutside a real Lambda env still yieldscfg.IsLambda=falsefromLoadApplicationConfig, so mode selection and the in-Application value can differ. Pre-existing and not the divergence #110 describes. Fix idea: deriveIsLambdafrom the resolved runtime mode.
What I attacked
- Single source of truth. Grepped cmd/ and internal/ non-test code for
IsLambda,isLambda,AWS_LAMBDA_*,AWS_EXECUTION_ENV,LAMBDA_TASK_ROOT,runtime.IsLambda. Remaining live reads: internal/runtime/runtime.go:18 (definition), internal/server/app.go:317 (the one capture), cmd/server/main.go:117 (entry-point mode choice, before any config), internal/oidc/lambda_issuer.go:33 (function name only, gated bycfg.IsLambdaat app.go:466). Detection is onlyAWS_LAMBDA_RUNTIME_APInon-empty, so Cloud Run, Container Apps, Fargate and local server runs are never classified as Lambda (they leave it unset), and nothing was added that changes that. - Constructor coverage.
scheduler.NewSchedulerandapi.NewHandlerhave exactly three production call sites in app.go (pre-DB scheduler :489, pre-DB handler :522, post-DB scheduler :854, post-DB handler :885). All now usecfg.IsLambdaorapp.appConfig.IsLambda. No otherHandlerConfig{}/SchedulerConfig{}construction in non-test code. - Consumers: scheduler.go:1191, 1460, 1510 (background refresh, cold-start kick) read
s.isLambda; api ri_utilization_cache.go:121 reads the cache field fed fromh.isLambda(handler.go:157); handler_history.go:269 readsh.isLambda. Non-Lambda behavior is unchanged: with the env unset andcfg.IsLambda=falsethe values are identical to the old live reads.getRIUtilizationCacheis lazy viasync.Once, so the field is set before first use. - Mutation probes (reverted, worktree clean afterwards): (1) pre-DB scheduler back to
runtime.IsLambda():TestNewApplicationFromDeps_IsLambdaSingleSourceOfTruthfails (expected false). (2)expireStaleExecutionsback toruntime.IsLambda():ExpireIfStale_LambdaGuardnon-Lambda subtest fails (blocks on sweep). Both the original bug and a regression of the handler gate are caught. - Tests at this SHA (AWS config files nulled):
go build ./...ok;go test -count=1 ./internal/server/... ./internal/scheduler/... ./internal/api/...4349 passed.go test -tags integration ./internal/server -run TestEnsureDB_IsLambda3 passed (testcontainers Postgres, cleaned up). - Integration file build tag is
integration; the CIintegration-testsjob (ci.yml:346) runs-tags=integration, andmake test-compile-integrationvet-compiles it, so it is covered in CI. - Scope/size: 250 additions, 10 files, under 400 added lines; no file over 500 lines introduced. No new helper duplicates an existing one (the two accessors are the minimal test seam).
- Labels: PR carries priority/p2, severity/medium, urgency/this-quarter, impact/few, effort/xs, type/bug, triaged; identical to issue #110.
- CodeRabbit: skipped (draft); no threads. Not pinged.
Gate status (origin/main Review gate)
- Independent adversarial review of the full diff naming the SHA: this comment.
- Actionable findings resolved: none open.
- CI green on SHA: NOT YET. Runs for this SHA were
queued/ checkspendingat review time (CI - Build & Test, pre-commit). Noaction_requiredfork runs were present at review time; re-check when the draft is marked ready. - Local verification on macOS exercising the real scenario with fail-before/pass-after: done above (my probes plus the author's M1-M7, which I partly reproduced; M3-M6 not re-run).
- Verdict, SHA and evidence recorded on the PR: this comment.
Blocks ready
- PR is still a draft.
- CI must finish green on e4d9fb7 (or a later SHA, which restarts this gate).
|
@IAmJSD this PR now conflicts with main in |
# Conflicts: # internal/api/handler_history.go
|
Resolved in 053b137 by merging The only conflict was the import block of Checked on the merged tree:
|
|
Merge driver check of the new head 053b137: it is a merge of main (8c93e39) into the reviewed e4d9fb7, and the PR's own commit is unchanged ( |
|
Gate record for 053b137 (origin/main CLAUDE.md "Review gate"):
Merging at 053b137 with --match-head-commit. |
Root cause
One
Applicationheld two answers to "am I on Lambda".LoadApplicationConfigcapturescfg.IsLambdafromruntime.IsLambda()once, and the post-DB scheduler inreinitializeAfterConnectusesapp.appConfig.IsLambda. ButNewApplicationFromDepsbuilt the pre-DB scheduler with a liveruntime.IsLambda()(internal/server/app.go,NewSchedulercall), ignoring thecfgit was handed. A caller passingApplicationConfig{IsLambda: true}withoutAWS_LAMBDA_RUNTIME_APIgot a container-mode scheduler untilensureDBsucceeded, and for good if the DB stayed unreachable. In that windowListRecommendationscould spawn the detached 5-minute background refresh (maybeKickBackgroundRefresh) inside a Lambda invocation, which is exactly what the scheduler'sisLambdagate exists to prevent.The API handler had the same split.
getRIUtilizationCache(SWR refresh goroutine gate) andexpireStaleExecutions(async vs sync stale sweep) each readruntime.IsLambda()live instead of the Application's value.Fix
ApplicationConfig.IsLambdais now the only source of truth inside anApplication, threaded to every consumer that branches on Lambda mode:NewApplicationFromDeps)runtime.IsLambda()(live)cfg.IsLambdareinitializeAfterConnect)app.appConfig.IsLambdaNewApplicationFromDeps)cfg.IsLambdaruntime.IsLambda()(live)h.isLambda, from newHandlerConfig.IsLambdaruntime.IsLambda()(live)h.isLambdaapi.NewHandlercallscfg.IsLambda/app.appConfig.IsLambdaRate limiter: on current
mainit no longer branches onIsLambda(temporary in-memory limiter pre-DB on every runtime, DB-backed afterensureDB), so nothing changes there.Scheduler.IsLambda()andHandler.IsLambda()are new read-only accessors so the server package can check its wiring. They return the constructed value and never re-read the environment. Lambda detection itself (internal/runtime.IsLambda) is unchanged.internal/andcmd/now contain noruntime.IsLambda()calls apart fromLoadApplicationConfig(the single capture) andcmd/server/main.go'sdetermineRuntimeMode(entry-point mode selection, before any config exists).Closes #110
Tests
internal/server/app_islambda_test.go(unit):NewApplicationFromDepswithIsLambda: truewhile all Lambda env vars (AWS_LAMBDA_RUNTIME_API,AWS_LAMBDA_FUNCTION_NAME,AWS_EXECUTION_ENV,LAMBDA_TASK_ROOT) are unset, and withIsLambda: falsewhile they are set. Asserts the pre-DB scheduler and API handler both follow the config.internal/server/app_islambda_integration_test.go(integrationtag, Postgres testcontainer): same two directions afterensureDB, covering the post-DB scheduler and handler rebuilt inreinitializeAfterConnect.internal/api/ri_utilization_cache_test.go:NewHandler(HandlerConfig{IsLambda: x})with the env set to the opposite. AssertsHandler.IsLambda()and the RI cache'sisLambdafollow the config.internal/api/handler_history_test.go(ExpireIfStale_LambdaGuard) andhandler_history_actionable_starvation_test.go: these used to flipAWS_LAMBDA_RUNTIME_API. They now setHandler.isLambda, and the env is set to the opposite value to show it is ignored.internal/schedulerisLambda guard tests from test(scheduler): make the Lambda no-refresh tests bite again #971/test(scheduler): make the LambdaGuard control catch a non-blocking harness (#971 follow-ups) #988 pass unchanged.Verification (local, macOS, at e4d9fb7)
Tests ran with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null. No real cloud calls: a configured issuer URL skips the Lambda function-URL lookup, and the integration test uses static dummy keys against a local container.go build ./...andgo vet ./...: cleango test -count=1on./internal/server/...,./internal/scheduler/...,./internal/mocks/...,./internal/api/...: all okgo test -tags integration ./internal/server/ -run 'TestApplicationRateLimiter|TestEnsureDB_IsLambda': okrun --allow-parallel-runners ./...: 0 issuesgocyclo -over 10on every changed non-test.gofile: no findings--register-aws, isolated HOME, temp copy)--scanon changed files: clean. A planted AKIA probe was flagged, so the scanner was live.bash scripts/test-git-secrets-allowlist.sh: 42 PASS, 0 FAILruntime.IsLambda()(the original bug). Killed by the unit test, both directions.HandlerConfig.IsLambdaset to the live env value. Killed.NewHandlerorgetRIUtilizationCachereading the live env. Killed.expireStaleExecutionsreading the live env. Killed: the Lambda sub-test sees no synchronous sweep, and the non-Lambda sub-test blocks.Out of scope
cmd/server/main.godetermineRuntimeModestill auto-detects Lambda viaruntime.IsLambda(). If someone forces--mode=lambdaoutside a Lambda environment,LoadApplicationConfigstill capturesIsLambda=falsefrom the env. That is entry-point mode selection, not this in-Application divergence. Left alone; it may be worth a follow-up.oidc.PrimeIssuerURLFromLambdareadsAWS_LAMBDA_FUNCTION_NAMEto name the function to look up. It is already gated bycfg.IsLambdaand does not decide the mode.🤖 Generated with Claude Code
Summary by CodeRabbit