C
C#7mo ago
xriba

✅ Hosted Service running as Windows Service

I have an application running as a Windows Service and I'd like to run some code after the system starts and before it shuts down. I have tried using HostedServices and everything works fine if I manually start and stop the service. However, it does not work when starting or shutting down the system. Here is a simplified version of my service, I can also provide an .exe if necessary.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Net.WebSockets;
using System.Text;

const string LOG4NET_CONFIG = "log4net.config";

var builder = Host.CreateApplicationBuilder();

builder.Services.AddWindowsService();
builder.Logging.AddLog4Net(LOG4NET_CONFIG);
builder.Services.AddHostedService<CustomHostedService>();

var host = builder.Build();

host.Run();


class CustomHostedService : IHostedService
{
private readonly ClientWebSocket _webSocket;
private readonly ILogger<CustomHostedService> _logger;

private bool Connected => _webSocket.State is WebSocketState.Open;

public CustomHostedService(ILogger<CustomHostedService> logger)
{
_logger = logger;
_webSocket = new ClientWebSocket();
}

public async Task StartAsync(CancellationToken cancellationToken)
{
var uri = new Uri("wss://echo.websocket.org");
await _webSocket.ConnectAsync(uri, cancellationToken);
Task.Run(ProcessDataAsync, cancellationToken);
}

private async Task ProcessDataAsync()
{
//...
}

public async Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Shutting down");

if (_webSocket.State is WebSocketState.Open || _webSocket.State is WebSocketState.Connecting)
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken);
}
}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Net.WebSockets;
using System.Text;

const string LOG4NET_CONFIG = "log4net.config";

var builder = Host.CreateApplicationBuilder();

builder.Services.AddWindowsService();
builder.Logging.AddLog4Net(LOG4NET_CONFIG);
builder.Services.AddHostedService<CustomHostedService>();

var host = builder.Build();

host.Run();


class CustomHostedService : IHostedService
{
private readonly ClientWebSocket _webSocket;
private readonly ILogger<CustomHostedService> _logger;

private bool Connected => _webSocket.State is WebSocketState.Open;

public CustomHostedService(ILogger<CustomHostedService> logger)
{
_logger = logger;
_webSocket = new ClientWebSocket();
}

public async Task StartAsync(CancellationToken cancellationToken)
{
var uri = new Uri("wss://echo.websocket.org");
await _webSocket.ConnectAsync(uri, cancellationToken);
Task.Run(ProcessDataAsync, cancellationToken);
}

private async Task ProcessDataAsync()
{
//...
}

public async Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Shutting down");

if (_webSocket.State is WebSocketState.Open || _webSocket.State is WebSocketState.Connecting)
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken);
}
}
During StartAsync I'm executing a network call, but upon reviewing the logs after booting the system, I see this exception: System.Net.Http.HttpRequestException: No such host is known. (Presumably a DNS error due to host not being resolved yet.) Followed by the Application Started event. Additional details:
Category: Microsoft.Extensions.Hosting.Internal.Host
EventId: 11

Hosting failed to start

Exception:
System.Net.WebSockets.WebSocketException (0x80004005): Unable to connect to the remote server
---> System.Net.Http.HttpRequestException: No such host is known. (echo.websocket.org:443)
---> System.Net.Sockets.SocketException (11001): No such host is known.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
Category: Microsoft.Extensions.Hosting.Internal.Host
EventId: 11

Hosting failed to start

Exception:
System.Net.WebSockets.WebSocketException (0x80004005): Unable to connect to the remote server
---> System.Net.Http.HttpRequestException: No such host is known. (echo.websocket.org:443)
---> System.Net.Sockets.SocketException (11001): No such host is known.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
As for StopAsync, I believe the method is not executing as I see no logs at all.
12 Replies
xriba
xribaOP7mo ago
I'm thinking StartAsync is executing too early while the system is booting up. Maybe I could delay it by polling Dns until it starts resolving or registering an application started event where I connect the websocket client, but it doesn't feel right to me. Basically, I want to connect the websocket client while the underlying platform is starting the service. Also, I want to execute some code and dispose the client when the platform restarts or shuts down.
JC
JC7mo ago
echo.websocket.org doesn't look like it's available anymore https://www.lob.com/blog/websocket-org-is-down-here-is-an-alternative so that may explain why you're unable to connect to it in your StartAsync.
echo.websocket.org no longer available
websocket.org shutdown, here are some alternative solutions for developers to echo requests made using websockets
Becquerel
Becquerel7mo ago
when you say you don't see StopAsync executing, is your app hanging when you send SIGTERM with ctrl-c?
xriba
xribaOP7mo ago
It does work, I'm using this endpoint as a sample but even this sample works fine when I start/stop the service manually. https://websocket.org/tools/websocket-echo-server/
WebSocket.org
WebSocket Echo Server
Public free endpoint used for testing Websockets, SSE and HTTP
xriba
xribaOP7mo ago
StopAsync executes when I manually stop the service with ctrl-c. It does not seem to execute when restarting the machine
Becquerel
Becquerel7mo ago
oh I see, apologies for missing that detail. in that case it sounds like an issue with how windows is communicating with the service... just to check, you're using the .UseWindowsService() method when constructing your app host? (or whatever it's called) from Extensions.Hosting.WindowsService
xriba
xribaOP7mo ago
Becquerel
Becquerel7mo ago
yeah, that's what I mean i assumed that on shutdown Windows gracefully killed services via the same mechanism as manually stopping them but maybe it's sending the equivalent of SIGKILL if your service takes too long to die apologies, i won't be of much help beyond this point
xriba
xribaOP7mo ago
I believe it's related to this open issue https://github.com/dotnet/runtime/issues/83093
GitHub
CoreCLR's CNTRL_SHUTDOWN_EVENT handler prevents graceful exit of se...
Description I am not sure if this belongs here, but this is a new weird behaviour I have been fighting with lately. I have a service, where the 'Main_Worker' is a 'IHostedService' a...
Becquerel
Becquerel7mo ago
always great when your problem ends up being an open issue my condolences
xriba
xribaOP7mo ago
Update on StartAsync while not the most elegant, if I delay the execution, the service works which leads me to believe it's related to the DNS not being resolved at first but I'm not sure how to properly ensure it's running Not Working
public async Task StartAsync(CancellationToken cancellationToken)
{
var uri = new Uri("wss://echo.websocket.org");
await _webSocket.ConnectAsync(uri, cancellationToken);
Task.Run(ProcessDataAsync, cancellationToken);
}

Exception:
System.Net.WebSockets.WebSocketException (0x80004005): Unable to connect to the remote server
---> System.Net.Http.HttpRequestException: No such host is known. (echo.websocket.org:443)
---> System.Net.Sockets.SocketException (11001): No such host is known.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
public async Task StartAsync(CancellationToken cancellationToken)
{
var uri = new Uri("wss://echo.websocket.org");
await _webSocket.ConnectAsync(uri, cancellationToken);
Task.Run(ProcessDataAsync, cancellationToken);
}

Exception:
System.Net.WebSockets.WebSocketException (0x80004005): Unable to connect to the remote server
---> System.Net.Http.HttpRequestException: No such host is known. (echo.websocket.org:443)
---> System.Net.Sockets.SocketException (11001): No such host is known.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
Working
public async Task StartAsync(CancellationToken cancellationToken)
{
var uri = new Uri("wss://echo.websocket.org");

await Task.Delay(5000, cancellationToken);
await _webSocket.ConnectAsync(uri, cancellationToken);
Task.Run(ProcessDataAsync, cancellationToken);
}
public async Task StartAsync(CancellationToken cancellationToken)
{
var uri = new Uri("wss://echo.websocket.org");

await Task.Delay(5000, cancellationToken);
await _webSocket.ConnectAsync(uri, cancellationToken);
Task.Run(ProcessDataAsync, cancellationToken);
}
xriba
xribaOP7mo ago
Documenting for further reference: 1. Windows Services do not exit/shutdown gracefully and therefore StopAsync is never called. Here is the open issue: https://github.com/dotnet/runtime/issues/83093 2. The reason why StartAsync runs into a Socket exception during system/platform startup is because the method is executed before the network is available. Here's my workaround:
public async Task StartAsync(CancellationToken cancellationToken)
{
NetworkIsAvailable = NetworkInterface.GetIsNetworkAvailable();
NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged;
var uri = new Uri("wss://echo.websocket.org");

while (!cancellationToken.IsCancellationRequested)
{
if (NetworkIsAvailable)
{
await _webSocket.ConnectAsync(uri, cancellationToken);
Task.Run(ProcessDataAsync, cancellationToken);
break;
}

await Task.Delay(1000);
}
}

private void NetworkChange_NetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs e)
=> NetworkIsAvailable = e.IsAvailable;
public async Task StartAsync(CancellationToken cancellationToken)
{
NetworkIsAvailable = NetworkInterface.GetIsNetworkAvailable();
NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged;
var uri = new Uri("wss://echo.websocket.org");

while (!cancellationToken.IsCancellationRequested)
{
if (NetworkIsAvailable)
{
await _webSocket.ConnectAsync(uri, cancellationToken);
Task.Run(ProcessDataAsync, cancellationToken);
break;
}

await Task.Delay(1000);
}
}

private void NetworkChange_NetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs e)
=> NetworkIsAvailable = e.IsAvailable;
GitHub
CoreCLR's CNTRL_SHUTDOWN_EVENT handler prevents graceful exit of se...
Description I am not sure if this belongs here, but this is a new weird behaviour I have been fighting with lately. I have a service, where the 'Main_Worker' is a 'IHostedService' a...
Want results from more Discord servers?
Add your server