This is an automated email from the ASF dual-hosted git repository.

Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new 581bee9f15 [type:fix] Immediate websocket master rediscovery and 
configurable check interval (#7808) (#7812)
581bee9f15 is described below

commit 581bee9f15ce0a6778df6cc28eb210eb29ba7c70
Author: Harsh Raj <[email protected]>
AuthorDate: Mon Oct 19 12:53:42 2026 +0530

    [type:fix] Immediate websocket master rediscovery and configurable check 
interval (#7808) (#7812)
    
    Co-authored-by: Harsh <[email protected]>
---
 .../data/websocket/WebsocketSyncDataService.java   | 55 ++++++++++++--
 .../websocket/client/ShenyuWebsocketClient.java    | 49 +++++++++++++
 .../data/websocket/config/WebsocketConfig.java     | 30 +++++++-
 .../websocket/WebsocketSyncDataServiceTest.java    | 85 ++++++++++++++++++++++
 .../client/ShenyuWebsocketClientTest.java          | 71 ++++++++++++++++++
 .../data/websocket/config/WebsocketConfigTest.java | 12 ++-
 6 files changed, 292 insertions(+), 10 deletions(-)

diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/WebsocketSyncDataService.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/WebsocketSyncDataService.java
index c2f32418a2..76f3dc8946 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/WebsocketSyncDataService.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/WebsocketSyncDataService.java
@@ -25,6 +25,7 @@ import org.apache.shenyu.common.config.ShenyuConfig;
 import org.apache.shenyu.common.constant.Constants;
 import org.apache.shenyu.common.enums.RunningModeEnum;
 import org.apache.shenyu.common.timer.AbstractRoundTask;
+import org.apache.shenyu.common.timer.TaskEntity;
 import org.apache.shenyu.common.timer.Timer;
 import org.apache.shenyu.common.timer.TimerTask;
 import org.apache.shenyu.common.timer.WheelTimerFactory;
@@ -67,6 +68,8 @@ public class WebsocketSyncDataService implements 
SyncDataService {
 
     private final AtomicBoolean initialSyncReady = new AtomicBoolean();
 
+    private final AtomicBoolean rediscoveryScheduled = new 
AtomicBoolean(false);
+
     
     private final PluginDataSubscriber pluginDataSubscriber;
     
@@ -131,7 +134,8 @@ public class WebsocketSyncDataService implements 
SyncDataService {
             clients.add(createClient(url));
         }
         LOG.info("start check task...");
-        this.timer.add(timerTask = new AbstractRoundTask(null, 
TimeUnit.SECONDS.toMillis(60)) {
+        long intervalSeconds = websocketConfig.getMasterCheckInterval() <= 0 ? 
60 : websocketConfig.getMasterCheckInterval();
+        this.timer.add(timerTask = new AbstractRoundTask(null, 
TimeUnit.SECONDS.toMillis(intervalSeconds)) {
             @Override
             public void doRun(final String key, final TimerTask timerTask) {
                 masterCheck();
@@ -148,8 +152,10 @@ public class WebsocketSyncDataService implements 
SyncDataService {
         }
         if (CollectionUtils.isEmpty(clients)) {
             List<String> urls = websocketConfig.getUrls();
-            for (String url : urls) {
-                clients.add(createClient(url));
+            if (CollectionUtils.isNotEmpty(urls)) {
+                for (String url : urls) {
+                    clients.add(createClient(url));
+                }
             }
         }
         Set<URI> masterUrls = new LinkedHashSet<>();
@@ -165,7 +171,9 @@ public class WebsocketSyncDataService implements 
SyncDataService {
             // check running mode
             if (Objects.equals(runningMode, 
RunningModeEnum.STANDALONE.name())) {
                 LOG.info("admin running in standalone mode...");
-                timerTask.cancel();
+                if (Objects.nonNull(timerTask)) {
+                    timerTask.cancel();
+                }
                 return;
             }
             
@@ -188,6 +196,14 @@ public class WebsocketSyncDataService implements 
SyncDataService {
                 }
             }
         }
+        if (CollectionUtils.isEmpty(clients)) {
+            List<String> urls = websocketConfig.getUrls();
+            if (CollectionUtils.isNotEmpty(urls)) {
+                for (String url : urls) {
+                    clients.add(createClient(url));
+                }
+            }
+        }
     }
 
     private URI parseMasterUri(final String masterUrl) {
@@ -206,6 +222,34 @@ public class WebsocketSyncDataService implements 
SyncDataService {
         LOG.warn("Ignore invalid websocket master address [{}]", masterUrl);
         return null;
     }
+
+    private void onMasterLost(final ShenyuWebsocketClient client) {
+        if (closed) {
+            return;
+        }
+        LOG.info("master lost detected from websocket server[{}], scheduling 
rediscovery", client.getURI());
+        triggerMasterRediscovery();
+    }
+
+    private void triggerMasterRediscovery() {
+        if (closed) {
+            return;
+        }
+        if (rediscoveryScheduled.compareAndSet(false, true)) {
+            try {
+                this.timer.add(new TimerTask(200, TimeUnit.MILLISECONDS) {
+                    @Override
+                    public void run(final TaskEntity taskEntity) {
+                        rediscoveryScheduled.set(false);
+                        masterCheck();
+                    }
+                });
+            } catch (Exception ex) {
+                rediscoveryScheduled.set(false);
+                LOG.warn("Failed to schedule master rediscovery task", ex);
+            }
+        }
+    }
     
     @Override
     public synchronized void close() {
@@ -248,7 +292,8 @@ public class WebsocketSyncDataService implements 
SyncDataService {
                 this.aiProxyApiKeyDataSubscribers,
                 namespaceId,
                 serverProperties.getPort(),
-                websocketConfig.isInitialSyncReadiness() ? initialSyncReady : 
null);
+                websocketConfig.isInitialSyncReadiness() ? initialSyncReady : 
null,
+                this::onMasterLost);
     }
     
     /**
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClient.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClient.java
index 86df7d5af9..663fe8b83e 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClient.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClient.java
@@ -60,6 +60,7 @@ import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 /**
  * The type shenyu websocket client.
@@ -125,6 +126,8 @@ public final class ShenyuWebsocketClient extends 
WebSocketClient {
 
     private volatile Thread reconnectThread;
 
+    private final Consumer<ShenyuWebsocketClient> onMasterLostCallback;
+
     /**
      * Instantiates a new shenyu websocket client.
      *
@@ -146,6 +149,7 @@ public final class ShenyuWebsocketClient extends 
WebSocketClient {
                                  final Integer port
     ) {
         super(serverUri);
+        this.onMasterLostCallback = null;
         this.namespaceId = namespaceId;
         this.addHeader(Constants.SHENYU_NAMESPACE_ID, namespaceId);
         this.addHeader(Constants.CLIENT_PORT_NAME, String.valueOf(port));
@@ -212,11 +216,44 @@ public final class ShenyuWebsocketClient extends 
WebSocketClient {
                                  final String namespaceId,
                                  final Integer port,
                                  final AtomicBoolean initialSyncReady) {
+        this(serverUri, headers, pluginDataSubscriber, metaDataSubscribers, 
authDataSubscribers,
+                proxySelectorDataSubscribers, 
discoveryUpstreamDataSubscribers, aiProxyApiKeyDataSubscribers,
+                namespaceId, port, initialSyncReady, null);
+    }
+
+    /**
+     * Create a client with an optional startup readiness latch and master 
lost callback.
+     * @param serverUri server URI
+     * @param headers headers
+     * @param pluginDataSubscriber plugin subscriber
+     * @param metaDataSubscribers metadata subscribers
+     * @param authDataSubscribers authorization subscribers
+     * @param proxySelectorDataSubscribers proxy selector subscribers
+     * @param discoveryUpstreamDataSubscribers discovery subscribers
+     * @param aiProxyApiKeyDataSubscribers API key subscribers
+     * @param namespaceId namespace
+     * @param port gateway port
+     * @param initialSyncReady startup latch, null for the legacy protocol
+     * @param onMasterLostCallback callback invoked when connection to master 
is lost
+     */
+    public ShenyuWebsocketClient(final URI serverUri,
+                                 final Map<String, String> headers,
+                                 final PluginDataSubscriber 
pluginDataSubscriber,
+                                 final List<MetaDataSubscriber> 
metaDataSubscribers,
+                                 final List<AuthDataSubscriber> 
authDataSubscribers,
+                                 final List<ProxySelectorDataSubscriber> 
proxySelectorDataSubscribers,
+                                 final List<DiscoveryUpstreamDataSubscriber> 
discoveryUpstreamDataSubscribers,
+                                 final List<AiProxyApiKeyDataSubscriber> 
aiProxyApiKeyDataSubscribers,
+                                 final String namespaceId,
+                                 final Integer port,
+                                 final AtomicBoolean initialSyncReady,
+                                 final Consumer<ShenyuWebsocketClient> 
onMasterLostCallback) {
         super(serverUri, headers);
         if (Objects.nonNull(initialSyncReady)) {
             this.initialSyncState = new InitialSyncState(initialSyncReady, 
this::resetSyncFailures);
         }
         this.namespaceId = namespaceId;
+        this.onMasterLostCallback = onMasterLostCallback;
         LOG.info("shenyu bootstrap websocket namespaceId: {}", namespaceId);
         this.addHeader(Constants.SHENYU_NAMESPACE_ID, namespaceId);
         this.addHeader(Constants.CLIENT_PORT_NAME, String.valueOf(port));
@@ -297,10 +334,14 @@ public final class ShenyuWebsocketClient extends 
WebSocketClient {
             if (Objects.equals(WebsocketSyncFrame.EVENT_TYPE, eventType) && 
Objects.nonNull(initialSyncState)) {
                 
initialSyncState.accept(GsonUtils.getInstance().fromJson(result, 
WebsocketSyncFrame.class), this::handleResult);
             } else if (Objects.equals(DataEventTypeEnum.RUNNING_MODE.name(), 
eventType)) {
+                boolean wasMaster = this.isConnectedToMaster;
                 String mode = 
String.valueOf(jsonToMap.get(RunningModeConstants.RUNNING_MODE));
                 if (!Objects.equals(RunningModeEnum.STANDALONE.name(), mode)) {
                     this.masterUrl = 
String.valueOf(jsonToMap.get(RunningModeConstants.MASTER_URL));
                     this.isConnectedToMaster = 
Boolean.TRUE.equals(jsonToMap.get(RunningModeConstants.IS_MASTER));
+                    if (wasMaster && !this.isConnectedToMaster && 
!this.manuallyClosed.get() && Objects.nonNull(onMasterLostCallback)) {
+                        onMasterLostCallback.accept(this);
+                    }
                 }
                 this.runningMode = mode;
             } else if (Objects.nonNull(initialSyncState)) {
@@ -318,7 +359,12 @@ public final class ShenyuWebsocketClient extends 
WebSocketClient {
 
     @Override
     public void onClose(final int i, final String s, final boolean b) {
+        boolean wasMaster = this.isConnectedToMaster;
+        this.isConnectedToMaster = false;
         this.close();
+        if (wasMaster && !this.manuallyClosed.get() && 
Objects.nonNull(onMasterLostCallback)) {
+            onMasterLostCallback.accept(this);
+        }
     }
     
     @Override
@@ -378,6 +424,9 @@ public final class ShenyuWebsocketClient extends 
WebSocketClient {
                 this.reconnectBackoff.set(0);
                 this.sendPing();
                 send(getInstanceInfo());
+                if (!Objects.equals(RunningModeEnum.STANDALONE.name(), 
runningMode)) {
+                    send(DataEventTypeEnum.RUNNING_MODE.name());
+                }
                 LOG.debug("websocket send to [{}] ping message successful", 
this.getURI());
             }
         } catch (Exception e) {
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/config/WebsocketConfig.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/config/WebsocketConfig.java
index e7ce4023d0..19bc1535ad 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/config/WebsocketConfig.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/config/WebsocketConfig.java
@@ -40,6 +40,11 @@ public class WebsocketConfig {
 
     private boolean initialSyncReadiness;
 
+    /**
+     * Master check interval in seconds.
+     */
+    private int masterCheckInterval = 60;
+
     /**
      * Whether the opt-in initial synchronization protocol is required.
      * @return enabled
@@ -105,6 +110,24 @@ public class WebsocketConfig {
     public void setToken(final String token) {
         this.token = token;
     }
+
+    /**
+     * get master check interval in seconds.
+     *
+     * @return master check interval
+     */
+    public int getMasterCheckInterval() {
+        return masterCheckInterval;
+    }
+
+    /**
+     * set master check interval in seconds.
+     *
+     * @param masterCheckInterval master check interval
+     */
+    public void setMasterCheckInterval(final int masterCheckInterval) {
+        this.masterCheckInterval = masterCheckInterval;
+    }
     
     @Override
     public boolean equals(final Object o) {
@@ -118,12 +141,13 @@ public class WebsocketConfig {
         return Objects.equals(urls, that.urls)
                 && Objects.equals(allowOrigin, that.allowOrigin)
                 && Objects.equals(token, that.token)
-                && initialSyncReadiness == that.initialSyncReadiness;
+                && initialSyncReadiness == that.initialSyncReadiness
+                && masterCheckInterval == that.masterCheckInterval;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(urls, allowOrigin, token, initialSyncReadiness);
+        return Objects.hash(urls, allowOrigin, token, initialSyncReadiness, 
masterCheckInterval);
     }
 
     @Override
@@ -136,6 +160,8 @@ public class WebsocketConfig {
                 + allowOrigin
                 + '\''
                 + ", token='******'"
+                + ", masterCheckInterval="
+                + masterCheckInterval
                 + '}';
     }
 }
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/WebsocketSyncDataServiceTest.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/WebsocketSyncDataServiceTest.java
index 65da2272a7..92428f9e31 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/WebsocketSyncDataServiceTest.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/WebsocketSyncDataServiceTest.java
@@ -36,9 +36,12 @@ import 
org.springframework.boot.autoconfigure.web.ServerProperties;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.net.URI;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.doThrow;
@@ -134,6 +137,88 @@ public final class WebsocketSyncDataServiceTest {
         }
     }
 
+    @Test
+    public void testConfigurableMasterCheckInterval() throws Exception {
+        WebsocketConfig config = new WebsocketConfig();
+        config.setUrls(Collections.emptyList());
+        config.setMasterCheckInterval(15);
+        WebsocketSyncDataService service = new WebsocketSyncDataService(
+                config,
+                new ShenyuConfig(),
+                mock(PluginDataSubscriber.class),
+                Collections.emptyList(),
+                Collections.emptyList(),
+                Collections.emptyList(),
+                Collections.emptyList(),
+                Collections.emptyList(),
+                mock(ServerProperties.class));
+        try {
+            Field timerTaskField = 
WebsocketSyncDataService.class.getDeclaredField("timerTask");
+            timerTaskField.setAccessible(true);
+            TimerTask timerTask = (TimerTask) timerTaskField.get(service);
+            Method getDelayMs = 
TimerTask.class.getDeclaredMethod("getDelayMs");
+            getDelayMs.setAccessible(true);
+            long delayMs = (long) getDelayMs.invoke(timerTask);
+            assertEquals(15000L, delayMs);
+        } finally {
+            service.close();
+        }
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testMasterCheckReseedsClientsWhenAllRemoved() throws Exception 
{
+        WebsocketConfig config = new WebsocketConfig();
+        
config.setUrls(Collections.singletonList("ws://127.0.0.1:9095/websocket"));
+        WebsocketSyncDataService service = new WebsocketSyncDataService(
+                config,
+                new ShenyuConfig(),
+                mock(PluginDataSubscriber.class),
+                Collections.emptyList(),
+                Collections.emptyList(),
+                Collections.emptyList(),
+                Collections.emptyList(),
+                Collections.emptyList(),
+                mock(ServerProperties.class));
+        Field clientsField = 
WebsocketSyncDataService.class.getDeclaredField("clients");
+        clientsField.setAccessible(true);
+        List<ShenyuWebsocketClient> clients = (List<ShenyuWebsocketClient>) 
clientsField.get(service);
+        clients.clear();
+        ShenyuWebsocketClient closedClient = mock(ShenyuWebsocketClient.class);
+        when(closedClient.isOpen()).thenReturn(false);
+        clients.add(closedClient);
+
+        try {
+            Method masterCheck = 
WebsocketSyncDataService.class.getDeclaredMethod("masterCheck");
+            masterCheck.setAccessible(true);
+            masterCheck.invoke(service);
+            verify(closedClient).nowClose();
+            assertEquals(1, clients.size());
+        } finally {
+            service.close();
+        }
+    }
+
+    @Test
+    public void testOnMasterLostTriggersRediscovery() throws Exception {
+        WebsocketSyncDataService service = createWebsocketSyncDataService();
+        ShenyuWebsocketClient client = mock(ShenyuWebsocketClient.class);
+        when(client.getURI()).thenReturn(URI.create("ws://localhost:9095"));
+
+        try {
+            Method onMasterLost = 
WebsocketSyncDataService.class.getDeclaredMethod("onMasterLost", 
ShenyuWebsocketClient.class);
+            onMasterLost.setAccessible(true);
+            onMasterLost.invoke(service, client);
+
+            Field rediscoveryScheduledField = 
WebsocketSyncDataService.class.getDeclaredField("rediscoveryScheduled");
+            rediscoveryScheduledField.setAccessible(true);
+            AtomicBoolean rediscoveryScheduled = (AtomicBoolean) 
rediscoveryScheduledField.get(service);
+            assertTrue(rediscoveryScheduled.get());
+        } finally {
+            service.close();
+        }
+    }
+
     private WebsocketSyncDataService createWebsocketSyncDataService() {
         WebsocketConfig websocketConfig = new WebsocketConfig();
         websocketConfig.setUrls(Collections.emptyList());
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClientTest.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClientTest.java
index 7c51d9f92b..5409433f3f 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClientTest.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClientTest.java
@@ -17,11 +17,13 @@
 
 package org.apache.shenyu.plugin.sync.data.websocket.client;
 
+import org.apache.shenyu.common.constant.RunningModeConstants;
 import org.apache.shenyu.common.dto.PluginData;
 import org.apache.shenyu.common.dto.WebsocketData;
 import org.apache.shenyu.common.dto.WebsocketSyncFrame;
 import org.apache.shenyu.common.enums.ConfigGroupEnum;
 import org.apache.shenyu.common.enums.DataEventTypeEnum;
+import org.apache.shenyu.common.enums.RunningModeEnum;
 import org.apache.shenyu.common.exception.ShenyuException;
 import org.apache.shenyu.common.timer.TimerTask;
 import org.apache.shenyu.common.utils.GsonUtils;
@@ -44,12 +46,15 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -206,6 +211,72 @@ public class ShenyuWebsocketClientTest {
         verify(shenyuWebsocketClient).close();
     }
 
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testOnCloseWithMasterLostNotification() {
+        ShenyuWebsocketClient client = createMockClient();
+        Consumer<ShenyuWebsocketClient> callback = mock(Consumer.class);
+        setField(client, "onMasterLostCallback", callback);
+        setField(client, "isConnectedToMaster", true);
+        doNothing().when(client).close();
+
+        client.onClose(1, "disconnected", true);
+
+        verify(callback).accept(client);
+        assertFalse(client.isConnectedToMaster());
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testOnCloseWhenManuallyClosedDoesNotNotifyMasterLost() {
+        ShenyuWebsocketClient client = createMockClient();
+        Consumer<ShenyuWebsocketClient> callback = mock(Consumer.class);
+        setField(client, "onMasterLostCallback", callback);
+        setField(client, "isConnectedToMaster", true);
+        setField(client, "manuallyClosed", new AtomicBoolean(true));
+        doNothing().when(client).close();
+
+        client.onClose(1, "normal closure", true);
+
+        verify(callback, never()).accept(any());
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testOnMessageRunningModeDemotionNotifiesMasterLost() {
+        ShenyuWebsocketClient client = createMockClient();
+        Consumer<ShenyuWebsocketClient> callback = mock(Consumer.class);
+        setField(client, "onMasterLostCallback", callback);
+        setField(client, "isConnectedToMaster", true);
+
+        Map<String, Object> map = new HashMap<>();
+        map.put(RunningModeConstants.EVENT_TYPE, 
DataEventTypeEnum.RUNNING_MODE.name());
+        map.put(RunningModeConstants.RUNNING_MODE, 
RunningModeEnum.CLUSTER.name());
+        map.put(RunningModeConstants.MASTER_URL, "ws://localhost:9096");
+        map.put(RunningModeConstants.IS_MASTER, false);
+        String json = GsonUtils.getInstance().toJson(map);
+
+        client.onMessage(json);
+
+        verify(callback).accept(client);
+        assertFalse(client.isConnectedToMaster());
+    }
+
+    @Test
+    public void testHealthCheckSendsRunningModeWhenClusterAndOpen() throws 
Exception {
+        ShenyuWebsocketClient client = createSyncTestClient();
+        doReturn(true).when(client).isOpen();
+        doNothing().when(client).sendPing();
+        doNothing().when(client).send(anyString());
+        setField(client, "runningMode", RunningModeEnum.CLUSTER.name());
+
+        Method healthCheck = 
ShenyuWebsocketClient.class.getDeclaredMethod("healthCheck");
+        healthCheck.setAccessible(true);
+        healthCheck.invoke(client);
+
+        verify(client).send(DataEventTypeEnum.RUNNING_MODE.name());
+    }
+
     @Test
     public void testOnError() {
         shenyuWebsocketClient = spy(shenyuWebsocketClient);
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/config/WebsocketConfigTest.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/config/WebsocketConfigTest.java
index ee14c4d552..a487fbfb18 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/config/WebsocketConfigTest.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/config/WebsocketConfigTest.java
@@ -54,6 +54,9 @@ public class WebsocketConfigTest {
         assertEquals(URLS, websocketConfig.getUrls());
         assertEquals(ALLOW_ORIGIN, websocketConfig.getAllowOrigin());
         assertEquals(TOKEN, websocketConfig.getToken());
+        assertEquals(60, websocketConfig.getMasterCheckInterval());
+        websocketConfig.setMasterCheckInterval(30);
+        assertEquals(30, websocketConfig.getMasterCheckInterval());
     }
 
     @Test
@@ -66,6 +69,9 @@ public class WebsocketConfigTest {
         assertEquals(websocketConfig, that);
         that.setInitialSyncReadiness(true);
         assertNotEquals(websocketConfig, that);
+        that.setInitialSyncReadiness(false);
+        that.setMasterCheckInterval(10);
+        assertNotEquals(websocketConfig, that);
         assertNotEquals(websocketConfig, null);
         assertNotEquals(websocketConfig, new Object());
     }
@@ -73,13 +79,13 @@ public class WebsocketConfigTest {
     @Test
     public void testHashCode() {
         assertEquals(Objects.hash(websocketConfig.getUrls(), 
websocketConfig.getAllowOrigin(),
-                websocketConfig.getToken(), 
websocketConfig.isInitialSyncReadiness()), websocketConfig.hashCode());
+                websocketConfig.getToken(), 
websocketConfig.isInitialSyncReadiness(), 
websocketConfig.getMasterCheckInterval()), websocketConfig.hashCode());
     }
 
     @Test
     public void testToString() {
-        String toString = "WebsocketConfig{urls='%s', allowOrigin='%s', 
token='******'}";
-        String expected = String.format(toString, URLS, ALLOW_ORIGIN);
+        String toString = "WebsocketConfig{urls='%s', allowOrigin='%s', 
token='******', masterCheckInterval=%d}";
+        String expected = String.format(toString, URLS, ALLOW_ORIGIN, 60);
         assertEquals(expected, websocketConfig.toString());
         assertFalse(websocketConfig.toString().contains(TOKEN));
     }

Reply via email to