Hello guys i am currently testing a websocket server written in Asp .Net Core 2.0 and i believe i have a memory leak.I can not find it even though i have tried to dispose everything that might be a concern.
The tests were done consecutively and the value of the ram is taken when it reaches stability.(this varies from 5 sec to 20 sec).
The occupied Ram is measured with plain Task Manager Monitor.
The testing tool:
Thor
: https://github.com/observing/thor
The command : thor --amount [amount] ws://[HostIP (localhost)]:[portnumber]
Results:
Connections | RAM Consumed at the end of test (GB):
0 4.54
50 4.55
100 4.55
150 4.61
200 4.68
300 4.76
400 4.59
400 4.59
500 4.62
500 4.65
550 4.65
The WebSocket Server:
SocketMiddleware -used by the appbuilder:
public class SocketMiddleware
{
public byte[] ToSegment(string message) => System.Text.Encoding.UTF8.GetBytes(message);
ClientTracker clientTracker; //the socket clients tracker this is the object we're speaking of
RequestDelegate next;
public SocketMiddleware(ClientTracker tracker,RequestDelegate del)
{
this.clientTracker=tracker;
this.next=del;
}
public async Task Invoke(HttpContext context)
{
if(!context.WebSockets.IsWebSocketRequest)
{
await this.next.Invoke(context);
return;
}
await this.clientTracker.AddClient(context.WebSockets);
}
}
SocketTracker- this is the suspect which is dealing with all the opened sockets
public class ClientTracker
{
ConcurrentDictionary<string, Client> clientMap = new ConcurrentDictionary<string, Client>();
public string CreateConnectionID() => Guid.NewGuid().ToString();
public string GetIDOfSocket(WebSocket socket) => this.clientMap.First(x => x.Value.webSocket.Equals(socket)).Key;
public Client GetClientByID(string id)
{
this.clientMap.TryGetValue(id, out Client client);
return client;
}
public async Task AddClient(WebSocketManager manager)
{
using (WebSocket socket = await manager.AcceptWebSocketAsync())
{
Client newClient = Client.CreateClient(socket, CreateConnectionID());
if(clientMap.TryAdd(newClient.clientID, newClient))
{
await ReceiveMessage(newClient);
}
}
}
public async Task ReceiveMessage(Client client)
{
while (client.webSocket.State == WebSocketState.Open)
{
WebSocketReceiveResult result = await client.ReceiveResult();
//dosomething with result...
if (result.MessageType == WebSocketMessageType.Close)
{
await client.webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client closed", CancellationToken.None);
break;
}
//send custom message
await client.SendMessage("lala");
}
}
}
Client- socketWrapper which performs all required operations on the socket and stores temporary data to be used by the tracker
public class Client
{
//Fields
public readonly WebSocket webSocket;
public readonly string clientID;
public StringBuilder tempData;
//Auxiliary
private const int BufferSize = 1024 * 4;
public static Client CreateClient(WebSocket socket, string id)
{
Client client = new Client(socket, id);
return client;
}
public Client(WebSocket socket, string id)
{
this.webSocket = socket;
this.clientID = id;
tempData = new StringBuilder();
}
public async Task<WebSocketReceiveResult> ReceiveResult()
{
tempData.Clear();
ArraySegment<byte> segment = new ArraySegment<byte>(new byte[BufferSize]);
WebSocketReceiveResult result = await this.webSocket.ReceiveAsync(segment, CancellationToken.None);
tempData.Append(BitConverter.ToString(segment.Array));
return result;
}
public async Task SendMessage(string message)
{
byte[] bytes = Encoding.UTF8.GetBytes(message);
await this.webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<ClientTracker>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseWebSockets();
app.UseMiddleware<SocketMiddleware>();
}
P.S: The server was not closed between requests.Could it be the concurrent dictionary ?Besides it, the reference of the Client is cleaned ,the Client is disposed,and the socket is closed.The string builder can not be disposed,and the Tracker/Middleware live as long as the app lives.