Skip to content

Pub/sub PGListener holds a pooled Hikari connection for the JVM lifetime, triggering a false leak warning on every boot #36934

Description

@fabrizzio-dotCMS

Problem Statement

Every dotCMS boot logs a WARN with a full stack trace that reads like a bug and is not one:

WARN pool.ProxyLeakTask: Connection leak detection triggered for org.postgresql.jdbc.PgConnection@… on thread main
java.lang.Exception: Apparent connection leak detected
    at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:100)
    at com.dotcms.dotpubsub.JDBCPubSubImpl$PGListener.<init>(JDBCPubSubImpl.java:128)
    at com.dotcms.dotpubsub.JDBCPubSubImpl.listener(JDBCPubSubImpl.java:63)
    at com.dotcms.dotpubsub.JDBCPubSubImpl.start(JDBCPubSubImpl.java:88)
    at com.dotcms.dotpubsub.QueuingPubSubWrapper.start(QueuingPubSubWrapper.java:74)
    at org.apache.felix.framework.OSGIUtil.<init>(OSGIUtil.java:155)
    …
    at com.dotmarketing.startup.runalways.Task00004LoadStarter.executeUpgrade(Task00004LoadStarter.java:16)

PGListener takes a connection from the Hikari pool and holds it for the lifetime of the listener thread — which is correct for what it does, since a Postgres LISTEN needs a persistent connection:

// JDBCPubSubImpl.java:121
private final Lazy<Connection> connection =
        Lazy.of(() -> Try.of(() -> DbConnectionFactory.getDataSource().getConnection())
                .getOrElseThrow(DotRuntimeException::new));

Hikari's leak detector has no way to know that, so it flags any connection held past DB_LEAK_DETECTION_THRESHOLD (default 300s / 60s depending on the datasource strategy). The warning is a false positive — the connection is released in stop()stopListening().

Two real consequences behind the noise:

  1. A pooled connection is permanently withdrawn from the pool for the JVM's lifetime. On installations with a tight max_connections that is a slot nobody accounts for, and it does not show up as in-use work.
  2. Boot logs carry a scary stack trace that is not actionable, which trains operators and support to ignore leak warnings — including real ones.

A LISTEN connection should not come from the request pool at all.

Steps to Reproduce

  1. Start any dotCMS instance backed by PostgreSQL with the default leak-detection threshold (no special configuration required — the current dotcms-test:1.0.0-SNAPSHOT image reproduces it).
  2. Let the first boot run through the starter load (Task00004LoadStarter).
  3. Search the log for Apparent connection leak detected.

Observed: the warning above is logged with a stack trace rooted at JDBCPubSubImpl$PGListener.<init>, and one pool connection stays checked out for as long as the JVM runs.

Expected: no leak warning, and the pool keeps all of its connections available for request work.

Acceptance Criteria

  • The pub/sub LISTEN connection no longer comes from the Hikari request pool — it uses a dedicated connection built from the same JDBC coordinates, so the pool keeps its full capacity.
  • When those coordinates are not available (JNDI or any datasource strategy that does not expose a JDBC URL), the listener still works, falling back to the current behaviour rather than failing to start.
  • A normal boot logs no Apparent connection leak detected warning attributable to JDBCPubSubImpl.
  • The dedicated connection is closed on stop() / stopListening() and on listener restart, so repeated start/stop cycles do not accumulate connections.
  • Pub/sub behaviour is unchanged: topics are still received after the change, including after a listener reconnect.

dotCMS Version

Reproduced on dotcms/dotcms-test:1.0.0-SNAPSHOT built from main at 2026-08-06. The code path is long-standing and not specific to that build.

Notes

Found incidentally while reproducing #36222 (TC-056) on a local ES→OS migration stack. Unrelated to that issue's subsystem — filed separately rather than bundled into its fix.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Status
    New

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions