dotnet / dotnet/AspNetCore.Docs

Clarify the need to hold a strong rooted reference to Web Socket Request TCS in background services

Open
#30,449 0 comments 0 reactions 1 assignee Claimed by @wadepickett View on GitHub
doc-enhancement Source - Docs.ms
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.