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