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 连接 * 关闭 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() { private void stopWebSocket() {
cancelSilentDisconnectWatchdog(); cancelSilentDisconnectWatchdog();
@ -558,6 +565,21 @@ public class FeishuChannelAdapter extends AbstractChannelAdapter implements Stre
wsThread.interrupt(); wsThread.interrupt();
wsThread = null; 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 WebSocket,close(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; wsClient = null;
} }