fix(store): refresh gRPC channels after address changes - #3130
fix(store): refresh gRPC channels after address changes#3130bitflicker64 wants to merge 11 commits into
Conversation
218b681 to
26218cb
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates the HStore gRPC client channel/stub lifecycle so that when a stable Store target (e.g., DNS name) resolves to a different address set, the client discards the prior channel pool and rebuilds related stub pools to avoid getting stuck on failed transports (issue #3124).
Changes:
- Add per-target “resolved address set” fingerprinting and retire/replace cached channel pools when the fingerprint changes (or when a previously-unresolved target first resolves).
- Rebuild blocking and async stub pools when they no longer correspond to the current channel pool, with concurrency ordering to prevent stale work from reintroducing retired channels.
- Add regression tests covering address changes and concurrent refresh/stub-build interleavings.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java | Adds resolved-address fingerprinting, refresh/retire logic for channel pools, and stub-pool rebuild safeguards under concurrent refresh. |
| hugegraph-store/hg-store-test/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClientTest.java | Adds refresh-focused tests validating channel replacement, stub-pool rebuild, and concurrency ordering behavior. |
| hugegraph-store/hg-store-test/src/main/java/org/apache/hugegraph/store/client/ClientSuiteTest.java | Introduces a small suite to run the refresh regression tests together. |
Comments suppressed due to low confidence (1)
hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java:172
- When (re)building the async stub pool, the loop always selects
targetChannels[index]for every slot, so all cached async stubs share a single channel. This defeats the channel pool and undermines concurrency/failover across channels. Bind each stub to its corresponding channel (or at least distribute across the pool) by using the loop index.
IntStream.range(0, concurrency).parallel().forEach(i -> {
ManagedChannel channel = targetChannels[index];
AbstractAsyncStub stub = getAsyncStub(channel);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: Address refresh can abort active RPCs, adds synchronous DNS resolution to every request path, and does not preserve the existing gRPC target contract; the async refresh race also lacks equivalent coverage. Evidence: static review across six independent lanes; mvn test -pl hugegraph-store/hg-store-test -am -Dtest=AbstractGrpcClientTest -DfailIfNoTests=false passed 4 tests.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3130 +/- ##
============================================
- Coverage 39.13% 34.73% -4.40%
- Complexity 264 498 +234
============================================
Files 770 782 +12
Lines 65779 67086 +1307
Branches 8726 8956 +230
============================================
- Hits 25742 23304 -2438
- Misses 37288 41178 +3890
+ Partials 2749 2604 -145 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Refresh cached channel pools when a Store target resolves to a new address. Rebuild stale blocking and async stub pools, and guard concurrent resolution and publication races. Fixes apache#3124
- move graceful retirement to a cleanup scheduler - force-close partial pools after creation failures - validate cached stubs against channels by index - cover saturation, interruption, and drain deadlines
a08ac4b to
ddeef7a
Compare
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: DNS refresh can fail under the default security policy, and QueryV2 can race channel retirement. Evidence: six independent exact-head lanes; 14/14 focused Java tests and git diff --check passed; all visible exact-head checks passed.
Channel refresh resolved DNS on whichever thread asked for a stub. Under the default launcher that thread can be a Gremlin worker, and HugeSecurityManager denies it socket access, so an HStore-backed request could fail with a SecurityException instead of using the healthy pool. - run resolution, replacement creation and retirement on a channel maintenance executor, keeping the last healthy pool when resolution fails or times out - build the first pool for a target once its address is known, so a cold start no longer creates and immediately retires a pool - replace the per-target refresh lock with a single-flight task map that the cold path can also wait on, and throttle from both submission and completion - route QueryV2Client through the guarded async stub path instead of taking a channel straight from the pool, and restrict getChannels to subclasses - drop the channel monitor from stub acquisition: publishing a pool before retiring the previous one already orders the check, and the monitor is static - log refresh failures and pool replacements, which the executor otherwise discards, and never let a denied thread creation wedge refresh for a target - parse targets with URI, rejecting resolver schemes such as unix:/path that were resolved as a host named after the scheme - fold the blocking and async stub acquisition loops into one implementation
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. The initial refresh deadline can suppress DNS monitoring for the lifetime of a target when the JVM's nanoTime origin is negative; three independent current-head review lanes converged on this issue.
…tore disabled The Server wrapper now writes auth.admin_pa from the auth Secret alongside usePD and pd.peers, so an auth-enabled release keeps its configured admin password with init_store.enabled=false instead of silently falling back to the public default. The Secret value is rejected when it contains properties-parser metacharacters that would inject config lines or store a different password than the Secret holds. The new hubble component deploys the Hubble UI as a single-replica Deployment with pd and direct wiring modes, optional Ingress and H2 persistence, schema validation, render-time guards, docs, and CI coverage. PD-meta installs (auth enabled, or Hubble in pd mode) also announce the Server client Service URL to PD via server.urls_to_pd and server.deploy_in_k8s so discovery clients receive a resolvable address instead of the 0.0.0.0 default, and the Hubble wrapper writes server.host so current images bind all interfaces. Because current Hubble images authenticate their login against the cluster, rendering Hubble without server.auth fails unless explicitly overridden. The CI invalid-value step now fails on every case rather than only its last line, and positive renders cover both Hubble modes. Validated against a composition of master 1716c77 plus the current heads of apache#3119 (edf07d0), apache#3126 (b40c42f), and apache#3130 (198de19): fresh auth-enabled installs reach Ready with zero restarts, the admin credential comes from the Secret while unauthenticated and default-password requests get 401, and Hubble logs in with the Secret credential and reads cluster metadata through PD discovery, with its H2 metadata persisted on the PVC.
|
Codecov follow-up for the earlier 0% patch report at |
|
Final-head CI note for 6beea6f: all 17 GitHub checks passed, and the Store job generated and discovered the expected JaCoCo reports. Its legacy codecov-action v3.0.0 upload was rejected by Codecov with HTTP 429 and an expected retry window of 1,703 seconds, after which the action intentionally exited 0; consequently Codecov never registered the final commit. The cooldown has elapsed, but GitHub does not allow the fork contributor to rerun the completed Apache job. I am briefly closing and reopening this PR to retrigger trusted CI on the unchanged SHA, without modifying history or the four-file diff. The regenerated Java 11 Store profile still passes 27/27 and records QueryV2 line 47 as mi=0, ci=5 and line 58 as mb=0, cb=2. |
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: The refresh implementation still has a non-atomic stub-to-channel handoff and a shared resolver pool that can pause refreshes for every target when DNS blocks. Evidence: exact-head static review across six independent lanes; current-head checks are green, while these races remain distinct from the previously resolved SecurityManager, target parsing, stub distribution, and nanoTime findings.
…tore disabled The Server wrapper now writes auth.admin_pa from the auth Secret alongside usePD and pd.peers, so an auth-enabled release keeps its configured admin password with init_store.enabled=false instead of silently falling back to the public default. The Secret value is rejected when it contains properties-parser metacharacters that would inject config lines or store a different password than the Secret holds. The new hubble component deploys the Hubble UI as a single-replica Deployment with pd and direct wiring modes, optional Ingress and H2 persistence, schema validation, render-time guards, docs, and CI coverage. PD-meta installs (auth enabled, or Hubble in pd mode) also announce the Server client Service URL to PD via server.urls_to_pd and server.deploy_in_k8s so discovery clients receive a resolvable address instead of the 0.0.0.0 default, and the Hubble wrapper writes server.host so current images bind all interfaces. Because current Hubble images authenticate their login against the cluster, rendering Hubble without server.auth fails unless explicitly overridden. The CI invalid-value step now fails on every case rather than only its last line, and positive renders cover both Hubble modes. Validated against a composition of master 1716c77 plus the current heads of apache#3119 (edf07d0), apache#3126 (b40c42f), and apache#3130 (198de19): fresh auth-enabled installs reach Ready with zero restarts, the admin credential comes from the Secret while unauthenticated and default-password requests get 401, and Hubble logs in with the Secret credential and reads cluster metadata through PD discovery, with its H2 metadata persisted on the PVC.
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: Cold-target initialization can still run channel creation from a restricted Gremlin request, and the fingerprint parser accepts a DNS target form that gRPC 1.39 cannot build. Evidence: AbstractGrpcClient.java lines 131, 490, and 546; ExecutorPool.createExecutor() uses a lazy SynchronousQueue executor with CallerRunsPolicy; gRPC 1.39 DnsNameResolverProvider requires a slash-prefixed URI path.
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: The change eagerly creates 129 executor threads, retains target-keyed state indefinitely, and can publish a channel pool containing null entries after an Error. Focused validation passed 29/29 tests, while the latest dependency-check and Codecov checks are failing. Evidence: static review of AbstractGrpcClient.java:54-93 and 444-485; mvn -q test -pl hugegraph-store/hg-store-test -am -Dtest=AbstractGrpcClientTest -Dsurefire.failIfNoSpecifiedTests=false -Djacoco.skip=true; gh pr checks 3130 -R apache/hugegraph.
|
Current-head CI triage for fad20a3: the dependency-check job did not execute dependency analysis; actions/setup-java failed while downloading JDK 11 after repeated socket hang up errors, so that failure is external setup/network noise. The Codecov failures are current and substantive as reported: patch coverage is 0% for two changed QueryV2Client lines and project coverage is 34.73%. The Store job itself passed, but its uploaded aggregate coverage did not demonstrate those changed lines to Codecov. The three new exact-head review findings were validated and answered inline; all three require code/lifecycle follow-up before the review can be considered clear. |
Design direction to validate: keep Store identity at the DNS target layerThe current head ( In Kubernetes, HugeGraph should ideally know a stable logical target such as flowchart LR
HG["HugeGraph Store client<br/>stable DNS target only"]
CH["gRPC ManagedChannel"]
NR["gRPC NameResolver<br/>re-resolution / reconnect"]
DNS["Kubernetes DNS / Endpoint"]
OLD["old Store Pod<br/>10.0.0.8"]
NEW["replacement Store Pod<br/>10.0.0.19"]
HG --> CH --> NR --> DNS
DNS -. "before replacement" .-> OLD
DNS -- "after replacement" --> NEW
The intended responsibility split would be: Under that model, HugeGraph would not normally maintain resolved IP This is a hypothesis, not yet a replacement proposal. Before changing this PR,
The key acceptance test is: I am running three independent validation tracks next: gRPC/JDK source and |
Validation update: native gRPC recovery works; refocus this PR on HugeGraph lifecycle gapsThree independent tracks now converge: gRPC/JDK source analysis, a Java 11 What the runtime experiment provedThe final Server distribution resolves the behavior-driving gRPC artifacts Using Java 11.0.18, the #3126-equivalent Java security property
This directly refutes the assumption that a Store IP change inherently requires The source path explains the result:
Separate blocking HugeGraph issue found by the blind analysisThe current PR evicts the exact This can prevent the newly added eviction path from recovering even when gRPC The same audit must cover stream errors and the separate channel caches in Proposed refactor boundaryflowchart LR
K8S["Kubernetes<br/>Pod IP + DNS/Endpoint"]
JDK["Java DNS policy<br/>finite TTL from #3126"]
GRPC["gRPC ManagedChannel<br/>resolve + reconnect + transport"]
HG["HugeGraph<br/>logical node/session + bounded retry"]
K8S --> JDK --> GRPC --> HG
The default refactor should remove the duplicated external IP fingerprint, Only if a real Kubernetes run falsifies native recovery should we add the Remaining integration gateThe minimal Docker experiment did not exercise full HugeGraph, CoreDNS, I will post the exact result and cleanup state before recommending the final |
K8s A/B result: recovery exists, but the current PR breaks the in-flight retryI ran a resource-bounded
The merge image includes #3126 and has What this provesThis matches the independent source-level analysis. The failure is no longer hypothetical: the current PR shortens the failure to the configured retry window, but it cannot recover the request that encountered the Store replacement. Required refactor before merge
One test nuance: short client-side probe timeouts can still leave Server-side requests running; several timed-out probes later committed. Acceptance therefore needs unique operation IDs and must distinguish “HTTP response observed” from “write eventually committed”. Based on this K8s run, I would not merge the current channel-refresh implementation as-is. The small, coherent fix is stable FQDN + native gRPC resolution + generation-aware HugeGraph node/session retry. |
imbajin
left a comment
There was a problem hiding this comment.
Blocking: no. Summary: No new actionable findings were identified on the exact head after local review and focused Java 11 validation. Evidence: mvn -q test -pl hugegraph-store/hg-store-test -am -Dtest=AbstractGrpcClientTest -Dsurefire.failIfNoSpecifiedTests=false -Djacoco.skip=true (37/37 passed); gh -R apache/hugegraph pr checks 3130 (17/17 passed).
Purpose of the PR
HStore caches gRPC channels and blocking/asynchronous stubs by Store target.
When a stable target resolves to a different address, those pools can retain the
failed transport indefinitely even though the replacement Store is reachable.
This is the channel-lifecycle half of #3124. Complete recovery behind a stable
DNS name still requires the finite positive DNS TTL from #3126; this patch does
not bypass an indefinitely stale JVM DNS cache.
Main Changes
selecting a channel, with explicit first-run state and overflow-safe refresh
deadlines.
callers using bounded prestarted executors (4 maintenance, 4 initialization,
and 1 retirement thread), with independent targets initialized concurrently.
healthy pool across resolution, submission, creation, cleanup, and fatal-error
paths, and force-terminate every partial pool before propagating failure.
channel pool; route QueryV2 through that guarded path and clean up injected test
channels when they are replaced.
locks. Terminal Store notices evict exact node identities, stale sessions cannot
resurrect state, and late notices cannot evict a same-address replacement.
host:port,dns:///host:port, and bracketed IPv6 targets forfingerprinting; unsupported resolver schemes remain delegated to gRPC.
boundaries, target isolation, lifecycle races, partial creation, interrupts,
executor shutdown, Store-node eviction, QueryV2, and DNS deadline edge cases.
Verifying these changes
mvn test -pl hugegraph-store/hg-store-test -am -Dtest=AbstractGrpcClientTest -Dsurefire.failIfNoSpecifiedTests=false: 37 tests, 0 failures, 0 errors, 0 skips.git diff --check fad20a3c515018278869cc62e8162894373bff95..bdd8df11106d8b015228655ed140e70543469738: passed with no output.Does this PR potentially affect the following parts?
Documentation Status
Doc - TODODoc - DoneDoc - No NeedBlocking review follow-up (2026-08-13)
bdd8df11106d8b015228655ed140e70543469738, directly on prior PR headfad20a3c515018278869cc62e8162894373bff95.Error.