Configure Windows Service to restart on both graceful AND ungraceful shutdown - .net-core

I am aware of the Recovery section of the Service control, and how we can set an app to restart after failure.
I have created a .NET 6 worker service to run as a windows service. The problem is that whenever there is an exception in the code, the app logs the error but then shuts down gracefully. This does not signal to windows that the service should be restarted since it returns an exit code of 0.
I've tried returning an exit code of -1 (by setting Environment.ExitCode and returning -1 from Main()) but it's ignored.
I've also tried setting the exit code of the underlying WindowsServiceLifetime and that also does not work.
Are there any ways to have the SCM restart the service no matter how it shut down?

Exceptions should not bring down the host. Exceptions do not bring down IIS and they should not bring down a Windows Service.
You should put try/catch where work begins – every endpoint and background service. In the catch you should log the error.
Here is an endpoint example:
[Route("Get")]
[HttpGet]
public async Task<IActionResult> GetAsync()
{
try
{
return Ok(await BusinessRules.GetSomethingAsync());
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
throw;
}
}
Here is a background service example:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
//Need a try/catch round Task.Delay because exception will the thrown
//if stoppingToken is activated and we don't care about logging this exception.
try
{
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
catch { }
await BusinessRules.DoSomethingAsync(stoppingToken);
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
//In a loop, log file can fill up quickly unless we slow it down after error.
try
{
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
}
catch { }
}
}
}

Related

Zxing cannot be started while StartForeground is running

If you are running Start Foreground from an Android.App service
I get an error when I start Zxing (barcode reading).
Error details: Compatible code is not running.
Selective debug execution may or may not be the executable code running in the current thread.
Unhandled Exception:
Java.Lang.NullPointerException: <Timeout exceeded getting exception details>
Is there anyone who can solve this problem?
-- 2020/12/22 add --
Thank you for your reply.
I thought I'd ask a simple question,
It seems that it was not concise but vague Excuse me.
Execute background service to acquire location information continuously (every 15 minutes)
I want to read barcodes as another function,
An error will occur if the barcode is read while the background service is running.
It would be ideal if there was a way to avoid an error in this situation, but I don't know how to deal with it.
Exit background service, read barcode, then
I was wondering if I could handle it by restarting the background service.
I can't solve the problem.
1. Refer to the url below to get the location information in the background.
https://teratail.com/questions/167858
Package installation
ZXing.Net.Mobile (2.4.1)
ZXing.Net.Mobile.Forms (2.4.1)
Run the Clicked event on the Xamarin.Forms Button
The code below calls the Zxing form.
await Navigation.PushAsync (new QRScanPage ()); ← * An error occurs here
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
public partial class QRScanPage: ContentPage
{
public Page00_QRScanPage ()
{
InitializeComponent ();
}
void Handle_OnScanResult (ZXing.Result result)
{
Device.BeginInvokeOnMainThread (async () =>
{
zxing.IsAnalyzing = false;
await Navigation.PopAsync ();
await DisplayAlert ("notification", "Read the following value:" + result.Text, "OK");
zxing.IsAnalyzing = true;
});
}
protected override void OnAppearing ()
{
try
{
base.OnAppearing ();
zxing.IsScanning = true;
}
catch (Exception ex)
{
DisplayAlert ("ExErr",
$ "{ex.Message}", Msg.Button.Ok);
}
}
protected override void OnDisappearing ()
{
zxing.IsScanning = false;
base.OnDisappearing ();
}
}
Service outage
Intent serviceIntent = new Intent (this, typeof (BackgroundService)); * "BackgroundService" is the running Service class
Base.StopService (serviceIntent);
Stop with StopSelf () method
As a result, nothing could be solved.

Hosted service not terminating after Environment.Exit

I've got a .NET core 3.1 app with a hosted service that runs as a console application on Windows.
In case of an error I'm trying to terminate the worker with Environment.Exit(1).
Now the problem is that, if Enviroment.Exit() is called before any await in ExecuteAsync, the application does not terminate. It logs Waiting for the host to be disposed. Ensure all 'IHost' instances are wrapped in 'using' blocks. and then hangs indefinitely.
When I await anything before the call to Enviroment.Exit() it also logs that, but it terminates as expected.
Here is the simplest code that I could come up with to reproduce the problem.
The NotTerminatingWorker hangs forever, the TerminatingWorker terminates. The only difference is a tiny Task.Delay:
public class Program {
public static async Task Main(string[] args) {
using var host = CreateHostBuilder(args).Build();
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) {
return Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) => { services.AddHostedService<NotTerminatingWorker>(); });
}
}
public class NotTerminatingWorker : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
Environment.Exit(1);
}
}
public class TerminatingWorker : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
await Task.Delay(1);
Environment.Exit(1);
}
}
I would expect that both behave the same way, but that's obviously not the case.
Any explanation for this would be greatly appreciated!
UPDATE: The application should be able to run both as a console application and as a Windows service. The non-zero return code is required to get it restarted if it crashes.
And apparently Windows does not restart services that exited with code 0.
I believe the behavior you're seeing is a side-effect of how the .NET Core runtime does its startup: it calls ExecuteAsync for each background worker and then waits for it to complete. So a synchronous ExecuteAsync can cause problems. I've used Task.Run to work around this.
In case of an error I'm trying to terminate the worker with Environment.Exit(1).
I recommend not using Environment.Exit at all. Instead, do a controlled shutdown by injecting IHostApplicationLifetime and calling StopApplication. This will trigger the stoppingToken for each of your background services, and if they ignore it, they will be forcibly terminated after a timeout.
Handling the hostLifetime events in the Main method did for me the job. This is working for me on .NET6
public static int Main(string[] args)
{
ExitCode = 0;
ILogger? logger = null;
try
{
var builder = CreateHostBuilder(args)
.Build();
var hostLifetime = builder.Services.GetRequiredService<IHostApplicationLifetime>();
logger = builder.Services.GetService<ILogger<Program>>();
// register on hostLifetime events for handling stopping and finalize
using var hostLtAppStopping = hostLifetime.ApplicationStopping.Register(() =>
{
// service is about to stop... do some cleanup stuff here
});
using var hostLtAppStopped = hostLifetime.ApplicationStopped.Register(() =>
{
logger?.LogDebug("Service graceful shout down, exit with code {exitCode}!", ExitCode);
Environment.Exit(ExitCode); // ExitCode is set by the caller of hostApplicationLifetime.StopApplication
});
// start the service
logger?.LogDebug("builder.Run()");
builder.Run();
}
catch (Exception e)
{
logger?.LogError(e, "Unhandled Exception occurred => exit with exit code 1!");
ExitCode = 1;
return ExitCode;
}
return ExitCode;
}

Rebus - Exit application from Handle method

I'm implementing an enricher pattern (https://www.enterpriseintegrationpatterns.com/patterns/messaging/DataEnricher.html) using a command/consumer queue where the consumer is the enricher and publishes the enriched message to a separate endpoint (SQL database in this case). The consumer is running as a HostedService which implements cancellation token.
Because I'm consuming commands from one transport and publishing events to another there is a possibility that the transport I'm publishing to is down while the one I'm consuming from is up. In that case I'd like to log an error and stop my Hosted service. However, I cannot see how that would work since whatever calls the Handle method already handles exceptions, and I cannot access my cancellation token. Does anyone have any ideas?
This is a draft of what I want to do.
public async Task Handle(EditedEventData message)
{
var enricher = _enricherFactory.GetEnricher(message);
object #event = await enricher.EnrichAsync(message);
var transformers = _transformerFactory.GetTransformers(message);
var messages = new List<object>();
foreach (var transformer in transformers)
{
messages.AddRange(transformer.Transform(#event, message));
}
foreach (var item in messages)
{
try
{
await _bus.Publish(item);
}
catch (Exception ex)
{
_logger.LogCritical("Publishing event message {#item} failed with error {ex}", item, ex);
//how do I exit from here?
}
}
}
If I were you, I would come up with some kind of application service, e.g. IApplicationControlService, which you can configure to be injected into your handlers using whichever IoC container you're using.
It could look somewhat like this:
public interface IApplicationControlService
{
void RequestApplicationShutdown();
}
and then your code could simply
public class YourHandler : IHandleMessages<EditedEventData>
{
readonly IApplicationControlService applicationControlService;
public YourHandler(IApplicationControlService applicationControlService)
{
this.applicationControlService = applicationControlService;
}
public async Task Handle(EditedEventData message)
{
// (...)
foreach (var item in messages)
{
try
{
await _bus.Publish(item);
}
catch (Exception ex)
{
_logger.LogCritical("Publishing event message {#item} failed with error {ex}", item, ex);
applicationControlService.RequestApplicationShutdown();
}
}
}
}
to request the application be stopped, when an error occurs.
An implementation of IApplicationControlService could then be something like
public class BruteForceApplicationControlService : IApplicationControlService
{
public void RequestApplicationShutdown()
{
Environment.FailFast("you should probably not do THIS πŸ˜‰");
}
}
or something more gentle 😁 – the point is, that you will be able to provide a way to request your application to shut down "from the outside", most likely from the place where your application is assembled (i.e. the "composition root")

Xamarin forms local store exception when insert item

Almost everything works great except one form that doesn't save some data to Azure database and I can't catch any info about exception i.e exception instance is null when break point stop in exception of type MobileServiceLocalStoreException. I checked the initialization of the local store and there is no problem with it, so what could be the reason behind this exception.
public async Task<T> CreateItemAsync(T item)
{
try
{
await table.InsertAsync(item);
}
catch(MobileServiceLocalStoreException ex)
{ // here exception occurd
Debug.WriteLine(ex.Message);
}
catch(Exception ex)
{
}
return item;
}

SignalR disconnects and does not reconnect

Whenever my application gets reset, signalR disconnects but does not reconnect.
I have a long running server task which sends updates to clients when each task is completed.
// inside action executed on every completion of a task
var h = new ForceHub();
h.MessageSent(email);
above code stops sending updates when application gets reset (i can emulate this problem by touching web.config).
I'd like a way to reconnect to a client. Currently the user has to reload the page for it to get updates again.
Here is my hub definition
public class ForceHub : Hub
{
public void MessageSent(string text)
{
GetContext().Clients.All.sent(text);
}
public void UpdateStatus(string msg)
{
GetContext().Clients.All.status(msg);
}
IHubContext GetContext()
{
return GlobalHost.ConnectionManager.GetHubContext<ForceHub>();
}
public override Task OnConnected()
{
try {
IoC.Resolve<ILogger>().Info("SignalR Connected -----------");
}catch (Exception){}
return base.OnConnected();
}
public override Task OnDisconnected()
{
try {
IoC.Resolve<ILogger>().Info("SignalR Disconnected -----------");
}
catch (Exception) { }
return base.OnDisconnected();
}
public override Task OnReconnected()
{
try {
IoC.Resolve<ILogger>().Info("SignalR Re-Connected -----------");
}
catch (Exception) { }
return base.OnReconnected();
}
}
I can see Connected and Re-Connected events triggered after startup, however after touching web.config, I don't see any of these events triggered.
i tried catching this on the client, but this event is not tirggered:
$.connection.hub.disconnected(function () {
console.error('signalR disconnected, retrying connection');
logError('Signal lost.');
setTimeout(function () { connection.start(); }, 1000);
});
update
I also hooked into State Changed event, which does get triggered, but the re-connection attempt below does not work.
$.connection.hub.stateChanged(function (state) {
console.debug('signalR state changed', state);
if (state.newState == 1) {
console.debug('restarting');
setTimeout(function () { $.connection.hub.start(); }, 1000);
}
});
this event gets triggered twice: newState is 2 ,and then 1.
I might have a clue... Touching the Web.config produces an appPool Recycle, meaning that a new worker process will be created for new requests while the existing process will continue for a while until the remaining requests end or the timeout is reached. Request that do not end in the timeout period are terminated.
Signalr client reconnects to the new process while the long running task is running in the old process, so when on the long running task you do
GlobalHost.ConnectionManager.GetHubContext<ForceHub>();
you actually get a reference for "old" hub while the client is connected to the "new" hub.
That's why the test preformed by Wasp worked: he was making a new request to publish on the signalr hub that was processed in the newly created worker process.
You could try to configure a singalr backplane (https://www.asp.net/signalr/overview/performance/scaleout-in-signalr), it’s really easy to configure it using Sql Server (https://www.asp.net/signalr/overview/performance/scaleout-with-sql-server). The backplane should be capable of connect the two worker processes and hopefully you will get the notification on the client.
If this is the problem, notifications generated by new requests will work even without the backplane. Notice that the real purpose of the backplane is to scale out signalr, this is, to connect a farm of WebServers between them.
Also keep in mind that running long-running task inside IIS is as task hard to achieve as, among other things, IIS does regular appPool recycles and has timeout limits for the requests to execute. I recommend that you read the following post: http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx
β€œIf you think you can just write a background task yourself, it's likely you'll get it wrong. I'm not impugning your skills, I'm just saying it's subtle. Plus, why should you have to?”
Hope this helps

Resources