C# WebSocket Fleck 内存泄漏
最近在维护公司旧项目,内存泄漏严重,找了行业内大佬帮忙分析Dump文件(windbg我不擅长),大佬指出问题在于Fleck,这里记录一下。
整理一下问题:
1. 大佬指出 System.Threading.Tasks.ContinuationTaskFromTask 和 System.ObjectDisposedException 有71完个对象。
2. System.ObjectDisposedException 是由调用 System.Net.Sockets.NetworkStream.EndWrite(System.IAsyncResult) 方法时无法访问已释放的对象造成的。
3. System.Threading.Tasks.ContinuationTaskFromTask 也就是 Task.ContinueWith(t => { }) 过多造成的,这种写法比较传统,导致托管堆大量的 task,引发碎片化。
4. 个人观察每次释放时 都会频繁调用 Fleck.IWebSocketConnection.OnClose 事件,触发事件的 OnClose 的参数 IWebSocketConnectionInfo.Id 完全相同。
问题解决:
1. 修改了Fleck的源码,修改了Fleck.SocketWrapper 类中使用了TaskFactory.FromAsync(begin, end).ContinueWith(t => { })的内容,但感觉问题没定位到。
修改了Accept(Action<ISocket>, Action<Exception>) 方法、Receive(byte[], Action<int>, Action<Exception>, int)方法 和 Send(byte[], Action, Action<Exception>) 方法,代码如下:
public void Accept(Action<ISocket> callback, Action<Exception> error) { try { Action<IAsyncResult> end = (ar) => { try { Socket client = _socket.EndAccept(ar); var sw = new SocketWrapper(client); callback(sw); } catch (Exception e) { error(e); } }; _socket.BeginAccept(new AsyncCallback(end), null); } catch (Exception e) { error(e); } } public void Receive(byte[] buffer, Action<int> callback, Action<Exception> error, int offset) { try { Action<IAsyncResult> end = (ar) => { try { var num = _stream.EndRead(ar); callback(num); } catch (Exception ex) { error(ex); } }; _stream.BeginRead(buffer, offset, buffer.Length, new AsyncCallback(end), null); } catch (Exception e) { error(e); } } public async Task Send(byte[] buffer, Action callback, Action<Exception> error) { if (_tokenSource.IsCancellationRequested) return; try { if (!_stream.CanWrite) { return; } await _stream.WriteAsync(buffer, 0, buffer.Length, _tokenSource.Token).ConfigureAwait(false);
await _stream.FlushAsync(_tokenSource.Token).ConfigureAwait(false); // 这个其实没用,源码里面是空的
callback(); } catch (Exception e) { error(e); } }
2. 研究了 WebSocket客户端代码,发现客户端多次执行了 window.ws = new window.WebSocket('ws://localhost:8181/'); 每次都赋予 window.ws 值前都没有关闭,入截图所示
修改为:创建window.WebSocket("") 和 关闭页面前都执行 window.ws.close();
3. Fleck.IWebSocketConnection.OnClose 事件 触发时 循环所有 IWebSocketConnection 客户端集合,移除 IsAvailable==false的数据;服务器向客户端推送数据前判断 IsAvailable==true;
上面的修改后 两周过去了,但问题还是没有解决,中间还是各种尝试。这里做下总结
问题是:TCP协议需要客户端确认接收,WebSocket频繁发送消息到客户端时,可能某些原因客户端卡顿了不能及时确认回复,导致Task一直停留在EndWrite()的回调中直到连接关闭。
期间进行了各种尝试:
1. 打印 当前客户端连接数量,确实监控到一下额外的客户端。
2. 修改 Fleck.SocketWrapper.cs 类,构造方法中增加 socket.ReceiveTimeout = 1000 和 socket.SendTimeout = 1000 的设置。也就是1秒超时。
3. 当内存增大时,手动关闭客户端连接。过段时间内存也确实降了下来。
4. 也看了 System.Net.Sockets.Socket 和 System.Net.Sockets.NetworkStream 的源码,但没有太大的入手的。
最后通过修改客户端代码解决的,修改了一些数组只Push 不 Shift 的问题。要怪只能怪写客户端的人太不责任了。