dotCMS / dotCMS/core

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

Open
#36,934 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

OKR : Customer Support Team : Scout Type : Defect
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in JDBCPubSubImpl.java at PGListener and the listener/start/stop entry points shown in the stack trace; trace DbConnectionFactory and the available datasource strategies. Verify how a dedicated JDBC connection can be created while preserving the fallback behavior, then exercise boot and listener restart/stop flows. Done means no attributable leak warning, no pooled connection held, clean shutdown, and unchanged topic delivery.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, postgresql
Domain
backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.