feat(java): add cluster metadata and leader redirection to TCP client - #3745
Conversation
Java clients connected to a VSR follower had no way to discover the cluster leader, so operations against a multi-node cluster failed until the user manually pointed the client at the right node. Add the GET_CLUSTER_METADATA command with cluster roster models and a binary deserializer. After login the async TCP client fetches the roster and, when a healthy leader with an enabled TCP endpoint lives elsewhere, reconnects to it and replays the login. Redirections are capped at 3 consecutive hops so two nodes both claiming leadership cannot ping-pong a client, and every failure on the redirect path is non-fatal: the client stays on the current node. Mirrors the Rust and Go SDKs. Covered by a leader_redirection BDD scenario wired into the cluster docker-compose setup.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3745 +/- ##
============================================
- Coverage 76.27% 74.75% -1.53%
- Complexity 969 1046 +77
============================================
Files 1325 1313 -12
Lines 163541 148495 -15046
Branches 136421 123806 -12615
============================================
- Hits 124742 111003 -13739
+ Misses 35097 34015 -1082
+ Partials 3702 3477 -225
🚀 New features to boost your workflow:
|
slbotbm
left a comment
There was a problem hiding this comment.
The changes look alright to me. One comment though:
Let us consider the following setup: users A and B, follower F, and leader L. users A and B login through follower using the following code:
AsyncIggyTcpClient client = ...;
client.users().login("userA", "passwordA");
client.users().login("userB", "passwordB");If the two users utilize the same client as above, the following happens:
- A calls login(). Its initial authentication runs on F.
- B calls login() concurrently. Its initial authentication also runs on F.
- A enters the serialized redirection step, discovers L, switches the shared client connection to L, and replays A’s login there.
- B’s redirection step runs next. It checks the current connection, which now points to L.
- Because L is already the leader, B’s login is not replayed.
- B’s future returns B’s identity from the earlier login on F, but the active connection to L is authenticated as A.
This happens because the initial login happens before the serialization:
loginWithoutRedirect(username, password)
.thenCompose(identity -> redirectionHook.afterLogin(...));If this is to be fixed, you should serialize the complete login-and-redirection operation.
If this is not a concern, please feel free to merge the PR.
|
@slbotbm Thanks for the review - the race you describe is real. I would merge it as is, as the scenario itself falls outside the client's contract: a TCP client wraps a single connection, and the server keeps one session per connection, so the client is inherently single-user. Even with the complete login-and-redirection operation serialized, the second login would still replace the connection's identity, and user A's caller would silently end up on a connection authenticated as user B. Multiple users need separate client instances. |
Java clients connected to a VSR follower had no way to discover
the cluster leader, so operations against a multi-node cluster
failed until the user manually pointed the client at the right
node.
Add the GET_CLUSTER_METADATA command with cluster roster models
and a binary deserializer. After login the async TCP client
fetches the roster and, when a healthy leader with an enabled
TCP endpoint lives elsewhere, reconnects to it and replays the
login. Redirections are capped at 3 consecutive hops so two
nodes both claiming leadership cannot ping-pong a client, and
every failure on the redirect path is non-fatal: the client
stays on the current node. Mirrors the Rust and Go SDKs.
Covered by a leader_redirection BDD scenario wired into the
cluster docker-compose setup.