Repository navigation
Fix double initialization of pooled Connection in TrackingConnectionPool - #4547
Conversation
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.
Test Results 203 files ±0 203 suites ±0 9m 28s ⏱️ +17s Results for commit 3d1b06b. ± Comparison against base commit c9f05c2. This pull request skips 597 tests.♻️ This comment has been updated with latest results. |
🛡️ Jit Security Scan Results✅ No security findings were detected in this PR
Security scan by Jit
|
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.
…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.
atakavci
left a comment
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
this build() gets no use anymore.
…-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
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) [](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)
What
When you get a
Connectionfrom aTrackingConnectionPool(i.e., everyMultiDbClientdatabase), it was being initialized twice: once byConnection.Builder.build(), and a second time byFailFastConnectionFactory.makeObject().Two visible effects, per pooled connection:
HELLO/CLIENTround-trip on the wire.PUBSUB_CONSUMERregistered 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, aftersuper.makeObject()had already done it.Fix
Split construction from initialization so the pool factory can interpose without re-initializing:
createConnection()hook onConnection.Builder— subclasses (e.g.CacheConnection.Builder) override this to pick the concrete type.buildUninitialized()letsConnectionFactorybuild without auto-init.ConnectionFactoryexposes a protectedinitialize()hook thatFailFastConnectionFactoryoverrides to wrap the (now sole) init in its existing tracking — which, as a side benefit, finally makesforceDisconnect()able to interrupt a connection mid-handshake.Public API is unchanged.
Tests
New
TrackingConnectionPoolInitTestchecks that a pool-obtained connection has exactly onePUBSUB_CONSUMERregistered. 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
Connectioninstances fromTrackingConnectionPool(used by multi-DB clients):Connection.Builder.build()and the pool factory both raninitializeFromClientConfig(), causing an extra HELLO/CLIENT handshake and duplicatePUBSUB_CONSUMERregistration.Construction vs initialization is split without changing public
build()behavior:Connection.BuilderaddscreateConnection()(subclass hook, e.g.CacheConnection) and package-privatebuildUninitialized();ConnectionFactorybuilds uninitialized connections and calls a new protectedinitialize()that runs init once.FailFastConnectionFactorydrops the second init inmakeObject()and overridesinitialize()to add/remove connections infactoryTrackedObjectsduring the sole init, soforceDisconnect()can interrupt in-flight HELLO/AUTH/CLIENT handshakes.Adds
TrackingConnectionPoolInitTestto 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.