diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 2f363f0292b..9b4a208890e 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -46,7 +46,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#7142](https://github.com/apache/incubator-seata/pull/7142)] upgrade commons-compress to 1.27.1 - [[#7149](https://github.com/apache/incubator-seata/pull/7149)] Fix abnormal character display issues in ./distribution/NOTICE.md - [[#7170](https://github.com/apache/incubator-seata/pull/7170)] Optimize seata client I/O processing by adjusting thread count - +- [[#7095](https://github.com/apache/incubator-seata/pull/7095)] support fast fail when channel disconnect after message sent for RemotingClient ### security: - [[#6069](https://github.com/apache/incubator-seata/pull/6069)] Upgrade Guava dependencies to fix security vulnerabilities diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 29fed04339c..ea5a0eaf01e 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -46,7 +46,7 @@ - [[#7142](https://github.com/apache/incubator-seata/pull/7142)] 升级 commons-compress 至 1.27.1 版本 - [[#7149](https://github.com/apache/incubator-seata/pull/7149)] 修复./distribution/NOTICE.md文件中的异常字符串显示问题 - [[#7170](https://github.com/apache/incubator-seata/pull/7170)] 通过调整线程数优化 Seata 客户端 I/O 处理 - +- [[#7095](https://github.com/apache/incubator-seata/pull/7095)] 当消息发送后通道中断,快速失败,以便于减少不必要的等待 ### security: - [[#6069](https://github.com/apache/incubator-seata/pull/6069)] 升级Guava依赖版本,修复安全漏洞 diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/AbstractNettyRemotingClient.java b/core/src/main/java/org/apache/seata/core/rpc/netty/AbstractNettyRemotingClient.java index 8618c3030b4..ec92889e6ac 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/AbstractNettyRemotingClient.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/AbstractNettyRemotingClient.java @@ -20,6 +20,7 @@ import java.net.InetSocketAddress; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; @@ -91,6 +92,9 @@ public abstract class AbstractNettyRemotingClient extends AbstractNettyRemoting protected final Map childToParentMap = new ConcurrentHashMap<>(); + // Channel -> Set + protected final ConcurrentHashMap> channelToRequestIds = new ConcurrentHashMap<>(); + /** * When batch sending is enabled, the message will be stored to basketMap * Send via asynchronous thread {@link AbstractNettyRemotingClient.MergedSendRunnable} @@ -181,6 +185,7 @@ public Object sendSyncRequest(Object msg) throws TimeoutException { } } else { Channel channel = clientChannelManager.acquireChannel(serverAddress); + channelToRequestIds.computeIfAbsent(channel, ch -> ConcurrentHashMap.newKeySet()).add(rpcMessage.getId()); return super.sendSync(channel, rpcMessage, timeoutMillis); } @@ -193,6 +198,7 @@ public Object sendSyncRequest(Channel channel, Object msg) throws TimeoutExcepti return null; } RpcMessage rpcMessage = buildRequestMessage(msg, ProtocolConstants.MSGTYPE_RESQUEST_SYNC); + channelToRequestIds.computeIfAbsent(channel, ch -> ConcurrentHashMap.newKeySet()).add(rpcMessage.getId()); return super.sendSync(channel, rpcMessage, this.getRpcRequestTimeout()); } @@ -214,6 +220,7 @@ public void sendAsyncRequest(Channel channel, Object msg) { childToParentMap.put(msgId, parentId); } } + channelToRequestIds.computeIfAbsent(channel, ch -> ConcurrentHashMap.newKeySet()).add(rpcMessage.getId()); } super.sendAsync(channel, rpcMessage); } @@ -423,7 +430,9 @@ class ClientHandler extends ChannelDuplexHandler { @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof RpcMessage) { - processMessage(ctx, (RpcMessage)msg); + RpcMessage rpcMessage = (RpcMessage)msg; + processMessage(ctx, rpcMessage); + removeRequestIdFromChannel(ctx.channel(), rpcMessage.getId()); } else { LOGGER.error("rpcMessage type error"); } @@ -448,6 +457,7 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { LOGGER.info("channel inactive: {}", ctx.channel()); } clientChannelManager.releaseChannel(ctx.channel(), NetUtil.toStringAddress(ctx.channel().remoteAddress())); + failFuturesForChannel(ctx.channel(), new FrameworkException(FrameworkErrorCode.ChannelIsNotWritable)); super.channelInactive(ctx); } @@ -489,6 +499,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E if (LOGGER.isInfoEnabled()) { LOGGER.info("remove exception rm channel:{}", ctx.channel()); } + failFuturesForChannel(ctx.channel(), cause); super.exceptionCaught(ctx, cause); } @@ -501,4 +512,71 @@ public void close(ChannelHandlerContext ctx, ChannelPromise future) throws Excep } } + /** + * Iterates over futures and marks all MessageFutures associated with the specified Channel as failed. + * + * @param channel The Channel that has been disconnected or encountered an exception. + * @param cause The reason for the failure. + */ + private void failFuturesForChannel(Channel channel, Throwable cause) { + Set requestIds = channelToRequestIds.remove(channel); + if (requestIds != null) { + for (Integer requestId : requestIds) { + MessageFuture future = futures.remove(requestId); + if (future != null) { + future.setResultMessage(cause); + } + } + } + } + + /** + * Removes the association between the specified Channel and requestId from the channelToRequestIds mapping. + * If the Channel no longer has any associated requestId, the Channel will be removed from the mapping. + * + * @param channel The Channel that has been disconnected or encountered an exception. + * @param requestId The requestId to be removed. + */ + private void removeRequestIdFromChannel(Channel channel, Integer requestId) { + if (channel == null) { + if (requestId != null) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Attempted to remove requestId {} from a null channel.", requestId); + } + } else { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Attempted to remove a null requestId from a null channel."); + } + } + return; + } + + if (requestId == null) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Attempted to remove a null requestId from channel {}.", channel); + } + return; + } + + channelToRequestIds.computeIfPresent(channel, (ch, requestIds) -> { + boolean removed = requestIds.remove(requestId); + if (removed) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Removed requestId {} from channel {}.", requestId, ch); + } + } else { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Attempted to remove non-existing requestId {} from channel {}.", requestId, ch); + } + } + + if (requestIds.isEmpty()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("No more requestIds associated with channel {}. Channel removed from mapping.", ch); + } + return null; + } + return requestIds; + }); + } }