You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@seata.apache.org by ji...@apache.org on 2026/10/06 15:56:09 UTC
(incubator-seata) branch 2.x updated: bugfix: do not return a Redis global session when its status does not match (#8276)
This is an automated email from the ASF dual-hosted git repository.
slievrly pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git
The following commit(s) were added to refs/heads/2.x by this push:
new 410ec3a243 bugfix: do not return a Redis global session when its status does not match (#8276)
410ec3a243 is described below
commit 410ec3a243af045342d8bcaf215b6890d0bfe7d1
Author: Burak Kalaycı <ka...@gmail.com>
AuthorDate: Tue Oct 6 18:56:04 2026 +0300
bugfix: do not return a Redis global session when its status does not match (#8276)
---
changes/en-us/2.x.md | 1 +
changes/zh-cn/2.x.md | 1 +
.../impl/redis/GlobalSessionRedisServiceImpl.java | 34 ++++-
.../GlobalSessionRedisServiceImplQueryTest.java | 154 +++++++++++++++++++++
.../redis/GlobalSessionRedisServiceImplTest.java | 19 +++
5 files changed, 202 insertions(+), 7 deletions(-)
diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index 96b43b6b76..8860e86699 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -42,6 +42,7 @@ Add changes here for all PR submitted to the 2.x branch.
- [[#8170](https://github.com/apache/incubator-seata/pull/8170)] fix ConfigTools encryption corrupting non-ASCII content on non-UTF-8 platforms
- [[#8182](https://github.com/apache/incubator-seata/issues/8182)] fix registry preferred networks and ignored interfaces from Spring Boot application.yml
- [[#8201](https://github.com/apache/incubator-seata/issues/8197)] fix residual file-mode global locks caused by an async commit cleanup race
+- [[#8276](https://github.com/apache/incubator-seata/pull/8276)] fix Redis global session query returning a non-matching XID row when a status filter is empty
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index fc2d818a4d..8cd7c7ef58 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -42,6 +42,7 @@
- [[#8170](https://github.com/apache/incubator-seata/pull/8170)] 修复 ConfigTools 加密在非 UTF-8 默认字符集平台损坏非 ASCII 内容的问题
- [[#8182](https://github.com/apache/incubator-seata/issues/8182)] 修复 Spring Boot application.yml 中 registry preferred networks 和 ignored interfaces 配置不生效的问题
- [[#8201](https://github.com/apache/incubator-seata/issues/8197)] 修复异步提交清理竞态导致 file 模式全局锁残留的问题
+- [[#8276](https://github.com/apache/incubator-seata/pull/8276)] 修复 Redis 全局会话按 XID 加状态查询时,状态不匹配仍返回该会话的问题
### optimize:
diff --git a/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java b/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java
index 0bd932aed7..8efb1b21eb 100644
--- a/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java
+++ b/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java
@@ -73,7 +73,9 @@ public class GlobalSessionRedisServiceImpl extends AbstractGlobalService impleme
instance.findGlobalSessionByPage(param.getPageNum(), param.getPageSize(), param.isWithBranch());
} else {
List<GlobalSession> globalSessionsNew = new ArrayList<>();
- if (isNotBlank(param.getXid())) {
+ boolean filterByStatus = isKnownStatus(param.getStatus());
+ boolean xidRequested = isNotBlank(param.getXid());
+ if (xidRequested) {
SessionCondition sessionCondition = new SessionCondition();
sessionCondition.setXid(param.getXid());
sessionCondition.setLazyLoadBranch(!param.isWithBranch());
@@ -81,11 +83,15 @@ public class GlobalSessionRedisServiceImpl extends AbstractGlobalService impleme
total = (long) globalSessions.size();
}
- if (param.getStatus() != null && GlobalStatus.get(param.getStatus()) != null) {
- if (CollectionUtils.isNotEmpty(globalSessions)) {
- globalSessionsNew = globalSessions.stream()
- .filter(globalSession -> globalSession.getStatus().getCode() == (param.getStatus()))
- .collect(Collectors.toList());
+ if (filterByStatus) {
+ if (xidRequested) {
+ // A missing xid must not fall through to a status-wide query.
+ if (CollectionUtils.isNotEmpty(globalSessions)) {
+ globalSessionsNew = globalSessions.stream()
+ .filter(globalSession ->
+ globalSession.getStatus().getCode() == (param.getStatus()))
+ .collect(Collectors.toList());
+ }
total = (long) globalSessionsNew.size();
} else {
total = instance.countByGlobalSessions(new GlobalStatus[] {GlobalStatus.get(param.getStatus())});
@@ -103,11 +109,25 @@ public class GlobalSessionRedisServiceImpl extends AbstractGlobalService impleme
LOGGER.debug("not supported according to transactionName query");
}
}
- globalSessions = globalSessionsNew.size() > 0 ? globalSessionsNew : globalSessions;
+ if (filterByStatus) {
+ globalSessions = globalSessionsNew;
+ }
}
convertToGlobalSessionVo(result, globalSessions);
return PageResult.success(result, total.intValue(), param.getPageNum(), param.getPageSize());
}
+
+ /**
+ * {@link GlobalStatus#get(int)} throws for a code outside the enum.
+ * An unrecognized status is not a filter.
+ */
+ private static boolean isKnownStatus(Integer status) {
+ if (status == null) {
+ return false;
+ }
+ int code = status;
+ return code >= 0 && code < GlobalStatus.values().length;
+ }
}
diff --git a/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImplQueryTest.java b/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImplQueryTest.java
new file mode 100644
index 0000000000..48f57f740e
--- /dev/null
+++ b/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImplQueryTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.console.impl.redis;
+
+import org.apache.seata.common.holder.ObjectHolder;
+import org.apache.seata.common.result.PageResult;
+import org.apache.seata.core.model.GlobalStatus;
+import org.apache.seata.server.console.entity.param.GlobalSessionParam;
+import org.apache.seata.server.console.entity.vo.GlobalSessionVO;
+import org.apache.seata.server.session.GlobalSession;
+import org.apache.seata.server.session.SessionCondition;
+import org.apache.seata.server.storage.redis.store.RedisTransactionStoreManager;
+import org.apache.seata.server.storage.redis.store.RedisTransactionStoreManagerFactory;
+import org.apache.seata.spring.boot.autoconfigure.SeataCoreEnvironmentPostProcessor;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.springframework.mock.env.MockEnvironment;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+
+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.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Status filtering for an XID query must not fall back to the unfiltered session.
+ */
+class GlobalSessionRedisServiceImplQueryTest {
+
+ @BeforeAll
+ static void initConfiguration() {
+ ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT, new MockEnvironment());
+ SeataCoreEnvironmentPostProcessor.init();
+ }
+
+ @Test
+ void queryByXidAndNonMatchingStatusReturnsEmpty() throws Exception {
+ PageResult<GlobalSessionVO> result = query(GlobalStatus.RollbackRetrying.getCode());
+
+ assertTrue(result.isSuccess());
+ assertEquals(0, result.getTotal());
+ assertEquals(0, result.getData().size());
+ }
+
+ @Test
+ void queryByXidAndMatchingStatusKeepsTheSession() throws Exception {
+ PageResult<GlobalSessionVO> result = query(GlobalStatus.Begin.getCode());
+
+ assertTrue(result.isSuccess());
+ assertEquals(1, result.getTotal());
+ assertEquals(1, result.getData().size());
+ assertEquals("192.168.1.1:8091:1001", result.getData().get(0).getXid());
+ }
+
+ @Test
+ void queryByXidWithoutStatusKeepsTheSession() throws Exception {
+ PageResult<GlobalSessionVO> result = query(null);
+
+ assertTrue(result.isSuccess());
+ assertEquals(1, result.getTotal());
+ assertEquals(1, result.getData().size());
+ }
+
+ @Test
+ void queryByXidAndUnrecognizedStatusKeepsTheSession() throws Exception {
+ PageResult<GlobalSessionVO> result = query(999);
+
+ assertTrue(result.isSuccess());
+ assertEquals(1, result.getTotal());
+ assertEquals(1, result.getData().size());
+ }
+
+ @Test
+ void queryByMissingXidAndStatusReturnsEmpty() throws Exception {
+ GlobalSession other = new GlobalSession();
+ other.setXid("192.168.1.1:8091:9999");
+ other.setStatus(GlobalStatus.Begin);
+ Field branchSessions = GlobalSession.class.getDeclaredField("branchSessions");
+ branchSessions.setAccessible(true);
+ branchSessions.set(other, new ArrayList<>());
+
+ RedisTransactionStoreManager store = mock(RedisTransactionStoreManager.class);
+ when(store.readSession(any(SessionCondition.class))).thenReturn(Collections.emptyList());
+ when(store.readSessionStatusByPage(any(GlobalSessionParam.class))).thenReturn(Collections.singletonList(other));
+ when(store.countByGlobalSessions(any(GlobalStatus[].class))).thenReturn(1L);
+
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(20);
+ param.setXid("192.168.1.1:8091:404");
+ param.setStatus(GlobalStatus.Begin.getCode());
+ param.setWithBranch(false);
+
+ try (MockedStatic<RedisTransactionStoreManagerFactory> factory =
+ mockStatic(RedisTransactionStoreManagerFactory.class)) {
+ factory.when(RedisTransactionStoreManagerFactory::getInstance).thenReturn(store);
+ PageResult<GlobalSessionVO> result = new GlobalSessionRedisServiceImpl().query(param);
+
+ assertTrue(result.isSuccess());
+ assertEquals(0, result.getTotal());
+ assertEquals(0, result.getData().size());
+ verify(store, never()).readSessionStatusByPage(any(GlobalSessionParam.class));
+ verify(store, never()).countByGlobalSessions(any(GlobalStatus[].class));
+ }
+ }
+
+ private PageResult<GlobalSessionVO> query(Integer status) throws Exception {
+ GlobalSession session = new GlobalSession();
+ session.setXid("192.168.1.1:8091:1001");
+ session.setStatus(GlobalStatus.Begin);
+ Field branchSessions = GlobalSession.class.getDeclaredField("branchSessions");
+ branchSessions.setAccessible(true);
+ branchSessions.set(session, new ArrayList<>());
+
+ RedisTransactionStoreManager store = mock(RedisTransactionStoreManager.class);
+ when(store.readSession(any(SessionCondition.class))).thenReturn(Collections.singletonList(session));
+
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(20);
+ param.setXid(session.getXid());
+ param.setStatus(status);
+ param.setWithBranch(false);
+
+ try (MockedStatic<RedisTransactionStoreManagerFactory> factory =
+ mockStatic(RedisTransactionStoreManagerFactory.class)) {
+ factory.when(RedisTransactionStoreManagerFactory::getInstance).thenReturn(store);
+ return new GlobalSessionRedisServiceImpl().query(param);
+ }
+ }
+}
diff --git a/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImplTest.java b/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImplTest.java
index a3794dc580..b16dc0d241 100644
--- a/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImplTest.java
+++ b/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImplTest.java
@@ -177,6 +177,25 @@ class GlobalSessionRedisServiceImplTest extends BaseSpringBootTest {
Assertions.assertEquals(1, result.getTotal());
}
+ @Test
+ void queryByXidAndNonMatchingStatusTest() throws Exception {
+ createTestGlobalTransaction("127.0.0.1:8091:1001", 1001L, GlobalStatus.Begin);
+
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("127.0.0.1:8091:1001");
+ param.setStatus(GlobalStatus.RollbackRetrying.getCode());
+ param.setWithBranch(false);
+
+ PageResult<GlobalSessionVO> result = globalSessionRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+ }
+
@Test
void queryEmptyResultTest() {
GlobalSessionParam param = new GlobalSessionParam();
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@seata.apache.org
For additional commands, e-mail: notifications-help@seata.apache.org