fix(feishu): properly close WebSocket connection to prevent resource leak (#221)

stopWebSocket() only nullified the wsClient reference without calling
disconnect() on the SDK client. This left the old WebSocket connection's
pingLoop thread and ExecutorService running, leaking file descriptors
and threads on each reconnect. Over time, accumulated leaks prevented
new connections from being established, causing the Feishu channel to
silently stop receiving messages.

Fix: use reflection to access the SDK's protected `conn` field and call
close(1000) on the OkHttp WebSocket, triggering the SDK's onClosed →
disconnect() cleanup chain.

Note: oapi-sdk 2.7.1 adds a public close() method that would make this
reflection unnecessary. Consider upgrading as a follow-up.

Closes #220
This commit is contained in:
倪程伟 2026-05-25 21:28:05 +08:00 committed by GitHub
parent a01f0354eb
commit 54e3f7f3fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -549,7 +549,14 @@ public class FeishuChannelAdapter extends AbstractChannelAdapter implements Stre
/**
* 关闭 WebSocket 连接
* SDK start() 在线程中阻塞运行通过中断线程来触发停止
* <p>
* 必须主动关闭底层 WebSocket 连接并触发 SDK 的内部清理停止 pingLoop
* 释放 ExecutorService仅置空引用会导致旧连接的 pingLoop 和线程池
* 持续运行造成文件描述符和线程泄漏最终使新连接无法建立
* <p>
* SDK {@code disconnect()} protected 无法直接调用通过反射
* 获取内部 {@code conn} 字段并发送 close(1000) 触发 SDK
* {@code onClosed disconnect()} 清理链路
*/
private void stopWebSocket() {
cancelSilentDisconnectWatchdog();
@ -558,6 +565,21 @@ public class FeishuChannelAdapter extends AbstractChannelAdapter implements Stre
wsThread.interrupt();
wsThread = null;
}
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 WebSocketclose(1000) 会触发 onClosed disconnect()
var closeMethod = conn.getClass().getMethod("close", int.class, String.class);
closeMethod.invoke(conn, 1000, "client closed");
}
} catch (Exception e) {
log.debug("[feishu] Error during WebSocket disconnect: {}", e.getMessage());
}
}
wsClient = null;
}