fix: connection pool leak in StartClient (sqlstore.New opens unbounded pool every reconnect) - #168
Open
jefersonfborba wants to merge 1 commit into
Conversation
… a new one StartClient() called sqlstore.New(ctx, "postgres", PostgresAuthDB, log) on every reconnect (disconnect retry, session check, API ConnectInstance). sqlstore.New() calls sql.Open() internally, opening a brand-new, unbounded connection pool each time, and the returned container was never closed here -- every call leaked one idle Postgres connection that accumulated for days until the shared Postgres server's max_connections was exhausted, causing downstream services (n8n workflows, other apps on the same shared Postgres) to fail with "remaining connection slots are reserved for non-replication superuser connections" (SQLSTATE 53300). The service already opens authDB once at startup with a properly bounded pool (initPostgresAuthDB: SetMaxOpenConns(25), SetMaxIdleConns(5), SetConnMaxIdleTime(1m)) and injects it into whatsmeowService, but StartClient() never used it for the whatsmeow session store. Fix: use sqlstore.NewWithDB(w.authDB, "postgres", dbLog) to wrap the existing pooled connection instead of opening a new one, calling container.Upgrade(ctx) explicitly since NewWithDB skips the auto-upgrade that New() performs. Observed in production (self-hosted): evogo_auth_user accumulated 40+ idle connections over ~2 days, recurring 4x (07-31, 08-04, 08-06, 08-08), escalating on 08-06 to exhausting even superuser-reserved connection slots and requiring a full Postgres restart.
Reviewer's guide (collapsed on small PRs)Reviewer's GuideThis PR fixes a Postgres connection pool leak in whatsmeowService.StartClient by reusing the already-pooled authDB connection via sqlstore.NewWithDB and simplifying the debug/non-debug branching for database logging, while leaving the sqlite fallback logic intact. Sequence diagram for StartClient Postgres session store initializationsequenceDiagram
participant whatsmeowService
participant authDB as sql.DB_authDB
participant sqlstore as sqlstore
participant container as sqlstore.Container
whatsmeowService->>whatsmeowService: StartClient(cd *ClientData)
alt WaDebug enabled
whatsmeowService->>whatsmeowService: waLog.Stdout("Database", WaDebug, true)
end
alt PostgresAuthDB configured
whatsmeowService->>sqlstore: NewWithDB(authDB, "postgres", dbLog)
sqlstore-->>whatsmeowService: container
whatsmeowService->>container: Upgrade(context.Background())
else PostgresAuthDB not configured
whatsmeowService->>sqlstore: New(context.Background(), "sqlite", dsn, dbLog)
sqlstore-->>whatsmeowService: container
end
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
StartClient()(called on every reconnect: disconnect retries, sessionchecks,
ConnectInstanceAPI calls) creates the whatsmeow session storecontainer via:
sqlstore.New()callssql.Open()internally, opening a brand-new,unbounded Postgres connection pool on every call — and the resulting
containeris never closed anywhere inStartClient(). Each reconnectleaks one idle connection that is never released.
On a shared Postgres instance, this accumulates over days until
max_connectionsis exhausted, and every other service/database sharingthat Postgres starts failing with:
We hit this in production 4 times over ~10 days (self-hosted, shared
Postgres): 2026-07-31, 2026-08-04, 2026-08-06 (escalated to exhausting even
the superuser-reserved slots, requiring a full
docker restartof theshared Postgres — affecting unrelated projects on the same server), and
2026-08-08 (~43 idle
evogo_auth_userconnections, oldest ~1d19h,106/100 connections at capture time).
Root cause
whatsmeowServicealready opensauthDBonce, at startup, with acorrectly bounded pool (
initPostgresAuthDBincmd/evolution-go/main.go):...and injects it into
whatsmeowService.authDBviaNewWhatsmeowService.But
StartClient()never uses it for the whatsmeow session store — itopens a fresh, unbounded pool from the DSN string every time instead.
Fix
Use
sqlstore.NewWithDB(w.authDB, "postgres", dbLog)— which wraps anexisting
*sql.DBinstead of opening a new one — reusing thealready-pooled, already-limited connection. Since
NewWithDBskips theauto-upgrade that
New()performs,container.Upgrade(ctx)is calledexplicitly right after.
The sqlite fallback path (
PostgresAuthDB == "") is untouched — it isn'tthe source of this leak (local file, not a shared server).
Validation
I wasn't able to run
go build ./...for this PR — my local Dockerdaemon has a corrupted/read-only containerd filesystem unrelated to this
repo, and I don't have a local Go toolchain. The change is a 2-line
semantic substitution (confirmed against
go.mau.fi/whatsmeow'sstore/sqlstore/container.go—NewWithDB(db *sql.DB, dialect string, log waLog.Logger) *Containersignature matches exactly what's used here), but please run a build/lint
pass before merging — I couldn't verify it locally this time.
Related
Full incident history (4 occurrences, mitigation steps,
pg_stat_activitysnapshots) documented downstream in a consumer project's backlog:
https://github.com/jefersonfborba/tabloides/blob/master/docs/backlog.md
(search "BL-028").
Summary by Sourcery
Reuse the existing pooled Postgres connection for the Whatsmeow session store to prevent connection pool leaks on client reconnects.
Bug Fixes:
Enhancements: