Fix: Add defensive type validation to prevent silent cluster refresh failure.#6993
Fix: Add defensive type validation to prevent silent cluster refresh failure.#6993ngyngcphu wants to merge 1 commit intoredisson:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a defensive guard in the Redis protocol decoder to detect when a CLUSTER NODES request is incorrectly bound to a non-topology response (e.g., PONG), turning what would become a silent ClassCastException into an explicit failure that upstream retry/scheduling logic can handle (per issue #6992).
Changes:
- Add a
CLUSTER NODESresponse-type validation inCommandDecoder.completeResponse. - Convert mismatched response types into a
RedisExceptionviatryFailureinstead of completing the promise with an invalid type.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| && "CLUSTER".equals(data.getCommand().getName()) | ||
| && "NODES".equals(data.getCommand().getSubName()) | ||
| && !(result instanceof List)) { | ||
| data.tryFailure(new RedisException("Response type mismatch for command: CLUSTER NODES")); |
There was a problem hiding this comment.
The new RedisException message is quite generic and drops the contextual details used elsewhere in this decoder (e.g., command info via LogHelper). Consider including the expected vs actual response type (and the command via LogHelper.toString(data)) so this failure is diagnosable from logs without additional instrumentation.
| data.tryFailure(new RedisException("Response type mismatch for command: CLUSTER NODES")); | |
| String commandInfo = LogHelper.toString(data); | |
| String actualType = (result == null) ? "null" : result.getClass().getName(); | |
| String message = "Response type mismatch for command " + commandInfo | |
| + ": expected java.util.List (e.g. List<ClusterNodeInfo>), but got " + actualType; | |
| data.tryFailure(new RedisException(message)); |
| && "CLUSTER".equals(data.getCommand().getName()) | ||
| && "NODES".equals(data.getCommand().getSubName()) |
There was a problem hiding this comment.
This check hard-codes command/subcommand names as string literals. Since RedisCommands already defines REDIS_CLUSTER_NODES, consider using its getName()/getSubName() values (or a shared constant) to avoid typos and keep this consistent if command naming ever changes in one place.
| && "CLUSTER".equals(data.getCommand().getName()) | |
| && "NODES".equals(data.getCommand().getSubName()) | |
| && RedisCommands.REDIS_CLUSTER_NODES.getName().equals(data.getCommand().getName()) | |
| && RedisCommands.REDIS_CLUSTER_NODES.getSubName().equals(data.getCommand().getSubName()) |
| // Fix for: https://github.com/redisson/redisson/issues/6992 | ||
| // CLUSTER NODES expects List<ClusterNodeInfo>, not String | ||
| // This prevents queue-based response binding errors from causing | ||
| // ClassCastException in CompletableFuture handlers | ||
| if (data.getCommand() != null | ||
| && "CLUSTER".equals(data.getCommand().getName()) | ||
| && "NODES".equals(data.getCommand().getSubName()) | ||
| && !(result instanceof List)) { | ||
| data.tryFailure(new RedisException("Response type mismatch for command: CLUSTER NODES")); | ||
| return; | ||
| } |
There was a problem hiding this comment.
This change introduces new behavior (failing CLUSTER NODES when a non-List reply is bound) but there doesn’t appear to be a unit/integration test covering it. Adding a regression test that simulates a queued CLUSTER NODES receiving a +PONG response would help prevent the silent-refresh regression from reappearing.
This PR fixes a critical bug where a queue based response binding error causes
CLUSTER NODESto receive aPONGresponse instead of cluster topology data. The resultingClassCastExceptionis silently swallowed byCompletableFuture, causing the cluster topology refresh to stop permanently.The fix detects type mismatches early and converts them into recoverable errors, allowing the existing retry mechanism to handle the situation gracefully instead of failing silently.
Fixes: #6992