OA-1090: silent write failures and stale idle connections in ServerChannels#176
Open
siddharthteotia wants to merge 5 commits into
Open
OA-1090: silent write failures and stale idle connections in ServerChannels#176siddharthteotia wants to merge 5 commits into
siddharthteotia wants to merge 5 commits into
Conversation
PinotServerStreamingQueryClient cached gRPC clients in a ConcurrentHashMap that grew monotonically — clients were never closed or removed when servers left the cluster. This caused stale ManagedChannel connections to accumulate over the lifetime of the broker. The fix adds a cleanupStaleClients() method that compares cached clients against the current enabled server instance map and closes/removes any that are no longer needed. GrpcBrokerRequestHandler now implements ClusterChangeHandler and is registered for INSTANCE_CONFIG changes, so cleanup runs automatically whenever servers are added or removed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove ClusterChangeHandler implementation — request handlers don't implement that interface in this codebase. Instead, call cleanupStaleClients() opportunistically from retryUnhealthyServer(), which already runs periodically via the failure detector's retry thread. This keeps the fix self-contained within GrpcBrokerRequestHandler. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add try-catch around close() in cleanupStaleClients so one failing client doesn't prevent cleanup of remaining stale clients - Add @VisibleForTesting getter for streamingQueryClient - Add 13 tests covering: - cleanupStaleClients: single/multiple removals, port specificity, empty maps, idempotency, close() exception resilience - retryUnhealthyServer integration: stale client cleanup on retry, return states (UNHEALTHY/UNKNOWN/HEALTHY) for each code path Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Assert return value in testRetryUnhealthyServerCleansUpStaleClients - Add testRetryCleanupAndHealthyReturnInSameCall: verifies stale client cleanup AND healthy return for the retried server happen in one call Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rverChannels Two bugs in ServerChannels caused queries to hang until timeout instead of failing fast when a server-side Netty connection was unhealthy: 1. writeAndFlush() failure not propagated: The ChannelFuture listener that fires after every write never checked f.isSuccess(). A write to a dead channel would silently complete the listener, mark the request as sent, and leave the query waiting forever. Fixed by calling QueryRouter.markServerDown() on write failure so all in-flight queries on that channel fail immediately. 2. No idle connection eviction: SO_KEEPALIVE relied on OS-level TCP keepalive (~2h default on Linux). A silently hung connection (network partition, frozen process) would never trigger channelInactive(), and isActive() would still return true. Added an IdleStateHandler (default 300s, configurable via NettyConfig.channelIdleTimeoutSeconds) that closes fully idle channels, firing channelInactive() and triggering the existing markServerDown() path. Also added CONNECT_TIMEOUT_MILLIS (default 10s, configurable via NettyConfig.channelConnectTimeoutMs) to the Bootstrap so that bootstrap.connect().sync() cannot block indefinitely when a server is unreachable. Tests added: - testWriteFailureMarksServerDown: mocks a failed ChannelFuture and verifies QueryRouter.markServerDown() is called and markRequestSent() is not. - testIdleConnectionEviction: connects with a 1s idle timeout and verifies the channel is closed and markServerDown() is called within 5s. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two independent bugs in
ServerChannelswhere dead or stale Netty connections caused queries to silently hang until timeout instead of failing fast.Bug 1 —
writeAndFlush()failure not propagatedBefore (broken path):
After (fixed path):
Bug 2 — No idle connection eviction (silent hung TCP)
Before (broken path):
After (fixed path):
Changes
NettyConfig.javachannelIdleTimeoutSeconds(default 300s) andchannelConnectTimeoutMs(default 10s)ServerChannels.javawriteAndFlushlistener; addIdleStateHandler; addCONNECT_TIMEOUT_MILLISto BootstrapServerChannelsTest.javaConfiguration (via
NettyConfig):channel.idle.timeout.seconds— how long a fully idle connection lives before eviction (default: 300s)channel.connect.timeout.ms— max timebootstrap.connect().sync()can block (default: 10s)Testing Done
testWriteFailureMarksServerDown— mocks a failedChannelFuture; verifiesQueryRouter.markServerDown()is called andmarkRequestSent()is NOT calledtestIdleConnectionEviction— connects with a 1s idle timeout; verifies the channel is closed byIdleStateHandlerandmarkServerDown()fires viachannelInactive()within 5stestConnecttests (both NIO and native transport) still passFixes: OA-1090