Skip to content

Fix double initialization of pooled Connection in TrackingConnectionPool - #4547

Merged
ggivo merged 6 commits into
masterfrom
topic/ggivo/fix-tracking-pool-double-init
Jun 8, 2026
Merged

ggivo merged 6 commits into
masterfrom
topic/ggivo/fix-tracking-pool-double-init

Conversation

@ggivo

@ggivo ggivo commented May 29, 2026 •

Copy link
Copy Markdown
Collaborator

What

When you get a Connection from a TrackingConnectionPool (i.e., every MultiDbClient database), it was being initialized twice: once by Connection.Builder.build(), and a second time by FailFastConnectionFactory.makeObject().

Two visible effects, per pooled connection:

  • An extra HELLO/CLIENT round-trip on the wire.
  • The default PUBSUB_CONSUMER registered twice on the push-consumer chain.

Present since v7.0.0.

Why it happened

Connection.Builder.build() has always initialized the connection it returns. The pool factory then ran initialization a second time on top, after super.makeObject() had already done it.

Fix

Split construction from initialization so the pool factory can interpose without re-initializing:

  • New protected createConnection() hook on Connection.Builder — subclasses (e.g. CacheConnection.Builder) override this to pick the concrete type.
  • Package-private buildUninitialized() lets ConnectionFactory build without auto-init.
  • ConnectionFactory exposes a protected initialize() hook that FailFastConnectionFactory overrides to wrap the (now sole) init in its existing tracking — which, as a side benefit, finally makes forceDisconnect() able to interrupt a connection mid-handshake.

Public API is unchanged.

Tests

New TrackingConnectionPoolInitTest checks that a pool-obtained connection has exactly one PUBSUB_CONSUMER registered. Fails on the buggy version, passes here.

Follow-up

Connection.initializeFromClientConfig() has no out-of-package caller after this — its visibility can be tightened in a separate PR for the next major release.


Note

Medium Risk
Touches core connection pooling and handshake initialization paths used by MultiDbClient; behavior fix is targeted but affects every pooled connection lifecycle.

Overview
Fixes double initialization of pooled Connection instances from TrackingConnectionPool (used by multi-DB clients): Connection.Builder.build() and the pool factory both ran initializeFromClientConfig(), causing an extra HELLO/CLIENT handshake and duplicate PUBSUB_CONSUMER registration.

Construction vs initialization is split without changing public build() behavior: Connection.Builder adds createConnection() (subclass hook, e.g. CacheConnection) and package-private buildUninitialized(); ConnectionFactory builds uninitialized connections and calls a new protected initialize() that runs init once.

FailFastConnectionFactory drops the second init in makeObject() and overrides initialize() to add/remove connections in factoryTrackedObjects during the sole init, so forceDisconnect() can interrupt in-flight HELLO/AUTH/CLIENT handshakes.

Adds TrackingConnectionPoolInitTest to assert single init and a single pub/sub push consumer on borrowed connections.

Reviewed by Cursor Bugbot for commit 3d1b06b. Bugbot is set up for automated code reviews on this repo. Configure here.

Connection.Builder.build() already initializes the connection, so
FailFastConnectionFactory.makeObject() was running initializeFromClientConfig()
a second time. This re-registered PUBSUB_CONSUMER and re-ran the HELLO/CLIENT
handshake on every MultiDbClient database connection.
Split Builder.build() into buildUninitialized() + init so the existing
factoryTrackedObjects set can wrap the (now sole) init — preserving the
ability for forceDisconnect() to interrupt a connection mid-handshake.
@github-actions

github-actions Bot commented May 29, 2026 •

Copy link
Copy Markdown

Test Results

  203 files  ±0    203 suites  ±0   9m 28s ⏱️ +17s
7 767 tests ±0  7 108 ✅  - 597  659 💤 +597  0 ❌ ±0 
7 787 runs  ±0  7 128 ✅  - 597  659 💤 +597  0 ❌ ±0 

Results for commit 3d1b06b. ± Comparison against base commit c9f05c2.

This pull request skips 597 tests.
redis.clients.jedis.commands.commandobjects.CommandObjectsArrayCommandsTest[1] ‑ testArcountMissingKey()[1]
redis.clients.jedis.commands.commandobjects.CommandObjectsArrayCommandsTest[1] ‑ testArcountMissingKeyBinary()[1]
redis.clients.jedis.commands.commandobjects.CommandObjectsArrayCommandsTest[1] ‑ testArdel()[1]
redis.clients.jedis.commands.commandobjects.CommandObjectsArrayCommandsTest[1] ‑ testArdelBinary()[1]
redis.clients.jedis.commands.commandobjects.CommandObjectsArrayCommandsTest[1] ‑ testArdelrange()[1]
redis.clients.jedis.commands.commandobjects.CommandObjectsArrayCommandsTest[1] ‑ testArdelrangeBinary()[1]
redis.clients.jedis.commands.commandobjects.CommandObjectsArrayCommandsTest[1] ‑ testArget()[1]
redis.clients.jedis.commands.commandobjects.CommandObjectsArrayCommandsTest[1] ‑ testArgetBinary()[1]
redis.clients.jedis.commands.commandobjects.CommandObjectsArrayCommandsTest[1] ‑ testArgetrange()[1]
redis.clients.jedis.commands.commandobjects.CommandObjectsArrayCommandsTest[1] ‑ testArgetrangeBinary()[1]
…

♻️ This comment has been updated with latest results.

@jit-ci

jit-ci Bot commented May 29, 2026 •

Copy link
Copy Markdown

🛡️ Jit Security Scan Results

CRITICAL HIGH MEDIUM

✅ No security findings were detected in this PR


Security scan by Jit

@ggivo ggivo added this to the 8.0.0 milestone May 29, 2026
@ggivo
ggivo requested review from atakavci and uglide May 29, 2026 17:30
ggivo added a commit that referenced this pull request May 29, 2026
Follow-up to #4547: no out-of-package caller remains.
…Test

Adds pooledConnectionInitializedExactlyOnce(), which mocks the Connection
constructor and asserts initializeFromClientConfig() runs exactly once on a
pooled borrow. Complements the push-consumer assertion without depending on
TcpMockServer behaviour.
ggivo added a commit that referenced this pull request Jun 1, 2026
…in TrackingConnectionPool

A Connection obtained through a TrackingConnectionPool (the pool backing
every MultiDbClient database) was initialized twice: once by
Connection.Builder.build() and again by FailFastConnectionFactory.makeObject().
Each pooled connection therefore ran an extra HELLO/AUTH/CLIENT handshake
round-trip. Present since v7.0.0.

Split construction from initialization so the pool factory can interpose
without re-initializing:

- Connection.Builder gains a protected createConnection() hook (overridden
  by CacheConnection.Builder) and a package-private buildUninitialized() so
  pool factories can build without auto-init. Public build() still
  initializes once for external callers.
- ConnectionFactory.build() now calls buildUninitialized() followed by a new
  protected initialize() hook.
- FailFastConnectionFactory overrides initialize() to wrap the (now sole)
  init in its in-flight tracking, so forceDisconnect() can also interrupt a
  connection blocked mid-handshake. Its makeObject() no longer re-initializes.

Public API is unchanged.

Backport of #4547.
@ggivo ggivo added waiting-for-triage Still needs to be triaged status: waiting-for-review and removed waiting-for-triage Still needs to be triaged labels Jun 2, 2026

@uglide uglide left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@atakavci atakavci left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good in terms of functionality and consistent with existing behaviour.
i have no comments other than the one related to the way we are changing the design and responsibilities. The factory is kind of taking on the role of builder.
Plus, i like to stay away from changing core types(as much as possible) for specific client setup.
Right now i am trying to explore the ways if we can achieve this with changes limited to MultiDb.


public Connection build() {
Connection conn = new Connection(this);
Connection conn = createConnection();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this build() gets no use anymore.

@ggivo
ggivo merged commit 99b4c8c into master Jun 8, 2026
19 of 20 checks passed
@ggivo
ggivo deleted the topic/ggivo/fix-tracking-pool-double-init branch June 8, 2026 07:37
ggivo added a commit that referenced this pull request Jun 10, 2026
…-private (#4548)

* Fix double initialization of pooled Connection in TrackingConnectionPool

Connection.Builder.build() already initializes the connection, so
FailFastConnectionFactory.makeObject() was running initializeFromClientConfig()
a second time. This re-registered PUBSUB_CONSUMER and re-ran the HELLO/CLIENT
handshake on every MultiDbClient database connection.
Split Builder.build() into buildUninitialized() + init so the existing
factoryTrackedObjects set can wrap the (now sole) init — preserving the
ability for forceDisconnect() to interrupt a connection mid-handshake.

* format

* Tighten Connection.initializeFromClientConfig() to package-private

Follow-up to #4547: no out-of-package caller remains.

* test: guard pooled-connection double-init via HELLO count instead of mock-construction
mergify Bot added a commit to ArcadeData/arcadedb that referenced this pull request Jul 1, 2026
Bumps [redis.clients:jedis](https://github.com/redis/jedis) from 7.5.2 to 7.5.3.
Release notes

*Sourced from [redis.clients:jedis's releases](https://github.com/redis/jedis/releases).*

> 7.5.3
> -----
>
> This is a maintenance release with fixes for FT.HYBRID queries and connection pool handshake overhead. Recommended for any deployment using FT.HYBRID with `Combiner.as(...)` or `MultiDbClient`.
>
> 🐛 Bug Fixes
> -----------
>
> * Fixes `FT.HYBRID` `COMBINE` argument count omitting `YIELD_SCORE_AS` tokens, causing the command to be rejected when a combined-score alias is set via `Combiner.as(...)`. Fix by [`@​guyroyse`](https://github.com/guyroyse) in [redis/jedis#4575](https://redirect.github.com/redis/jedis/pull/4575), backported to `7.5.x` in [redis/jedis#4576](https://redirect.github.com/redis/jedis/pull/4576).
> * Fixes double initialization of a pooled `Connection` in `TrackingConnectionPool` (used by `MultiDbClient`). [redis/jedis#4547](https://redirect.github.com/redis/jedis/pull/4547), backported to `7.5.x` in <redis/jedis@dd42b3e>.
>
> **Full Changelog**: <redis/jedis@v7.5.2...v7.5.3>


Commits

* [`d0ca056`](redis/jedis@d0ca056) Add Redis 8.8 GA to test matrix on 7.5.x ([#4578](https://redirect.github.com/redis/jedis/issues/4578))
* [`2e73f17`](redis/jedis@2e73f17) Backport [#4571](https://redirect.github.com/redis/jedis/issues/4571) and [#4573](https://redirect.github.com/redis/jedis/issues/4573) to 7.5.x: stabilize XINFO STREAM tests ([#4577](https://redirect.github.com/redis/jedis/issues/4577))
* [`458bccf`](redis/jedis@458bccf) Backport [#4575](https://redirect.github.com/redis/jedis/issues/4575) to 7.5.x: fix FT.HYBRID COMBINE arg count for YIELD\_SCORE\_AS (...
* [`dd42b3e`](redis/jedis@dd42b3e) [7.5] Backport [#4547](https://redirect.github.com/redis/jedis/issues/4547): Fix double initialization of pooled Connection in Track...
* See full diff in [compare view](redis/jedis@v7.5.2...v7.5.3)
  
[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility\_score?dependency-name=redis.clients:jedis&package-manager=maven&previous-version=7.5.2&new-version=7.5.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
  
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot show  ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants