From e9ead959b83e996a9c2d0997e10597e84124b774 Mon Sep 17 00:00:00 2001 From: matevip Date: Mon, 25 May 2026 21:31:09 +0800 Subject: [PATCH] fix(feishu): invoke SDK disconnect() directly and surface cleanup failures --- .../channel/feishu/FeishuChannelAdapter.java | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuChannelAdapter.java b/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuChannelAdapter.java index 0f350e97..cf452cf5 100644 --- a/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuChannelAdapter.java +++ b/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuChannelAdapter.java @@ -548,15 +548,20 @@ public class FeishuChannelAdapter extends AbstractChannelAdapter implements Stre } /** - * 关闭 WebSocket 连接 + * 关闭 WebSocket 连接。 *

* 必须主动关闭底层 WebSocket 连接并触发 SDK 的内部清理(停止 pingLoop、 * 释放 ExecutorService)。仅置空引用会导致旧连接的 pingLoop 和线程池 * 持续运行,造成文件描述符和线程泄漏,最终使新连接无法建立。 *

- * SDK 的 {@code disconnect()} 是 protected 的,无法直接调用。通过反射 - * 获取内部 {@code conn} 字段并发送 close(1000) 触发 SDK 的 - * {@code onClosed → disconnect()} 清理链路。 + * 走 SDK 内部 {@code disconnect()} 这条单一入口完成 cleanup —— 它本身就是 + * SDK 在 {@code onClosed} 回调里调用的方法,包含 + * {@code conn.close(1000) → executor.shutdown() → 字段清零} 的全套动作。 + * 该方法在 SDK 2.6.1 中是 {@code protected},外部需通过反射 + setAccessible + * 调用;SDK 2.7.1 起新增了 public {@code close()},届时整段反射可以删除。 + *

+ * 反射失败(例如未来 SDK 重命名 {@code disconnect})以 WARN 级别记录而非 + * 静默吞掉,避免泄漏在升级后悄无声息地复发。 */ private void stopWebSocket() { cancelSilentDisconnectWatchdog(); @@ -567,17 +572,14 @@ public class FeishuChannelAdapter extends AbstractChannelAdapter implements Stre } if (wsClient != null) { try { - // 反射获取 protected conn 字段,发送 close(1000) 触发 SDK 清理 - var connField = wsClient.getClass().getDeclaredField("conn"); - connField.setAccessible(true); - Object conn = connField.get(wsClient); - if (conn != null) { - // conn 是 OkHttp WebSocket,close(1000) 会触发 onClosed → disconnect() - var closeMethod = conn.getClass().getMethod("close", int.class, String.class); - closeMethod.invoke(conn, 1000, "client closed"); - } + var disconnect = wsClient.getClass().getDeclaredMethod("disconnect"); + disconnect.setAccessible(true); + disconnect.invoke(wsClient); + } catch (NoSuchMethodException e) { + log.warn("[feishu] SDK Client.disconnect() not found — WebSocket cleanup skipped, " + + "leak may recur until SDK API is re-verified: {}", e.getMessage()); } catch (Exception e) { - log.debug("[feishu] Error during WebSocket disconnect: {}", e.getMessage()); + log.warn("[feishu] WebSocket disconnect failed: {}", e.getMessage()); } } wsClient = null;