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

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


The following commit(s) were added to refs/heads/master by this push:
     new d8852542fe1 Auto close idle frontend connection for Proxy (#38045)
d8852542fe1 is described below

commit d8852542fe1f4ab3c83e2b4c4a70f5b9102acb16
Author: Raigor <[email protected]>
AuthorDate: Sun Feb 15 15:01:17 2026 +0800

    Auto close idle frontend connection for Proxy (#38045)
    
    * Auto close idle frontend connection for Proxy
    
    * Update RELEASE-NOTES.md
---
 RELEASE-NOTES.md                                   |  7 +++--
 .../config/props/ConfigurationPropertyKey.java     |  5 ++++
 .../variable/ShowDistVariablesExecutorTest.java    |  2 +-
 .../netty/FrontendChannelInboundHandler.java       | 29 +++++++++++++++++++++
 .../frontend/netty/ServerHandlerInitializer.java   | 13 ++++++++++
 .../netty/FrontendChannelInboundHandlerTest.java   | 30 ++++++++++++++++++++--
 .../netty/ServerHandlerInitializerTest.java        | 11 ++++++++
 .../dataset/empty_rules/show_dist_variables.xml    |  1 +
 8 files changed, 93 insertions(+), 5 deletions(-)

diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index c3ddc1b487f..298baac6289 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -1,5 +1,3 @@
-## Release 5.5.4-SNAPSHOT
-
 ## Release 5.5.3
 
 ### CVE
@@ -9,6 +7,9 @@
 1. Fix CVE-2024-7254 
[#36153](https://github.com/apache/shardingsphere/pull/36153)
 1. Fix CVE-2015-5237, CVE-2024-7254, CVE-2022-3171, CVE-2021-22569, 
CVE-2021-22570 [#37888](https://github.com/apache/shardingsphere/pull/37888)
 1. Fix CVE-2024-12798, CVE-2024-12801, CVE-2025-11226 
[#37936](https://github.com/apache/shardingsphere/pull/37936)
+1. Fix CVE-2023-39017 
[#38039](https://github.com/apache/shardingsphere/pull/38039)
+1. Fix CVE-2024-22399, CVE-2021-32824, CVE-2025-5222, CVE-2016-1000027 
[#38040](https://github.com/apache/shardingsphere/pull/38040)
+1. Fix CVE-2023-2976, CVE-2024-29131, CVE-2025-27821 
[#38042](https://github.com/apache/shardingsphere/pull/38042)
 
 ### Metadata Storage Changes
 
@@ -21,6 +22,7 @@
 1. Remove configuration property key `system-log-level` - 
[#35493](https://github.com/apache/shardingsphere/pull/35493)
 1. Change ShardingSphere SQL log topic from `ShardingSphere-SQL` to 
`org.apache.shardingsphere.sql` - 
[#37022](https://github.com/apache/shardingsphere/pull/37022)
 1. Add temporary config key `instance-connection-enabled` - 
[#37694](https://github.com/apache/shardingsphere/pull/37694)
+1. Add property config key `proxy-frontend-connection-idle-timeout` - 
[#38045](https://github.com/apache/shardingsphere/pull/38045)
 
 ### New Features
 
@@ -30,6 +32,7 @@
 1. Decouple registry center types as pluggable - 
[#36087](https://github.com/apache/shardingsphere/pull/36087)
 1. Proxy: Support Firebird Proxy - 
[#35937](https://github.com/apache/shardingsphere/pull/35937)
 1. JDBC: Support ZooKeeper and ETCD URL format - 
[#37037](https://github.com/apache/shardingsphere/pull/37037)
+1. Proxy: Auto close idle frontend connection for Proxy - 
[#38045](https://github.com/apache/shardingsphere/pull/38045)
 
 ### Enhancements
 
diff --git 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/config/props/ConfigurationPropertyKey.java
 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/config/props/ConfigurationPropertyKey.java
index e27fd89b943..f7b45da852f 100644
--- 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/config/props/ConfigurationPropertyKey.java
+++ 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/config/props/ConfigurationPropertyKey.java
@@ -95,6 +95,11 @@ public enum ConfigurationPropertyKey implements 
TypedPropertyKey {
      */
     PROXY_FRONTEND_MAX_CONNECTIONS("proxy-frontend-max-connections", "0", 
int.class, false),
     
+    /**
+     * Proxy frontend connection idle timeout in seconds.
+     */
+    
PROXY_FRONTEND_CONNECTION_IDLE_TIMEOUT("proxy-frontend-connection-idle-timeout",
 "28800", long.class, false),
+    
     /**
      * Proxy default start port.
      */
diff --git 
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/handler/distsql/ral/queryable/variable/ShowDistVariablesExecutorTest.java
 
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/handler/distsql/ral/queryable/variable/ShowDistVariablesExecutorTest.java
index 950482675f2..aa5e9789af0 100644
--- 
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/handler/distsql/ral/queryable/variable/ShowDistVariablesExecutorTest.java
+++ 
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/handler/distsql/ral/queryable/variable/ShowDistVariablesExecutorTest.java
@@ -64,7 +64,7 @@ class ShowDistVariablesExecutorTest {
         executor.setConnectionContext(new 
DistSQLConnectionContext(mock(QueryContext.class), 1,
                 mock(DatabaseType.class), 
mock(DatabaseConnectionManager.class), mock(ExecutorStatementManager.class)));
         Collection<LocalDataQueryResultRow> actual = 
executor.getRows(mock(ShowDistVariablesStatement.class), contextManager);
-        assertThat(actual.size(), is(22));
+        assertThat(actual.size(), is(23));
         LocalDataQueryResultRow row = actual.iterator().next();
         assertThat(row.getCell(1), is("agent_plugins_enabled"));
         assertThat(row.getCell(2), is("false"));
diff --git 
a/proxy/frontend/core/src/main/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandler.java
 
b/proxy/frontend/core/src/main/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandler.java
index 613afd27928..79e30f51cf5 100644
--- 
a/proxy/frontend/core/src/main/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandler.java
+++ 
b/proxy/frontend/core/src/main/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandler.java
@@ -21,11 +21,15 @@ import io.netty.buffer.ByteBuf;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.timeout.IdleStateEvent;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.shardingsphere.authentication.result.AuthenticationResult;
 import org.apache.shardingsphere.database.protocol.constant.CommonConstants;
+import org.apache.shardingsphere.infra.executor.sql.process.Process;
 import org.apache.shardingsphere.infra.executor.sql.process.ProcessEngine;
+import org.apache.shardingsphere.infra.executor.sql.process.ProcessRegistry;
 import org.apache.shardingsphere.infra.metadata.user.Grantee;
+import org.apache.shardingsphere.infra.session.connection.ConnectionContext;
 import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
 import org.apache.shardingsphere.proxy.frontend.exception.ExpectedExceptions;
 import 
org.apache.shardingsphere.proxy.frontend.executor.ConnectionThreadExecutorGroup;
@@ -99,6 +103,31 @@ public final class FrontendChannelInboundHandler extends 
ChannelInboundHandlerAd
         return false;
     }
     
+    @Override
+    public void userEventTriggered(final ChannelHandlerContext ctx, final 
Object event) throws Exception {
+        if (event instanceof IdleStateEvent) {
+            if (isIdle()) {
+                ConnectionContext connectionContext = 
connectionSession.getConnectionContext();
+                Grantee grantee = null == connectionContext ? null : 
connectionContext.getGrantee();
+                String databaseName = null == connectionContext ? null : 
connectionContext.getCurrentDatabaseName().orElse("NONE");
+                log.info("Connection {} (processId: {}) will be closed due to 
receiving an IdleStateEvent as it is idle. Grantee: {}, database name: {}",
+                        connectionSession.getConnectionId(), 
connectionSession.getProcessId(), grantee, databaseName);
+                ctx.close();
+            } else {
+                log.info("Received IdleStateEvent, but connection {} 
(processId: {}) is not idle, ignore.", connectionSession.getConnectionId(), 
connectionSession.getProcessId());
+            }
+        }
+        super.userEventTriggered(ctx, event);
+    }
+    
+    private boolean isIdle() {
+        if (null == connectionSession.getProcessId()) {
+            return true;
+        }
+        Process process = 
ProcessRegistry.getInstance().get(connectionSession.getProcessId());
+        return null == process || process.isIdle();
+    }
+    
     @Override
     public void channelInactive(final ChannelHandlerContext context) {
         context.fireChannelInactive();
diff --git 
a/proxy/frontend/core/src/main/java/org/apache/shardingsphere/proxy/frontend/netty/ServerHandlerInitializer.java
 
b/proxy/frontend/core/src/main/java/org/apache/shardingsphere/proxy/frontend/netty/ServerHandlerInitializer.java
index 5528bf37cf6..4f28001d59a 100644
--- 
a/proxy/frontend/core/src/main/java/org/apache/shardingsphere/proxy/frontend/netty/ServerHandlerInitializer.java
+++ 
b/proxy/frontend/core/src/main/java/org/apache/shardingsphere/proxy/frontend/netty/ServerHandlerInitializer.java
@@ -20,12 +20,17 @@ package org.apache.shardingsphere.proxy.frontend.netty;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelPipeline;
+import io.netty.handler.timeout.IdleStateHandler;
 import lombok.RequiredArgsConstructor;
 import 
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
 import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
 import org.apache.shardingsphere.database.protocol.codec.PacketCodec;
+import org.apache.shardingsphere.infra.config.props.ConfigurationPropertyKey;
+import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
 import 
org.apache.shardingsphere.proxy.frontend.spi.DatabaseProtocolFrontendEngine;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Server handler initializer.
  */
@@ -42,7 +47,15 @@ public final class ServerHandlerInitializer extends 
ChannelInitializer<Channel>
         pipeline.addLast(new 
PacketCodec(databaseProtocolFrontendEngine.getCodecEngine()));
         pipeline.addLast(new 
FrontendChannelLimitationInboundHandler(databaseProtocolFrontendEngine));
         pipeline.addLast(ProxyFlowControlHandler.class.getSimpleName(), new 
ProxyFlowControlHandler());
+        addIdleStateHandlerIfNeeded(pipeline);
         pipeline.addLast(FrontendChannelInboundHandler.class.getSimpleName(), 
new FrontendChannelInboundHandler(databaseProtocolFrontendEngine, 
socketChannel));
         databaseProtocolFrontendEngine.initChannel(socketChannel);
     }
+    
+    private void addIdleStateHandlerIfNeeded(final ChannelPipeline pipeline) {
+        long idleTimeout = 
ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaData().getProps().getValue(ConfigurationPropertyKey.PROXY_FRONTEND_CONNECTION_IDLE_TIMEOUT);
+        if (0 < idleTimeout) {
+            pipeline.addLast(new IdleStateHandler(0, 0, idleTimeout, 
TimeUnit.SECONDS));
+        }
+    }
 }
diff --git 
a/proxy/frontend/core/src/test/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandlerTest.java
 
b/proxy/frontend/core/src/test/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandlerTest.java
index 549fb9cacb6..cab280430b3 100644
--- 
a/proxy/frontend/core/src/test/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandlerTest.java
+++ 
b/proxy/frontend/core/src/test/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandlerTest.java
@@ -22,6 +22,7 @@ import io.netty.buffer.Unpooled;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.timeout.IdleStateEvent;
 import lombok.SneakyThrows;
 import org.apache.shardingsphere.authentication.result.AuthenticationResult;
 import 
org.apache.shardingsphere.authentication.result.AuthenticationResultBuilder;
@@ -30,7 +31,9 @@ import 
org.apache.shardingsphere.database.connector.core.type.DatabaseType;
 import 
org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
 import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
 import org.apache.shardingsphere.database.protocol.payload.PacketPayload;
+import org.apache.shardingsphere.infra.executor.sql.process.Process;
 import org.apache.shardingsphere.infra.executor.sql.process.ProcessEngine;
+import org.apache.shardingsphere.infra.executor.sql.process.ProcessRegistry;
 import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
 import org.apache.shardingsphere.infra.metadata.user.Grantee;
 import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
@@ -71,15 +74,15 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockStatic;
-import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 @ExtendWith(AutoMockExtension.class)
-@StaticMockSettings(ProxyContext.class)
+@StaticMockSettings({ProxyContext.class, ProcessRegistry.class})
 class FrontendChannelInboundHandlerTest {
     
     private static final int CONNECTION_ID = 1;
@@ -266,6 +269,29 @@ class FrontendChannelInboundHandlerTest {
         verify(databaseConnectionManager, never()).getConnectionResourceLock();
     }
     
+    @Test
+    void assertUserEventTriggeredWithIdleStateEventAndIdle() throws Exception {
+        channel.register();
+        ProcessRegistry processRegistry = mock(ProcessRegistry.class);
+        when(ProcessRegistry.getInstance()).thenReturn(processRegistry);
+        
channel.pipeline().fireUserEventTriggered(IdleStateEvent.ALL_IDLE_STATE_EVENT);
+        assertThat(channel.isOpen(), is(false));
+    }
+    
+    @Test
+    void assertUserEventTriggeredWithIdleStateEventAndNotIdle() throws 
Exception {
+        channel.register();
+        ProcessRegistry processRegistry = mock(ProcessRegistry.class);
+        when(ProcessRegistry.getInstance()).thenReturn(processRegistry);
+        String processId = "foo_id";
+        connectionSession.setProcessId(processId);
+        Process process = mock(Process.class);
+        when(process.isIdle()).thenReturn(false);
+        when(processRegistry.get(processId)).thenReturn(process);
+        
channel.pipeline().fireUserEventTriggered(IdleStateEvent.ALL_IDLE_STATE_EVENT);
+        assertThat(channel.isOpen(), is(true));
+    }
+    
     @SneakyThrows(ReflectiveOperationException.class)
     private AtomicBoolean getAuthenticated() {
         return (AtomicBoolean) 
Plugins.getMemberAccessor().get(FrontendChannelInboundHandler.class.getDeclaredField("authenticated"),
 frontendChannelInboundHandler);
diff --git 
a/proxy/frontend/core/src/test/java/org/apache/shardingsphere/proxy/frontend/netty/ServerHandlerInitializerTest.java
 
b/proxy/frontend/core/src/test/java/org/apache/shardingsphere/proxy/frontend/netty/ServerHandlerInitializerTest.java
index 3bd5f42ca55..8e84dd504fb 100644
--- 
a/proxy/frontend/core/src/test/java/org/apache/shardingsphere/proxy/frontend/netty/ServerHandlerInitializerTest.java
+++ 
b/proxy/frontend/core/src/test/java/org/apache/shardingsphere/proxy/frontend/netty/ServerHandlerInitializerTest.java
@@ -19,22 +19,29 @@ package org.apache.shardingsphere.proxy.frontend.netty;
 
 import io.netty.channel.ChannelPipeline;
 import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.timeout.IdleStateHandler;
 import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
 import org.apache.shardingsphere.database.protocol.codec.PacketCodec;
 import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import org.apache.shardingsphere.infra.config.props.ConfigurationPropertyKey;
+import org.apache.shardingsphere.mode.manager.ContextManager;
+import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
 import 
org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExtension;
 import 
org.apache.shardingsphere.test.infra.framework.extension.mock.ConstructionMockSettings;
+import 
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockSettings;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 @ExtendWith(AutoMockExtension.class)
 @ConstructionMockSettings(FrontendChannelInboundHandler.class)
+@StaticMockSettings(ProxyContext.class)
 class ServerHandlerInitializerTest {
     
     @Test
@@ -42,10 +49,14 @@ class ServerHandlerInitializerTest {
         SocketChannel channel = mock(SocketChannel.class);
         ChannelPipeline pipeline = mock(ChannelPipeline.class);
         when(channel.pipeline()).thenReturn(pipeline);
+        ContextManager contextManager = mock(ContextManager.class, 
RETURNS_DEEP_STUBS);
+        
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+        
when(contextManager.getMetaDataContexts().getMetaData().getProps().getValue(ConfigurationPropertyKey.PROXY_FRONTEND_CONNECTION_IDLE_TIMEOUT)).thenReturn(28800L);
         new 
ServerHandlerInitializer(TypedSPILoader.getService(DatabaseType.class, 
"FIXTURE")).initChannel(channel);
         verify(pipeline).addLast(any(ChannelAttrInitializer.class));
         verify(pipeline).addLast(any(PacketCodec.class));
         
verify(pipeline).addLast(any(FrontendChannelLimitationInboundHandler.class));
+        verify(pipeline).addLast(any(IdleStateHandler.class));
         
verify(pipeline).addLast(eq(ProxyFlowControlHandler.class.getSimpleName()), 
any(ProxyFlowControlHandler.class));
         
verify(pipeline).addLast(eq(FrontendChannelInboundHandler.class.getSimpleName()),
 any(FrontendChannelInboundHandler.class));
     }
diff --git 
a/test/e2e/sql/src/test/resources/cases/ral/dataset/empty_rules/show_dist_variables.xml
 
b/test/e2e/sql/src/test/resources/cases/ral/dataset/empty_rules/show_dist_variables.xml
index d1017983726..07322251c7e 100644
--- 
a/test/e2e/sql/src/test/resources/cases/ral/dataset/empty_rules/show_dist_variables.xml
+++ 
b/test/e2e/sql/src/test/resources/cases/ral/dataset/empty_rules/show_dist_variables.xml
@@ -32,6 +32,7 @@
     <row values="persist_schemas_to_repository_enabled| true"/>
     <row values="proxy_backend_query_fetch_size| -1" />
     <row values="proxy_default_port| 3307" />
+    <row values="proxy_frontend_connection_idle_timeout| 28800" />
     <row values="proxy_frontend_database_protocol_type| " />
     <row values="proxy_frontend_executor_size| 0" />
     <row values="proxy_frontend_flush_threshold| 128" />

Reply via email to