dotnet / dotnet/AspNetCore.Docs
Clarify the need to hold a strong rooted reference to Web Socket Request TCS in background services
- Dominant language
- C#
- Stars
- 13.1k
- Forks
- 24.6k
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 97
Description
The section about using websocket from background service only says you can't complete the request until you are done with the websocket. And to do that you send a TCS to the service. But in many event based service architectures event subscriptions are WeakReferences. And It seems like ASP.NET Core pipeline also only hold a weak reference to the request task. This results in the entire async Task getting garbage collected and an error from the pipeline saying `crit: Microsoft.AspNetCore.Server.Kestrel[23] Connection id "XXXXXX" application never completed.`.
And the only solution I found to this is to have the controller keep the TCS in a dictionary as long as the web socket is in use by the background service and then in a finally clause remove it.
Another thing is that Socket State is never updated unless you call socket.ReceiveAsync(). So once the request is not GCed, it will never complete if looking at web socket state. This should probably also be clarified. I solved this using a dummy read:
`_ = webSocket.ReceiveAsync(data, default).ContinueWith(_ => tcs.TrySetResult());`
My working endresult for inspiration to a better example:
```csharp
static ConcurrentDictionary _activeWebSockets = new();
[Route("ws")]
public async Task WebSocket()
{
if (HttpContext.WebSockets.IsWebSocketRequest) {
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
var tcs = new TaskCompletionSource();
try {
_activeWebSockets.TryAdd(tcs, default);
var data = new byte[1];
_ = webSocket.ReceiveAsync(data, default).ContinueWith(_ => {
tcs.TrySetResult();
});
using var sub = _evtSvc.Subscribe(res => {
if (webSocket.State != System.Net.WebSockets.WebSocketState.Open) {
tcs.TrySetResult();
return;
}
var msg = JsonSerializer.SerializeToUtf8Bytes(res, _serializerOptions);
webSocket.SendAsync(msg, System.Net.WebSockets.WebSocketMessageType.Text, true, default);
});
await tcs.Task;
}
finally {
_activeWebSockets.TryRemove(tcs, out var _);
}
}
else {
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
}
}
```
---
#### Document Details
⚠ *Do not edit this section. It is required for learn.microsoft.com ➟ GitHub issue linking.*
* ID: a3b0cac1-adc7-434c-d867-6e28e39bdae7
* Version Independent ID: 762efeed-010e-f422-03d0-22d6681b2851
* Content: [WebSockets support in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-7.0)
* Content Source: [aspnetcore/fundamentals/websockets.md](https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/fundamentals/websockets.md)
* Product: **aspnet-core**
* Technology: **aspnetcore-fundamentals**
* GitHub Login: @wadepickett
* Microsoft Alias: **wpickett**
Contributor guide
Assessment
This issue has not been assessed yet.