fix(test): cap integration/e2e MySQL test pools to avoid max_connections exhaustion (bookshelf-or2yq) #1328
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-or2yq"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
dbtest.NewSuiteDBopened each isolated test DB withMaxOpenConns:10;bookdrop_integration_test.goandscan_integration_test.gocallStartSharedMySQL+NewSuiteDBper-BeforeEach (not once per suite), so undermake integration -p 8the 8 parallel packages × uncapped pools could exhaust MySQL'smax_connections, blocking connections with a 60scontext.DeadlineExceeded(bookshelf-rgjd / bookshelf-or2yq).internal/dbtest/dbtest.go: ReduceNewSuiteDBpool fromMaxOpenConns:10, MaxIdleConns:5to 5/2 (exported asSuiteDBMaxOpenConns/SuiteDBMaxIdleConnsconstants) to match the discipline already used byopenRootand the wfengine suites.internal/wfengine/engine_integration_test.go: Cap the previously-uncappedrawDB(used only for seed queries in one BeforeEach) to 5/2.e2e/testutil/server.goprovisionWFDatabase: Cap the two short-livedsql.Openconnections (one-shot DDL CREATE/DROP) to 1/1 — they need at most 1 connection each.internal/dbtest/dbtest_test.go: Add a pool-budget guard test (mirrorse2e/testutil/server_test.go) so future cap bumps fail the integration suite if they blow the budget.Test plan
make build— compiles cleanmake test— all unit tests passgo build -tags integration ./internal/db/... ./internal/dbtest/... ./internal/wfengine/...— compiles cleanCloses bead bookshelf-or2yq on merge.
Security Review — PR #1328 (bookshelf-or2yq)
Scope verified: all 9 changed files are test-only (
*_test.go,e2e/testutil/server.go,internal/dbtest/dbtest.go). No productioninternal/dbconnection handling, DSN construction, credential handling, or auth surfaces changed. Zero diff againstinternal/app/,internal/config/,internal/httpserver/, orinternal/users/.Findings
No security findings.
Detailed reasoning:
Test-harness scope: The diff touches exclusively test infrastructure —
internal/dbtest/dbtest.go(test helper package),internal/dbtest/dbtest_test.go(guard test),e2e/testutil/server.go(e2e suite helper),internal/wfengine/engine_integration_test.go,internal/wfengine/export_test.go,internal/wfengine/retention_sweep_test.go,internal/governor/inmemory_test.go,internal/library/scan/walk_test.go, ande2e/browser/journey_move_toast_test.go. None of these files execute in the production binary.No hard-coded secrets or DSNs: The new exported constants
SuiteDBMaxOpenConns = 5andSuiteDBMaxIdleConns = 2are plain integers with no credential or connection-string content. DSN handling ine2e/testutil/server.goandinternal/dbtestis unchanged in structure — the existingPERGAMUM_DSNenv-var path still strips the database name viastripDBNamebefore use; no DSN is newly logged or echoed.No cross-user / multi-user regression: The changes are all in test harness plumbing (pool caps, timeout tightening, rendezvous simplification). No handler, service, or query is touched. User-scoping in production paths is unaffected.
No injection surface added: The
provisionWFDatabasefunction that appends backtick-quoted DB names to raw SQL strings is pre-existing (not introduced by this PR) and that surface is unchanged. The two newSetMaxOpenConns(1)/SetMaxIdleConns(1)lines sit immediately after the existingsql.Open("mysql", rootDSN)call in the same short-lived DDL helper — no new SQL construction.Pool caps reduce, not expand, resource exposure: All changes reduce or cap connection counts (from 10→5 for suite pools, from uncapped to 1 for single-use DDL connections in
provisionWFDatabase, from 5→5 explicit forrawDBin the wfengine integration test). Lower caps narrow the attack surface against a shared MySQL instance rather than widening it.Guard test is black-box and correct:
internal/dbtest/dbtest_test.godeclarespackage dbtest_testand references only the two newly exported constants — no unexported symbols accessed. The arithmetic guard (parallelPkgs × (rootDBMaxOpenConns + SuiteDBMaxOpenConns) < maxConnections − safeHeadroom) is a deterministic compile-time-ish check that will fail the test suite if a future PR bumps either cap beyond the safe ceiling.Flake-regression assessment: The
walk_test.goandretention_sweep_test.gochanges replace channel-based rendezvous withruntime.Gosched() + time.Sleep(1ms) + cancel()andEventually(..., 2s, 1ms). Thetime.Sleepinwalk_test.gois in a test that pre-cancels a context before launching the walk goroutine — thesleepis not asserting on wall-clock elapsed time, it is yielding to let the goroutine fill the error channel before the context cancel is observed. This is a minor smell (a boundedEventuallyloop would be more deterministic) but is not a security concern and the test's purpose — exercising thectx.Done()drop path — is preserved. No flake-standard wall-clock assertion in thereview-standard.mdsense (assertingtime.Sincein the assertion itself) is introduced.REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Code Review — bookshelf-or2yq / PR #1328
Phase 1: Spec Compliance
The bead goal is to cap integration/e2e test-harness MySQL connection pools to stop
max_connectionsexhaustion under parallel procs. All four touch-points specified are addressed:internal/dbtest/dbtest.go(suite pool 10/5→5/2 via exported constants),internal/dbtest/dbtest_test.go(budget-guard test),internal/wfengine/engine_integration_test.go(rawDB 5/2),e2e/testutil/server.go(DDL pools 1/1). Productioninternal/db/db.gois confirmed untouched — pool sizing remains config-driven.Phase 2: Findings
[MINOR] internal/dbtest/dbtest_test.go:78 —
parallelPkgs = 8comment says "4 packages" but lists the wrong count, and the constant conflates-p Nwith package countThe comment reads: "Current packages: internal/db, internal/dbtest, internal/wfengine, cmd/pergamum." But
cmd/pergamumhas no//go:build integrationtest files (worker_test.goandhealthcheck_test.gohave no integration tag), so there are only 3 packages that open DB pools. The constant8equals the-p 8flag (process-parallelism ceiling), not the number of packages. In the worst casego test -p 8can run at most 3 pool-owning binaries concurrently, not 8. The math is safe (3 × 10 = 30 << 950) but the constant and its comment are misleading — a future maintainer adding a new package may trust the comment and miscalculate. Suggested fix: rename tomaxParallelProcs = 8(matching-p 8) and update the comment to list the 3 real packages:internal/db,internal/dbtest,internal/wfengine.[MINOR] internal/dbtest/dbtest_test.go:86 —
rootDBMaxOpenConnsis a hardcoded shadow; drift will silently produce a false-passing budget checkThe constant
rootDBMaxOpenConns = 5mirrors the literal indbtest.openRoot(line 478 ofdbtest.go), butopenRootis unexported so the test cannot reference it directly. IfopenRoot's pool is ever changed, this shadow constant will silently desync and the budget calculation will be wrong while still passing. The fix is to export aRootDBMaxOpenConnsconstant frominternal/dbtest(alongside the already-exportedSuiteDB*constants) and reference it in the test, the same pattern used for the suite constants.No Coverage / golangci Changes
Confirmed:
scripts/check-coverage.shand.golangci.ymlare unchanged. No new exclusions. Both test files declarepackage dbtest_test(black-box). The rawDB cap inengine_integration_test.gois inside an existing black-box test (package wfengine_test) — convention is preserved.REVIEW VERDICT: 0 blocker, 0 major, 2 minor
93ea6f0d93421b84834e