Asp.Net Core 3 graceful shutdown throws OperationCanceledException - asp.net-core

I'm trying to gracefully terminate a ASP.Net Core 3.1 service (which will run in Kubernetes). When Kubernetes stops a service, it will send a SIGTERM event to the application, at which point I want in-flight requests to complete (which may take several seconds) before terminating... I think I can catch this in a hostedservice, as below, and hence not stop immediately.
The following works, but with a timeout of 5 seconds or longer, I receive an OperationCanceledException. Could anyone shed any light on why I get an OperationCanceledException or how shed any light on an alternative way to delay a SIGTERM event, to allow a graceful shutdown?
public static int Main(string[] args)
{
var logger = NLogBuilder
.ConfigureNLog("nlog.config")
.GetCurrentClassLogger();
try
{
CreateHostBuilder(args)
.ConfigureServices((hostBuilderContext, services) => { services.AddHostedService<LifetimeEventsHostedService>(); })
.Build()
.Run();
return 0;
}
catch (Exception e)
{
logger.Fatal(e, "Stopping due to exception");
return -1;
}
finally
{
LogManager.Shutdown();
}
}
This is the hosted service...
internal class LifetimeEventsHostedService : IHostedService
{
private readonly Microsoft.Extensions.Logging.ILogger _logger;
private readonly IHostApplicationLifetime _appLifetime;
public LifetimeEventsHostedService(
ILogger<LifetimeEventsHostedService> logger,
IHostApplicationLifetime appLifetime)
{
_logger = logger;
_appLifetime = appLifetime;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_appLifetime.ApplicationStarted.Register(OnStarted);
_appLifetime.ApplicationStopping.Register(OnStopping);
_appLifetime.ApplicationStopped.Register(OnStopped);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private void OnStarted()
{
_logger.LogInformation("OnStarted has been called.");
// Perform post-startup activities here
}
private void OnStopping()
{
_logger.LogInformation("OnStopping has been called.");
// Perform on-stopping activities here
// This works, but a timeout of 5 seconds or more subsequently causes an OperationCanceledException
Thread.Sleep(5000);
}
private void OnStopped()
{
_logger.LogInformation("OnStopped has been called.");
// Perform post-stopped activities here
}
}

I'm open to alternative approaches to graceful shutdown with ASP.Net Core 3.1, as it stands, I'm using a hosted service.
Within the .Net Core app, I was setting ShutdownTimeout on the webhost, however, setting the ShutdownTimeout on the generic host, does allow me to gracefully wait a number of seconds (more than the default sigterm, which is 5 seconds) prior to shutdown. The hint from #PmanAce helped me work that out.
As such, the following codes allow me to gracefully terminate. One caveat, the Thread.Sleep in LifetimeEventsHostedService must be less than option.ShutdownTimeout.
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostBuilderContext, services) =>
{
services.AddHostedService<LifetimeEventsHostedService>();
services.Configure<HostOptions>(option =>
{
option.ShutdownTimeout = TimeSpan.FromSeconds(30);
});
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseKestrel();
webBuilder.UseStartup<Startup>();
});
}
The following LifetimeEventsHostedService
public class LifetimeEventsHostedService : IHostedService
{
private readonly IHostApplicationLifetime _hostApplicationLifetime;
public LifetimeEventsHostedService(IHostApplicationLifetime hostApplicationLifetime)
{
_hostApplicationLifetime = hostApplicationLifetime;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_hostApplicationLifetime.ApplicationStarted.Register(OnStarted);
_hostApplicationLifetime.ApplicationStopping.Register(OnStopping);
_hostApplicationLifetime.ApplicationStopped.Register(OnStopped);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private void OnStopped()
{
Console.WriteLine("OnStopped");
}
private void OnStopping()
{
Console.WriteLine("OnStopping");
Console.WriteLine(DateTime.Now.ToLongTimeString());
Thread.Sleep(15000);
Console.WriteLine("Sleep finished");
Console.WriteLine(DateTime.Now.ToLongTimeString());
}
private void OnStarted()
{
Console.WriteLine("OnStarted");
}
}

Related

IHostedService - background task locks app

I want to run task in different thread which will synchornize data, but it locks whole server (asp net core). i have no idea where i did something wrong.
I tried with tasks, and then with thread, but it always lock my app.
public interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
public class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ISynchronizationBackgroundService _syncService;
public ScopedProcessingService(ISynchronizationBackgroundService syncService)
{
_syncService= syncService;
}
public Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
_syncService.Synchronize();
}
catch (Exception ex)
{
Log.Error($"SYNCHRONIZATION ERROR {ex.Message} {ex.InnerException?.Message}");
}
Thread.Sleep(5000);
}
return Task.CompletedTask;
}
}
and the consumer
public class ConsumeScopedServiceHostedService : BackgroundService
{
public IServiceProvider Services { get; }
public ConsumeScopedServiceHostedService(IServiceProvider services)
{
Services = services;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
Thread t = new Thread(() =>
{
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
scopedProcessingService.DoWork(stoppingToken);
}
});
t.Start();
return Task.CompletedTask;
}
}
and in the end registration:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
services.AddScoped<ISynchronizationBackgroundService, SynchronizationBackgroundService>();
Would be grateful for any advice.

Host a SOAP service within a BackgroundService using .Net Core 5

I'm new to .NET Core. I currently have a WCF host service that hosts another service that I'm trying to convert to .NET core. Using .NET 5, I created a worker service that handles the host background tasks and setup another service w/ an endpoint to handle incoming responses from another client. I'm having trouble using the EndpointAddress and ChannelFactory approach to create the endpoint and channel so the endpoint can be accessible via the outside world for response messages, but in doing so, I get the following error:
"No connection could be made because the target machine actively refused it. (localhost:8000)"
Maybe I'm going about this in the wrong way to host the service, not sure. Does anyone know?
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
return;
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<JLinkHostService>();
});
}
}
[ServiceContractAttribute]
public interface IResponseService
{
[OperationContractAttribute]
bool ResponseMessage(string sTermID, string sRespMsg);
}
public class ResponseService : IResponseService
{
public bool ResponseMessage(string sTermID, string sRespMsg)
{
string filePath = $"{c:\test"}\\{DateTime.Now.ToString("yyyy -MM-dd_HHmmssfff")}.txt";
System.IO.File.WriteAllText(filePath, $"{sTermID}\n\n{sRespMsg}");
return true;
}
}
public class HostService : BackgroundService
{
public override Task StartAsync(CancellationToken cancellationToken)
{
return base.StartAsync(cancellationToken);
}
public override Task StopAsync(CancellationToken cancellationToken)
{
return base.StopAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
ChannelFactory<IResponseService> factory = null;
try
{
Binding binding = new BasicHttpBinding();
EndpointAddress respAddress = new EndpointAddress("http://localhost:8000/response.svc");
factory = new ChannelFactory<IResponseService>(binding, respAddress);
IResponseService channel = factory.CreateChannel();
// Test service proxy
channel.ResponseMessage("test", "test");
while (!stoppingToken.IsCancellationRequested)
{
// Host background tasks happen here
await Task.Delay(Int32.Parse(GetCfgValue("AppSettings:pollingIntervalMilli")), stoppingToken);
}
}
catch (Exception ex)
{
Log.Fatal(ex.ToString());
}
finally
{
if(factory != null)
factory.Close();
}
}
}
}

Unable to resolve service for type 'AService' while attempting to activate 'BService'

I am attemping to use an IHostedService from another IHostedService but am getting an error when I start my application. I would like to use AService from BService. I see there are other solutions but I am looking for a specific solution for .net-core 3.0.
System.InvalidOperationException: 'Unable to resolve service for type
'TwoHostedServices.AService' while attempting to activate
'TwoHostedServices.BService'.'
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
Host.CreateDefaultBuilder(args)
.ConfigureServices(ConfigureServicesDelegate)
.Build()
.Run();
}
private static void ConfigureServicesDelegate(HostBuilderContext hostBuilderContext, IServiceCollection services)
{
services.AddHostedService<AService>()
.AddHostedService<BService>();
}
}
public class AService : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
public class BService : IHostedService
{
private readonly AService _aService;
public BService(AService aService)
{
_aService = aService;
}
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}

Exceptions handled in UseExceptionHandler are logged by Application Insights, but they shouldnt

I initialize logging in program.cs:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseApplicationInsights()
.UseStartup<Startup>();
And later I have some global exception handling in Startup.cs:
public static void ConfigureExceptionHandler(this IApplicationBuilder app)
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
context.Response.StatusCode = 200;
});
});
}
What I've noticed, code from my ITelemetryProcessor is executed before the one from app.UseExceptionHandler.
As the result, handled exceptions are being logged to Application Insights. How can I prevent it?
Ok so I found a decent workaround. I used custom middleware for changing response codes, so the exception doesn't pop up to insights, as long as it's handled in the middleware. Example:
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
public ExceptionHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception ex)
{
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
}
}
}

.NET core web api with queue processing

How to setup a .NET core web api that
accepts a string value,
puts into a queue
and return flag that message is accepted (regardless it is processed).
Also, a routine which keeps checking the queue, and process the messages one by one.
As per the requirement, the api is going to act as the receiver of messages which may get hits as much as hundreds of times in a minute, while the messages it receives should be processed one by one.
I am bit new to web apis, so wonder if such setup is good to have and if yes how to put together different components.
Thanks in advance..
Honestly, I don't think that it makes sense to receive and process messages in one process, so I would recommend to use external messaging system like RabbitMQ or Kafka or any other existing system of your preference, where you can put your messages and another process would consume it. It's quite big topic, you can start from this tutorial
If you still want to have it in one process it's also possible, you can create a background task queue, put there your messages and create background task which will consume them from that queue.
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(
Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
public async Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
}
Background task:
public class QueuedHostedService : BackgroundService
{
private readonly ILogger _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILoggerFactory loggerFactory)
{
TaskQueue = taskQueue;
_logger = loggerFactory.CreateLogger<QueuedHostedService>();
}
public IBackgroundTaskQueue TaskQueue { get; }
protected async override Task ExecuteAsync(
CancellationToken cancellationToken)
{
_logger.LogInformation("Queued Hosted Service is starting.");
while (!cancellationToken.IsCancellationRequested)
{
var workItem = await TaskQueue.DequeueAsync(cancellationToken);
try
{
await workItem(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
$"Error occurred executing {nameof(workItem)}.");
}
}
_logger.LogInformation("Queued Hosted Service is stopping.");
}
}
Registration:
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
}
Inject to controller:
public class ApiController
{
private IBackgroundTaskQueue queue;
public ApiController(IBackgroundTaskQueue queue)
{
this.queue = queue;
}
public IActionResult StartProcessing()
{
queue.QueueBackgroundWorkItem(async token =>
{
// put processing code here
}
return Ok();
}
}
You can modify BackgroundTaskQueue to fit your requirements, but I hope you understand the idea behind this.
Update for latecomers....
I used in Asp.net core 6, you can download sample here: https://learn.microsoft.com/en-us/dotnet/core/extensions/queue-service
Config Program
// and more...
#region Worker Services
builder.Host.ConfigureServices((context, services) =>
{
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(_ =>
{
if (!int.TryParse(context.Configuration["QueueCapacity"], out var queueCapacity))
{
queueCapacity = 100;
}
return new BackgroundTaskQueue(queueCapacity);
});
});
#endregion
#region App
// App config
var app = builder.Build();
// Monitor worker config
var monitorLoop = app.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
else
{
app.UseHttpsRedirection();
}
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
await app.RunAsync();
#endregion
In Controller
// and more..
private readonly IMailService _mailService;
private readonly IBackgroundTaskQueue _queue;
// and more..
public AuthenticateController(
IMailService mailService,
IBackgroundTaskQueue queue)
{
_mailService = mailService;
_queue = queue;
}
[HttpPost]
[Route("forgot-password")]
public async Task<IActionResult> ForgotPassword([FromBody] ForgotPasswordModel model)
{
// and more...
// Queue processing
await _queue.QueueBackgroundWorkItemAsync(async (token) =>
{
await _mailService.SendAsync(mailData, token);
});
return Ok();
}
Hope this help !