Repository navigation
[type:fix] Immediate websocket master rediscovery and configurable ch… - #7812
Conversation
093e6b6 to
d91ecf1
Compare
Aias00
left a comment
There was a problem hiding this comment.
Approved. The debounce design is correct, and the trigger condition is scoped more tightly than the description suggests — which is the part I was most worried about.
What I verified:
The callback is gated on wasMaster.
if (wasMaster && !this.isConnectedToMaster && !this.manuallyClosed.get() && Objects.nonNull(onMasterLostCallback)) {
onMasterLostCallback.accept(this);
}Two things this gets right that a naive version would not: a follower client losing its connection does not trigger a rediscovery, so only the client that actually owned the master role can start the cycle; and manuallyClosed prevents the service’s own close() from kicking off a rediscovery during shutdown — otherwise every graceful shutdown would write a burst of "master lost" into the timer.
One rediscovery per burst. rediscoveryScheduled.compareAndSet(false, true) collapses N simultaneous onMasterLost callbacks into a single 200 ms task, and the flag is cleared both in the task body and in the scheduling-failure catch. Combined with masterCheck() being synchronized, a failover where every client notices at once produces exactly one check rather than one per client.
Config handling is sound. websocketConfig.getMasterCheckInterval() <= 0 ? 60 : ... keeps the previous behaviour for anyone who does not set the new property, so this is additive. The timerTask null-guards in the standalone-mode path and the re-seeding of clients when the list empties are both defensive fixes to real gaps — masterCheck could previously leave the client list permanently empty.
One concern worth thinking about before merge:
An extended master outage becomes a ~5 Hz reconnect loop. The flag is reset inside the scheduled task before masterCheck() runs:
public void run(final TaskEntity taskEntity) {
rediscoveryScheduled.set(false);
masterCheck();
}If the master stays unreachable, masterCheck closes/creates clients, the newly created clients immediately report master-lost, and each report re-schedules another 200 ms check. That is a dramatic change from the previous fixed 60 s cadence, and it is now the normal path during an outage rather than an exceptional one. A backoff — growing the delay when two rediscoveries fire within a short window, or a simple attempt counter reset on a successful connection — would keep the fast failover while preventing an admin-cluster reconnect storm. Worth at least a comment stating the expected steady-state rate during a sustained outage.
Two minor notes:
- The 200 ms delay is a magic number in three places (task delay, and implicitly the debounce window). A named constant would document that it is "long enough for the demoted client to settle, short enough to feel immediate".
masterCheck()holds the service monitor for the whole reconnect sequence, so anonMasterLostarriving during a slow check blocks on the lock rather than being dropped — correct, but it means the CAS does not bound concurrency, only scheduling. Fine as is; just noting the semantics.
The test coverage (WebsocketSyncDataServiceTest, ShenyuWebsocketClientTest, WebsocketConfigTest) matching each of the four bottlenecks listed in the description is the right structure for a change that touches config, timer and connection lifecycle at once.
Fixes #7808.
Motivation
When ShenYu Admin runs in cluster mode and the current master fails over, the gateway's websocket connection to the old master drops or gets demoted, but the gateway remains stale until
WebsocketSyncDataService's background timer triggersmasterCheck().This issue was caused by four cooperating bottlenecks:
WebsocketSyncDataServicescheduledmasterCheck()with a fixed 60-second delay onAbstractRoundTask, which was not configurable viaWebsocketConfig/ application properties (shenyu.sync.websocket.master-check-interval).masterCheck(),CollectionUtils.isEmpty(clients)was only evaluated at the start of the method. When the dead/demoted master client was pruned by the while loop,clientsbecame empty and remained empty for another entire round (up to 60s) before re-seeding clients fromwebsocketConfig.getUrls().onClose),ShenyuWebsocketClientonly attempted to reconnect to its own dead URL via internal exponential backoff without notifyingWebsocketSyncDataServicethat the cluster master connection had been lost.isConnectedToMaster == truebecauseRUNNING_MODEwas only queried once ononOpen.Modifications
masterCheckInterval(defaults to 60 seconds) with getter, setter,equals,hashCode, andtoStringtoWebsocketConfig.WebsocketSyncDataService, usedwebsocketConfig.getMasterCheckInterval()when initializing the periodic round task.WebsocketSyncDataService.masterCheck(), re-evaluatedCollectionUtils.isEmpty(clients)after the client pruning loop. If all clients were removed,clientsis immediately re-seeded fromwebsocketConfig.getUrls().ShenyuWebsocketClientacceptingConsumer<ShenyuWebsocketClient> onMasterLostCallback.onClose(), ifwasMaster && !this.manuallyClosed.get(),onMasterLostCallbackis invoked.onMessage()forRUNNING_MODE, if cluster mode reportswasMaster && !isMaster && !this.manuallyClosed.get(),onMasterLostCallbackis invoked.WebsocketSyncDataService,onMasterLostschedules a debounced one-shotmasterCheck()(after a 200ms delay) so the gateway immediately converges to the newly elected master without waiting for the full periodic interval.ShenyuWebsocketClient.healthCheck(), if the connection is open and the running mode is not standalone,DataEventTypeEnum.RUNNING_MODE.name()is sent alongside heartbeats to promptly catch leadership demotions.WebsocketConfigTestfor getter/setter, equals, hashCode, and toString.WebsocketSyncDataServiceTestverifying configurable check interval, same-pass re-seeding when all clients are removed, and master loss notification triggering rediscovery.ShenyuWebsocketClientTestcoveringonClosenotifications,manuallyClosedsuppression,RUNNING_MODEdemotion notification, and clusterhealthCheckprobing.Verification
shenyu-sync-data-websocketunit tests passed (127/127)shenyu-spring-boot-starter-sync-data-websocketunit tests passed (4/4)