From b4208646eae1405a17f38bdfecabeb3b6f785227 Mon Sep 17 00:00:00 2001 From: bubu <1654388696@qq.com> Date: Fri, 5 Jun 2026 21:22:28 +0800 Subject: [PATCH 1/5] fix: register namingServer control endpoint with service port --- .../instance/GeneralInstanceStrategy.java | 2 +- .../instance/RaftServerInstanceStrategy.java | 2 +- .../instance/GeneralInstanceStrategyTest.java | 124 ++++++++++++++++++ .../RaftServerInstanceStrategyTest.java | 3 +- 4 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java diff --git a/server/src/main/java/org/apache/seata/server/instance/GeneralInstanceStrategy.java b/server/src/main/java/org/apache/seata/server/instance/GeneralInstanceStrategy.java index a45eee781df..9e583f483e5 100644 --- a/server/src/main/java/org/apache/seata/server/instance/GeneralInstanceStrategy.java +++ b/server/src/main/java/org/apache/seata/server/instance/GeneralInstanceStrategy.java @@ -57,7 +57,7 @@ public Instance serverInstanceInit() { instance.setTerm(System.currentTimeMillis()); // load node Endpoint - instance.setControl(new Node.Endpoint(XID.getIpAddress(), serverProperties.getPort(), "http")); + instance.setControl(new Node.Endpoint(XID.getIpAddress(), XID.getPort(), "http")); // load metadata for (PropertySource propertySource : environment.getPropertySources()) { diff --git a/server/src/main/java/org/apache/seata/server/instance/RaftServerInstanceStrategy.java b/server/src/main/java/org/apache/seata/server/instance/RaftServerInstanceStrategy.java index 230933baa4d..280edd3c8a0 100644 --- a/server/src/main/java/org/apache/seata/server/instance/RaftServerInstanceStrategy.java +++ b/server/src/main/java/org/apache/seata/server/instance/RaftServerInstanceStrategy.java @@ -70,7 +70,7 @@ public Instance serverInstanceInit() { instance.setTerm(term); instance.setRole(stateMachine.isLeader() ? ClusterRole.LEADER : ClusterRole.FOLLOWER); // load node Endpoint - instance.setControl(new Node.Endpoint(XID.getIpAddress(), serverProperties.getPort(), "http")); + instance.setControl(new Node.Endpoint(XID.getIpAddress(), XID.getPort(), "http")); PeerId peerId = RaftServerManager.getRaftServer(raftProperties.getGroup()).getServerId(); diff --git a/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java b/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java new file mode 100644 index 00000000000..5a2aa6ed03a --- /dev/null +++ b/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.instance; + +import org.apache.seata.common.XID; +import org.apache.seata.common.holder.ObjectHolder; +import org.apache.seata.common.metadata.ClusterRole; +import org.apache.seata.common.metadata.Instance; +import org.apache.seata.common.metadata.Node; +import org.apache.seata.common.store.SessionMode; +import org.apache.seata.server.BaseSpringBootTest; +import org.apache.seata.spring.boot.autoconfigure.properties.registry.RegistryNamingServerProperties; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.StandardEnvironment; + +import java.util.HashMap; +import java.util.Map; + +import static org.apache.seata.common.Constants.OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class GeneralInstanceStrategyTest extends BaseSpringBootTest { + + private GeneralInstanceStrategy strategy; + + @BeforeEach + void setUp() { + strategy = new GeneralInstanceStrategy(); + RegistryNamingServerProperties namingProps = new RegistryNamingServerProperties(); + namingProps.setNamespace("public"); + namingProps.setCluster("default"); + + ServerProperties serverProperties = new ServerProperties(); + serverProperties.setPort(8088); + + strategy.registryNamingServerProperties = namingProps; + strategy.serverProperties = serverProperties; + } + + @AfterEach + void tearDown() { + resetInstance(); + } + + @Test + void serverInstanceInitShouldUseServicePortForControlEndpoint() { + ConfigurableEnvironment environment = buildEnvironmentWithMeta(); + ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT, environment); + + try (MockedStatic xidMock = Mockito.mockStatic(XID.class); + MockedStatic storeConfigMock = + Mockito.mockStatic(org.apache.seata.server.store.StoreConfig.class)) { + xidMock.when(XID::getIpAddress).thenReturn("10.0.0.2"); + xidMock.when(XID::getPort).thenReturn(7091); + storeConfigMock + .when(org.apache.seata.server.store.StoreConfig::getSessionMode) + .thenReturn(SessionMode.DB); + + Instance instance = strategy.serverInstanceInit(); + + assertEquals("public", instance.getNamespace()); + assertEquals("default", instance.getClusterName()); + Node.Endpoint control = instance.getControl(); + assertNotNull(control); + assertEquals("10.0.0.2", control.getHost()); + assertEquals(7091, control.getPort()); + assertEquals("default", instance.getMetadata().get("cluster-type")); + } + } + + @Test + void typeShouldReturnGeneral() { + assertEquals(SeataInstanceStrategy.Type.GENERAL, strategy.type()); + } + + private ConfigurableEnvironment buildEnvironmentWithMeta() { + Map map = new HashMap<>(); + map.put("meta.demo", "v"); + StandardEnvironment environment = new StandardEnvironment(); + MutablePropertySources sources = environment.getPropertySources(); + sources.addFirst(new MapPropertySource("testMeta", map)); + return environment; + } + + private void resetInstance() { + Instance instance = Instance.getInstance(); + instance.setNamespace(null); + instance.setClusterName(null); + instance.setUnit(null); + instance.setControl(null); + instance.setTransaction(null); + instance.setInternal(null); + instance.setHealthy(true); + instance.setWeight(1.0); + instance.setTerm(0L); + instance.setTimestamp(0L); + instance.setMetadata(new HashMap<>()); + instance.setRole(ClusterRole.MEMBER); + instance.setVersion(null); + } +} diff --git a/server/src/test/java/org/apache/seata/server/instance/RaftServerInstanceStrategyTest.java b/server/src/test/java/org/apache/seata/server/instance/RaftServerInstanceStrategyTest.java index d5d59d94fbf..7116a5fbd79 100644 --- a/server/src/test/java/org/apache/seata/server/instance/RaftServerInstanceStrategyTest.java +++ b/server/src/test/java/org/apache/seata/server/instance/RaftServerInstanceStrategyTest.java @@ -113,6 +113,7 @@ void serverInstanceInit_shouldPopulateInstanceFromRaft() { .thenReturn(raftServer); storeConfigMock.when(StoreConfig::getSessionMode).thenReturn(SessionMode.RAFT); xidMock.when(XID::getIpAddress).thenReturn("10.0.0.1"); + xidMock.when(XID::getPort).thenReturn(7091); Instance instance = strategy.serverInstanceInit(); @@ -124,7 +125,7 @@ void serverInstanceInit_shouldPopulateInstanceFromRaft() { Node.Endpoint control = instance.getControl(); assertNotNull(control); assertEquals("10.0.0.1", control.getHost()); - assertEquals(8088, control.getPort()); + assertEquals(7091, control.getPort()); Node.Endpoint internal = instance.getInternal(); assertNotNull(internal); assertEquals("127.0.0.1", internal.getHost()); From 32cd9707f7c3d93922dfb401667dca30658a0433 Mon Sep 17 00:00:00 2001 From: bubu <1654388696@qq.com> Date: Sat, 6 Jun 2026 08:44:23 +0800 Subject: [PATCH 2/5] fix: use service port in raft control metadata --- .../server/cluster/raft/RaftStateMachine.java | 10 +- .../cluster/raft/RaftStateMachineTest.java | 112 ++++++++++++++++++ 2 files changed, 114 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftStateMachine.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftStateMachine.java index 22b0c368251..06d467ced9d 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftStateMachine.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftStateMachine.java @@ -68,7 +68,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.core.env.Environment; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -89,7 +88,6 @@ import java.util.stream.Collectors; import static org.apache.seata.common.Constants.OBJECT_KEY_SPRING_APPLICATION_CONTEXT; -import static org.apache.seata.common.Constants.OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT; import static org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.ADD_BRANCH_SESSION; import static org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.ADD_GLOBAL_SESSION; import static org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.ADD_VGROUP_MAPPING; @@ -387,9 +385,7 @@ public RaftClusterMetadata changeOrInitRaftClusterMetadata() { cureentPeerId.getIp(), XID.getPort(), raftServer.getServerId().getPort(), - Integer.parseInt( - ((Environment) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT)) - .getProperty("server.port", String.valueOf(7091))), + XID.getPort(), group, Collections.emptyMap()); leader.setRole(ClusterRole.LEADER); @@ -434,9 +430,7 @@ private void syncCurrentNodeInfo(PeerId leaderPeerId) { cureentPeerId.getIp(), XID.getPort(), cureentPeerId.getPort(), - Integer.parseInt(((Environment) - ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT)) - .getProperty("server.port", String.valueOf(7091))), + XID.getPort(), group, Collections.emptyMap()); InvokeContext invokeContext = new InvokeContext(); diff --git a/server/src/test/java/org/apache/seata/server/cluster/raft/RaftStateMachineTest.java b/server/src/test/java/org/apache/seata/server/cluster/raft/RaftStateMachineTest.java index d19c9ea807b..e1f15c633ee 100644 --- a/server/src/test/java/org/apache/seata/server/cluster/raft/RaftStateMachineTest.java +++ b/server/src/test/java/org/apache/seata/server/cluster/raft/RaftStateMachineTest.java @@ -18,16 +18,22 @@ import com.alipay.sofa.jraft.Closure; import com.alipay.sofa.jraft.Iterator; +import com.alipay.sofa.jraft.RouteTable; import com.alipay.sofa.jraft.Status; import com.alipay.sofa.jraft.conf.Configuration; import com.alipay.sofa.jraft.entity.LeaderChangeContext; import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.rpc.RpcClient; +import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl; import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import org.apache.seata.common.XID; +import org.apache.seata.common.holder.ObjectHolder; import org.apache.seata.common.metadata.ClusterRole; import org.apache.seata.common.metadata.Node; import org.apache.seata.server.BaseSpringBootTest; import org.apache.seata.server.cluster.raft.execute.RaftMsgExecute; +import org.apache.seata.server.cluster.raft.processor.request.PutNodeMetadataRequest; import org.apache.seata.server.cluster.raft.snapshot.StoreSnapshotFile; import org.apache.seata.server.cluster.raft.snapshot.metadata.LeaderMetadataSnapshotFile; import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; @@ -40,14 +46,22 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.StandardEnvironment; import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import static org.apache.seata.common.Constants.OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -56,8 +70,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -69,16 +85,22 @@ public class RaftStateMachineTest extends BaseSpringBootTest { private RaftStateMachine raftStateMachine; private static final String TEST_GROUP = "test-group"; + private Object previousEnvironment; @BeforeEach public void setUp() { StoreConfig.setStartupParameter("file", "file", "file"); + previousEnvironment = ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT); raftStateMachine = new RaftStateMachine(TEST_GROUP); } @AfterEach public void tearDown() { StoreConfig.setStartupParameter("file", "file", "file"); + RouteTable.getInstance().removeGroup(TEST_GROUP); + if (previousEnvironment != null) { + ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT, previousEnvironment); + } } @Test @@ -188,6 +210,88 @@ public void testGetAndSetRaftLeaderMetadata() { assertEquals(100L, retrieved.getTerm()); } + @Test + public void testChangeOrInitRaftClusterMetadataUsesServicePortForControlEndpoint() { + bindEnvironmentWithServerPort(8088); + raftStateMachine.onLeaderStart(7L); + + RaftServer raftServer = mock(RaftServer.class); + when(raftServer.getServerId()).thenReturn(new PeerId("10.0.0.1", 9091)); + + try (MockedStatic raftServerManagerMock = Mockito.mockStatic(RaftServerManager.class); + MockedStatic xidMock = Mockito.mockStatic(XID.class)) { + raftServerManagerMock + .when(() -> RaftServerManager.getRaftServer(TEST_GROUP)) + .thenReturn(raftServer); + xidMock.when(XID::getPort).thenReturn(7091); + + RaftClusterMetadata metadata = raftStateMachine.changeOrInitRaftClusterMetadata(); + + assertNotNull(metadata.getLeader()); + assertEquals(7091, metadata.getLeader().getTransaction().getPort()); + assertEquals(7091, metadata.getLeader().getControl().getPort()); + assertEquals(9091, metadata.getLeader().getInternal().getPort()); + } + } + + @Test + public void testSyncCurrentNodeInfoUsesServicePortForControlEndpoint() throws Exception { + bindEnvironmentWithServerPort(8088); + + PeerId currentPeerId = new PeerId("10.0.0.2", 9092); + PeerId leaderPeerId = new PeerId("10.0.0.1", 9091); + Configuration configuration = new Configuration(); + configuration.addPeer(currentPeerId); + RouteTable.getInstance().updateConfiguration(TEST_GROUP, configuration); + + RaftClusterMetadata metadata = new RaftClusterMetadata(1L); + Node leader = new Node(); + leader.setVersion("2.1.0"); + metadata.setLeader(leader); + raftStateMachine.setRaftLeaderMetadata(metadata); + + RaftServer raftServer = mock(RaftServer.class); + when(raftServer.getServerId()).thenReturn(currentPeerId); + + CliClientServiceImpl cliClientService = mock(CliClientServiceImpl.class); + RpcClient rpcClient = mock(RpcClient.class); + when(cliClientService.getRpcClient()).thenReturn(rpcClient); + + AtomicReference capturedRequest = new AtomicReference<>(); + doAnswer(invocation -> { + capturedRequest.set(invocation.getArgument(1)); + return null; + }) + .when(rpcClient) + .invokeAsync(any(), any(), any(), any(), anyLong()); + + java.lang.reflect.Method method = RaftStateMachine.class.getDeclaredMethod("syncCurrentNodeInfo", PeerId.class); + method.setAccessible(true); + + try (MockedStatic raftServerManagerMock = Mockito.mockStatic(RaftServerManager.class); + MockedStatic xidMock = Mockito.mockStatic(XID.class)) { + raftServerManagerMock + .when(() -> RaftServerManager.getRaftServer(TEST_GROUP)) + .thenReturn(raftServer); + raftServerManagerMock + .when(RaftServerManager::getCliClientServiceInstance) + .thenReturn(cliClientService); + xidMock.when(XID::getPort).thenReturn(7091); + + assertDoesNotThrow(() -> { + try { + method.invoke(raftStateMachine, leaderPeerId); + } catch (java.lang.reflect.InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } + }); + } + + assertNotNull(capturedRequest.get()); + assertEquals(7091, capturedRequest.get().getNode().getTransaction().getPort()); + assertEquals(7091, capturedRequest.get().getNode().getControl().getPort()); + } + @Test public void testMultipleLeaderStarts() { for (int i = 1; i <= 5; i++) { @@ -892,4 +996,12 @@ public void testChangeNodeMetadataUpdatesExistingLearner() { assertEquals("2.0.0", resultNode.getVersion()); assertEquals(1, updatedMetadata.getLearner().size()); // Should still be 1, not 2 } + + private void bindEnvironmentWithServerPort(int serverPort) { + Map properties = new HashMap<>(); + properties.put("server.port", serverPort); + ConfigurableEnvironment environment = new StandardEnvironment(); + environment.getPropertySources().addFirst(new MapPropertySource("testServerPort", properties)); + ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT, environment); + } } From 02e4e049c26097a7fbe8356dd9a8223ae194e654 Mon Sep 17 00:00:00 2001 From: bubu <1654388696@qq.com> Date: Sat, 6 Jun 2026 08:51:28 +0800 Subject: [PATCH 3/5] test: restore shared state in control port regressions --- .../cluster/raft/RaftStateMachineTest.java | 23 ++++++- .../instance/GeneralInstanceStrategyTest.java | 62 ++++++++++++++----- .../RaftServerInstanceStrategyTest.java | 52 +++++++++++++++- 3 files changed, 117 insertions(+), 20 deletions(-) diff --git a/server/src/test/java/org/apache/seata/server/cluster/raft/RaftStateMachineTest.java b/server/src/test/java/org/apache/seata/server/cluster/raft/RaftStateMachineTest.java index e1f15c633ee..ae168015454 100644 --- a/server/src/test/java/org/apache/seata/server/cluster/raft/RaftStateMachineTest.java +++ b/server/src/test/java/org/apache/seata/server/cluster/raft/RaftStateMachineTest.java @@ -98,9 +98,7 @@ public void setUp() { public void tearDown() { StoreConfig.setStartupParameter("file", "file", "file"); RouteTable.getInstance().removeGroup(TEST_GROUP); - if (previousEnvironment != null) { - ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT, previousEnvironment); - } + restoreEnvironment(previousEnvironment); } @Test @@ -1004,4 +1002,23 @@ private void bindEnvironmentWithServerPort(int serverPort) { environment.getPropertySources().addFirst(new MapPropertySource("testServerPort", properties)); ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT, environment); } + + private void restoreEnvironment(Object environment) { + if (environment != null) { + ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT, environment); + return; + } + removeObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT); + } + + @SuppressWarnings("unchecked") + private void removeObject(String objectKey) { + try { + Field objectMapField = ObjectHolder.class.getDeclaredField("OBJECT_MAP"); + objectMapField.setAccessible(true); + ((Map) objectMapField.get(null)).remove(objectKey); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } } diff --git a/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java b/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java index 5a2aa6ed03a..751d1285df1 100644 --- a/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java +++ b/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java @@ -35,6 +35,7 @@ import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.StandardEnvironment; +import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; @@ -45,9 +46,13 @@ class GeneralInstanceStrategyTest extends BaseSpringBootTest { private GeneralInstanceStrategy strategy; + private Instance previousInstance; + private Object previousEnvironment; @BeforeEach void setUp() { + previousInstance = copyInstance(Instance.getInstance()); + previousEnvironment = ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT); strategy = new GeneralInstanceStrategy(); RegistryNamingServerProperties namingProps = new RegistryNamingServerProperties(); namingProps.setNamespace("public"); @@ -62,7 +67,8 @@ void setUp() { @AfterEach void tearDown() { - resetInstance(); + restoreInstance(previousInstance); + restoreEnvironment(previousEnvironment); } @Test @@ -105,20 +111,46 @@ private ConfigurableEnvironment buildEnvironmentWithMeta() { return environment; } - private void resetInstance() { + private Instance copyInstance(Instance instance) { + Instance snapshot = instance.clone(); + snapshot.setRole(instance.getRole()); + snapshot.setMetadata(new HashMap<>(instance.getMetadata())); + return snapshot; + } + + private void restoreInstance(Instance snapshot) { Instance instance = Instance.getInstance(); - instance.setNamespace(null); - instance.setClusterName(null); - instance.setUnit(null); - instance.setControl(null); - instance.setTransaction(null); - instance.setInternal(null); - instance.setHealthy(true); - instance.setWeight(1.0); - instance.setTerm(0L); - instance.setTimestamp(0L); - instance.setMetadata(new HashMap<>()); - instance.setRole(ClusterRole.MEMBER); - instance.setVersion(null); + instance.setNamespace(snapshot.getNamespace()); + instance.setClusterName(snapshot.getClusterName()); + instance.setUnit(snapshot.getUnit()); + instance.setControl(snapshot.getControl()); + instance.setTransaction(snapshot.getTransaction()); + instance.setInternal(snapshot.getInternal()); + instance.setHealthy(snapshot.isHealthy()); + instance.setWeight(snapshot.getWeight()); + instance.setTerm(snapshot.getTerm()); + instance.setTimestamp(snapshot.getTimestamp()); + instance.setMetadata(new HashMap<>(snapshot.getMetadata())); + instance.setRole(snapshot.getRole()); + instance.setVersion(snapshot.getVersion()); + } + + private void restoreEnvironment(Object environment) { + if (environment != null) { + ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT, environment); + return; + } + removeObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT); + } + + @SuppressWarnings("unchecked") + private void removeObject(String objectKey) { + try { + Field objectMapField = ObjectHolder.class.getDeclaredField("OBJECT_MAP"); + objectMapField.setAccessible(true); + ((Map) objectMapField.get(null)).remove(objectKey); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } } } diff --git a/server/src/test/java/org/apache/seata/server/instance/RaftServerInstanceStrategyTest.java b/server/src/test/java/org/apache/seata/server/instance/RaftServerInstanceStrategyTest.java index 7116a5fbd79..35ba5a3ca4e 100644 --- a/server/src/test/java/org/apache/seata/server/instance/RaftServerInstanceStrategyTest.java +++ b/server/src/test/java/org/apache/seata/server/instance/RaftServerInstanceStrategyTest.java @@ -46,6 +46,7 @@ import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.StandardEnvironment; +import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; @@ -62,6 +63,8 @@ class RaftServerInstanceStrategyTest extends BaseSpringBootTest { private RaftServerInstanceStrategy strategy; + private Instance previousInstance; + private Object previousEnvironment; private ServerRaftProperties raftProperties; @@ -71,6 +74,8 @@ class RaftServerInstanceStrategyTest extends BaseSpringBootTest { @BeforeEach void setUp() { + previousInstance = copyInstance(Instance.getInstance()); + previousEnvironment = ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT); strategy = new RaftServerInstanceStrategy(); raftProperties = new ServerRaftProperties(); namingProps = new RegistryNamingServerProperties(); @@ -87,8 +92,8 @@ void setUp() { @AfterEach void tearDown() { - resetInstance(); - // Do not put null into ObjectHolder to avoid ConcurrentHashMap NPE + restoreInstance(previousInstance); + restoreEnvironment(previousEnvironment); } @Test @@ -186,4 +191,47 @@ private void resetInstance() { instance.setRole(ClusterRole.MEMBER); instance.setVersion(null); } + + private Instance copyInstance(Instance instance) { + Instance snapshot = instance.clone(); + snapshot.setRole(instance.getRole()); + snapshot.setMetadata(new HashMap<>(instance.getMetadata())); + return snapshot; + } + + private void restoreInstance(Instance snapshot) { + Instance instance = Instance.getInstance(); + instance.setNamespace(snapshot.getNamespace()); + instance.setClusterName(snapshot.getClusterName()); + instance.setUnit(snapshot.getUnit()); + instance.setControl(snapshot.getControl()); + instance.setTransaction(snapshot.getTransaction()); + instance.setInternal(snapshot.getInternal()); + instance.setHealthy(snapshot.isHealthy()); + instance.setWeight(snapshot.getWeight()); + instance.setTerm(snapshot.getTerm()); + instance.setTimestamp(snapshot.getTimestamp()); + instance.setMetadata(new HashMap<>(snapshot.getMetadata())); + instance.setRole(snapshot.getRole()); + instance.setVersion(snapshot.getVersion()); + } + + private void restoreEnvironment(Object environment) { + if (environment != null) { + ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT, environment); + return; + } + removeObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT); + } + + @SuppressWarnings("unchecked") + private void removeObject(String objectKey) { + try { + Field objectMapField = ObjectHolder.class.getDeclaredField("OBJECT_MAP"); + objectMapField.setAccessible(true); + ((Map) objectMapField.get(null)).remove(objectKey); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } } From afb429ceea9e0151186566da3e7b4c3f66a9f86f Mon Sep 17 00:00:00 2001 From: bubu <1654388696@qq.com> Date: Sat, 6 Jun 2026 08:54:38 +0800 Subject: [PATCH 4/5] test: remove unused import from control port regression --- .../seata/server/instance/GeneralInstanceStrategyTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java b/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java index 751d1285df1..ad697bbae36 100644 --- a/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java +++ b/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java @@ -18,7 +18,6 @@ import org.apache.seata.common.XID; import org.apache.seata.common.holder.ObjectHolder; -import org.apache.seata.common.metadata.ClusterRole; import org.apache.seata.common.metadata.Instance; import org.apache.seata.common.metadata.Node; import org.apache.seata.common.store.SessionMode; From 9d19d945e057a5f1958718a329c9898db96350c6 Mon Sep 17 00:00:00 2001 From: bubu <1654388696@qq.com> Date: Tue, 9 Jun 2026 16:41:01 +0800 Subject: [PATCH 5/5] test: use reflection util in control port cleanup --- .../seata/server/cluster/raft/RaftStateMachineTest.java | 8 ++++---- .../server/instance/GeneralInstanceStrategyTest.java | 8 ++++---- .../server/instance/RaftServerInstanceStrategyTest.java | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/server/src/test/java/org/apache/seata/server/cluster/raft/RaftStateMachineTest.java b/server/src/test/java/org/apache/seata/server/cluster/raft/RaftStateMachineTest.java index ae168015454..3bd7bb9e057 100644 --- a/server/src/test/java/org/apache/seata/server/cluster/raft/RaftStateMachineTest.java +++ b/server/src/test/java/org/apache/seata/server/cluster/raft/RaftStateMachineTest.java @@ -31,6 +31,7 @@ import org.apache.seata.common.holder.ObjectHolder; import org.apache.seata.common.metadata.ClusterRole; import org.apache.seata.common.metadata.Node; +import org.apache.seata.common.util.ReflectionUtil; import org.apache.seata.server.BaseSpringBootTest; import org.apache.seata.server.cluster.raft.execute.RaftMsgExecute; import org.apache.seata.server.cluster.raft.processor.request.PutNodeMetadataRequest; @@ -1014,11 +1015,10 @@ private void restoreEnvironment(Object environment) { @SuppressWarnings("unchecked") private void removeObject(String objectKey) { try { - Field objectMapField = ObjectHolder.class.getDeclaredField("OBJECT_MAP"); - objectMapField.setAccessible(true); + Field objectMapField = ReflectionUtil.getField(ObjectHolder.class, "OBJECT_MAP"); ((Map) objectMapField.get(null)).remove(objectKey); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); + } catch (ReflectiveOperationException | SecurityException e) { + throw new RuntimeException("Failed to remove ObjectHolder entry: " + objectKey, e); } } } diff --git a/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java b/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java index ad697bbae36..b6d28893880 100644 --- a/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java +++ b/server/src/test/java/org/apache/seata/server/instance/GeneralInstanceStrategyTest.java @@ -21,6 +21,7 @@ import org.apache.seata.common.metadata.Instance; import org.apache.seata.common.metadata.Node; import org.apache.seata.common.store.SessionMode; +import org.apache.seata.common.util.ReflectionUtil; import org.apache.seata.server.BaseSpringBootTest; import org.apache.seata.spring.boot.autoconfigure.properties.registry.RegistryNamingServerProperties; import org.junit.jupiter.api.AfterEach; @@ -145,11 +146,10 @@ private void restoreEnvironment(Object environment) { @SuppressWarnings("unchecked") private void removeObject(String objectKey) { try { - Field objectMapField = ObjectHolder.class.getDeclaredField("OBJECT_MAP"); - objectMapField.setAccessible(true); + Field objectMapField = ReflectionUtil.getField(ObjectHolder.class, "OBJECT_MAP"); ((Map) objectMapField.get(null)).remove(objectKey); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); + } catch (ReflectiveOperationException | SecurityException e) { + throw new RuntimeException("Failed to remove ObjectHolder entry: " + objectKey, e); } } } diff --git a/server/src/test/java/org/apache/seata/server/instance/RaftServerInstanceStrategyTest.java b/server/src/test/java/org/apache/seata/server/instance/RaftServerInstanceStrategyTest.java index 35ba5a3ca4e..4a1fd01b6fe 100644 --- a/server/src/test/java/org/apache/seata/server/instance/RaftServerInstanceStrategyTest.java +++ b/server/src/test/java/org/apache/seata/server/instance/RaftServerInstanceStrategyTest.java @@ -23,6 +23,7 @@ import org.apache.seata.common.metadata.Instance; import org.apache.seata.common.metadata.Node; import org.apache.seata.common.store.SessionMode; +import org.apache.seata.common.util.ReflectionUtil; import org.apache.seata.server.BaseSpringBootTest; import org.apache.seata.server.cluster.listener.ClusterChangeEvent; import org.apache.seata.server.cluster.raft.RaftServer; @@ -227,11 +228,10 @@ private void restoreEnvironment(Object environment) { @SuppressWarnings("unchecked") private void removeObject(String objectKey) { try { - Field objectMapField = ObjectHolder.class.getDeclaredField("OBJECT_MAP"); - objectMapField.setAccessible(true); + Field objectMapField = ReflectionUtil.getField(ObjectHolder.class, "OBJECT_MAP"); ((Map) objectMapField.get(null)).remove(objectKey); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); + } catch (ReflectiveOperationException | SecurityException e) { + throw new RuntimeException("Failed to remove ObjectHolder entry: " + objectKey, e); } } }