diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java
index 6dc9a0f54a89..44218a5f02d0 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java
@@ -22,6 +22,7 @@
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.Assert;
+import org.apache.dubbo.remoting.exchange.PortUnificationExchanger;
import org.apache.dubbo.rpc.GracefulShutdown;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.model.ModuleModel;
@@ -93,6 +94,8 @@ private void doDestroy() {
for (GracefulShutdown gracefulShutdown : gracefulShutdowns) {
gracefulShutdown.readonly();
}
+ // close all exchangers to reject new requests
+ PortUnificationExchanger.goaway();
boolean hasModuleBindSpring = false;
// check if any modules are bound to Spring
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/GoAwayEvent.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/GoAwayEvent.java
new file mode 100644
index 000000000000..e3e2e2fc9361
--- /dev/null
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/GoAwayEvent.java
@@ -0,0 +1,38 @@
+/*
+ * 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.dubbo.remoting.api;
+
+/**
+ * Event used to trigger graceful shutdown for protocols that support it.
+ * For HTTP/2 (Triple protocol), this event triggers sending a GOAWAY frame
+ * to notify clients that the server is shutting down.
+ *
+ *
This event is fired through Netty's user event mechanism and is handled
+ * by protocol-specific handlers (e.g., TripleServerConnectionHandler for Triple protocol).
+ * Protocols that don't support GOAWAY (e.g., Dubbo protocol) will simply ignore this event.
+ */
+public class GoAwayEvent {
+
+ /**
+ * Singleton instance of GoAwayEvent.
+ */
+ public static final GoAwayEvent INSTANCE = new GoAwayEvent();
+
+ private GoAwayEvent() {
+ // Private constructor to enforce singleton pattern
+ }
+}
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/AbstractPortUnificationServer.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/AbstractPortUnificationServer.java
index c3a1513328e2..8ef7d7f0be4b 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/AbstractPortUnificationServer.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/pu/AbstractPortUnificationServer.java
@@ -103,4 +103,6 @@ public Map getSupportedHandlers() {
// this getter is just used by implementation of this class
return supportedHandlers;
}
+
+ public abstract void goaway();
}
diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/PortUnificationExchanger.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/PortUnificationExchanger.java
index 58da07715ff7..32f6d89bd2ac 100644
--- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/PortUnificationExchanger.java
+++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/PortUnificationExchanger.java
@@ -37,7 +37,8 @@ public class PortUnificationExchanger {
private static final ErrorTypeAwareLogger log =
LoggerFactory.getErrorTypeAwareLogger(PortUnificationExchanger.class);
- private static final ConcurrentMap servers = new ConcurrentHashMap<>();
+
+ private static final ConcurrentMap servers = new ConcurrentHashMap<>();
public static RemotingServer bind(URL url, ChannelHandler handler) {
ConcurrentHashMapUtils.computeIfAbsent(servers, url.getAddress(), addr -> {
@@ -52,7 +53,7 @@ public static RemotingServer bind(URL url, ChannelHandler handler) {
});
servers.computeIfPresent(url.getAddress(), (addr, server) -> {
- ((AbstractPortUnificationServer) server).addSupportedProtocol(url, handler);
+ server.addSupportedProtocol(url, handler);
return server;
});
return servers.get(url.getAddress());
@@ -69,9 +70,9 @@ public static AbstractConnectionClient connect(URL url, ChannelHandler handler)
}
public static void close() {
- final ArrayList toClose = new ArrayList<>(servers.values());
+ final ArrayList toClose = new ArrayList<>(servers.values());
servers.clear();
- for (RemotingServer server : toClose) {
+ for (AbstractPortUnificationServer server : toClose) {
try {
server.close();
} catch (Throwable throwable) {
@@ -80,8 +81,18 @@ public static void close() {
}
}
+ public static void goaway() {
+ for (AbstractPortUnificationServer server : servers.values()) {
+ try {
+ server.goaway();
+ } catch (Throwable throwable) {
+ log.error(PROTOCOL_ERROR_CLOSE_SERVER, "", "", "Goaway all port unification server failed", throwable);
+ }
+ }
+ }
+
// for test
- public static ConcurrentMap getServers() {
+ public static ConcurrentMap getServers() {
return servers;
}
diff --git a/dubbo-remoting/dubbo-remoting-netty/src/main/java/org/apache/dubbo/remoting/transport/netty/NettyPortUnificationServer.java b/dubbo-remoting/dubbo-remoting-netty/src/main/java/org/apache/dubbo/remoting/transport/netty/NettyPortUnificationServer.java
index f616dcb9327c..0b8cdc98308d 100644
--- a/dubbo-remoting/dubbo-remoting-netty/src/main/java/org/apache/dubbo/remoting/transport/netty/NettyPortUnificationServer.java
+++ b/dubbo-remoting/dubbo-remoting-netty/src/main/java/org/apache/dubbo/remoting/transport/netty/NettyPortUnificationServer.java
@@ -71,6 +71,16 @@ public void addSupportedProtocol(URL url, ChannelHandler handler) {
super.addSupportedProtocol(url, ChannelHandlers.wrap(handler, url));
}
+ @Override
+ public void goaway() {
+ // Netty 3 does not support HTTP/2 (Triple protocol), so we only need to mark channels as closing.
+ // The actual graceful shutdown for Dubbo protocol is handled by sending READONLY_EVENT
+ // through DubboGracefulShutdown.readonly() before this method is called.
+ for (Channel channel : getChannels()) {
+ channel.startClose();
+ }
+ }
+
@Override
public void close() {
if (channel != null) {
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServer.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServer.java
index 8bbdfb188fc3..2b3b2645efbf 100644
--- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServer.java
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServer.java
@@ -24,6 +24,7 @@
import org.apache.dubbo.remoting.ChannelHandler;
import org.apache.dubbo.remoting.Constants;
import org.apache.dubbo.remoting.RemotingException;
+import org.apache.dubbo.remoting.api.GoAwayEvent;
import org.apache.dubbo.remoting.api.WireProtocol;
import org.apache.dubbo.remoting.api.pu.AbstractPortUnificationServer;
import org.apache.dubbo.remoting.transport.dispatcher.ChannelHandlers;
@@ -87,6 +88,20 @@ public void close() {
}
}
+ @Override
+ public void goaway() {
+ for (Channel channel : getChannels()) {
+ channel.startClose();
+ // Fire GoAwayEvent through Netty pipeline to trigger protocol-specific graceful shutdown
+ // For HTTP/2 (Triple protocol), TripleServerConnectionHandler will handle this event
+ // and send Http2GoAwayFrame to notify clients
+ // For other protocols (e.g., Dubbo), this event will be ignored
+ if (channel instanceof NettyChannel) {
+ ((NettyChannel) channel).getNioChannel().pipeline().fireUserEventTriggered(GoAwayEvent.INSTANCE);
+ }
+ }
+ }
+
public void bind() throws Throwable {
if (channel == null) {
doOpen();
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProxyProtocol.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProxyProtocol.java
index 6123f22ff2fc..a325ce36af04 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProxyProtocol.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProxyProtocol.java
@@ -16,14 +16,10 @@
*/
package org.apache.dubbo.rpc.protocol;
-import org.apache.dubbo.common.Parameters;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.remoting.Channel;
-import org.apache.dubbo.remoting.ChannelHandler;
import org.apache.dubbo.remoting.Constants;
-import org.apache.dubbo.remoting.RemotingException;
import org.apache.dubbo.remoting.RemotingServer;
import org.apache.dubbo.rpc.Exporter;
import org.apache.dubbo.rpc.Invocation;
@@ -33,8 +29,6 @@
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;
-import java.net.InetSocketAddress;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -211,68 +205,4 @@ public Map getAttributes() {
return attributes;
}
}
-
- protected abstract class RemotingServerAdapter implements RemotingServer {
-
- public abstract Object getDelegateServer();
-
- /**
- * @return
- */
- @Override
- public boolean isBound() {
- return false;
- }
-
- @Override
- public Collection getChannels() {
- return null;
- }
-
- @Override
- public Channel getChannel(InetSocketAddress remoteAddress) {
- return null;
- }
-
- @Override
- public void reset(Parameters parameters) {}
-
- @Override
- public void reset(URL url) {}
-
- @Override
- public URL getUrl() {
- return null;
- }
-
- @Override
- public ChannelHandler getChannelHandler() {
- return null;
- }
-
- @Override
- public InetSocketAddress getLocalAddress() {
- return null;
- }
-
- @Override
- public void send(Object message) throws RemotingException {}
-
- @Override
- public void send(Object message, boolean sent) throws RemotingException {}
-
- @Override
- public void close() {}
-
- @Override
- public void close(int timeout) {}
-
- @Override
- public void startClose() {}
-
- @Override
- public boolean isClosed() {
- return false;
- }
- }
}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/transport/TripleServerConnectionHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/transport/TripleServerConnectionHandler.java
index 06c517208efe..4606575f4ae8 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/transport/TripleServerConnectionHandler.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/transport/TripleServerConnectionHandler.java
@@ -18,6 +18,7 @@
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.remoting.api.GoAwayEvent;
import java.io.IOException;
import java.net.SocketException;
@@ -97,7 +98,13 @@ private boolean isQuiteException(Throwable t) {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
- super.userEventTriggered(ctx, evt);
+ if (evt instanceof GoAwayEvent) {
+ // Handle GoAwayEvent by initiating graceful shutdown
+ // This will send HTTP/2 GOAWAY frame to notify clients
+ close(ctx, ctx.newPromise());
+ } else {
+ super.userEventTriggered(ctx, evt);
+ }
}
@Override