How to Configure ASP.NET Kestrel for Low Latency

How do you configure Kestrel to use a random dynamic port and determine the port at run-time with ASP.NET Core 3.1?

The reason the IHostedService method does not work is because .Net Core 3 changed when IHostedServices are executed. In .Net Core 2, IHostedService was executed after the host starts so the server address information was readily available. In .Net Core 3, IHostedService runs after the host is built, but before the host starts up and the address is not yet available. This blog has a nice explanation of what changed.

The following method of getting the bound address, copied from here, works in both .Net Core 2 & 3.

You can call IWebHost.Start() instead of IWebHost.Run() as suggested here. This will allow execution of your Main method to continue so you can get the desired information from IWebHost.ServerFeatures. Just remember, your application will shutdown immediately unless you explicitly tell it not to using IWebHost.WaitForShutdown().

 public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseStartup<Startup>()
.UseUrls("http://*:0") // This enables binding to random port
.Build();

host.Start();

foreach(var address in host.ServerFeatures.Get<IServerAddressesFeature>().Addresses)
{
var uri = new Uri(address);
var port = uri.Port;

Console.WriteLine($"Bound to port: {port}");
}

//Tell the host to block the thread just as host.Run() would have.
host.WaitForShutdown();
}

How to run an async task daily in a Kestrel process?

I am going to add an answer to this, because this is the only logical way to accomplish such a thing in ASP.NET Core: an IHostedService implementation.

This is a non-reentrant timer background service that implements IHostedService.

public sealed class MyTimedBackgroundService : IHostedService
{
private const int TimerInterval = 5000; // change this to 24*60*60 to fire off every 24 hours
private Timer _t;

public async Task StartAsync(CancellationToken cancellationToken)
{
// Requirement: "fire" timer method immediatly.
await OnTimerFiredAsync();

// set up a timer to be non-reentrant, fire in 5 seconds
_t = new Timer(async _ => await OnTimerFiredAsync(),
null, TimerInterval, Timeout.Infinite);
}

public Task StopAsync(CancellationToken cancellationToken)
{
_t?.Dispose();
return Task.CompletedTask;
}

private async Task OnTimerFiredAsync()
{
try
{
// do your work here
Debug.WriteLine($"{TimerInterval / 1000} second tick. Simulating heavy I/O bound work");
await Task.Delay(2000);
}
finally
{
// set timer to fire off again
_t?.Change(TimerInterval, Timeout.Infinite);
}
}
}

So, I know we discussed this in comments, but System.Threading.Timer callback method is considered a Event Handler. It is perfectly acceptable to use async void in this case since an exception escaping the method will be raised on a thread pool thread, just the same as if the method was synchronous. You probably should throw a catch in there anyway to log any exceptions.

You brought up timers not being safe at some interval boundary. I looked high and low for that information and could not find it. I have used timers on 24 hour intervals, 2 day intervals, 2 week intervals... I have never had them fail. I have a lot of them running in ASP.NET Core in production servers for years, too. We would have seen it happen by now.

OK, so you still don't trust System.Threading.Timer...

Let's say that, no... There is just no fricken way you are going to use a timer. OK, that's fine... Let's go another route. Let's move from IHostedService to BackgroundService (which is an implementation of IHostedService) and simply count down.

This will alleviate any fears of the timer boundary, and you don't have to worry about async void event handlers. This is also a non-reentrant for free.

public sealed class MyTimedBackgroundService : BackgroundService
{
private const long TimerIntervalSeconds = 5; // change this to 24*60 to fire off every 24 hours

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Requirement: "fire" timer method immediatly.
await OnTimerFiredAsync(stoppingToken);

var countdown = TimerIntervalSeconds;

while (!stoppingToken.IsCancellationRequested)
{
if (countdown-- <= 0)
{
try
{
await OnTimerFiredAsync(stoppingToken);
}
catch(Exception ex)
{
// TODO: log exception
}
finally
{
countdown = TimerIntervalSeconds;
}
}
await Task.Delay(1000, stoppingToken);
}
}

private async Task OnTimerFiredAsync(CancellationToken stoppingToken)
{
// do your work here
Debug.WriteLine($"{TimerIntervalSeconds} second tick. Simulating heavy I/O bound work");
await Task.Delay(2000);
}
}

A bonus side-effect is you can use long as your interval, allowing you more than 25 days for the event to fire as opposed to Timer which is capped at 25 days.

You would inject either of these as so:

services.AddHostedService<MyTimedBackgroundService>();

How to implement a pure ASP.NET Core Web API by using AddMvcCore()

What is the difference between AddMvc() and AddMvcCore()?

The first thing key thing to understand is that AddMvc() is just a pre-loaded version of AddMvcCore(). You can see the exact implementation of the AddMvc() extension at the GitHub repository.

I like using default VS templates as much as the next guy, but sometimes you need to know when it's the wrong choice. I have seen several guides online that lean more towards an attempt to "undo" these default services rather than just going with a solution that just does not implement them in the first place.

With the advent of ASP.NET Core being open source, there really isn't a good reason why we can't peel back a layer and work at a lower level without fear of losing "magic".


Definition of "minimal" and "pure"

Note: The definitions are intended for the context of this answer only. Mostly for the sake of clarity and assisting in further understanding.

This answer leans more towards "pure" and not "minimal". I'd like to describe why, so it's clearer what I'm talking about.

Minimal. A "minimal" solution would be an implementation that does not even call upon the AddMvcCore() method at all. The reason for this, is that MVC is not really a "required" component to assembling you own Web API, and it certainly adds some weight to your code with the additional dependencies. In this scenario, since you're not using the AddMvcCore() method, you also would not inject it into your application, here

public void Configure(IApplicationBuilder app)
{
app.UseMvc(); // you don't need this
}

This would mean mapping your own routes and responding to the context in your own way. This really isn't challenging at all, but I don't want to dive into it, because it's quite off-topic, but here is a tiny taste of a minimal implementation:

public void Configure(IApplicationBuilder app)
{
app.Map("/api", HandleMapApi);
// notice how we don't have app.UseMvc()?
}

private static void HandleMapApi(IApplicationBuilder app)
{
app.Run(async context =>
{
// implement your own response
await context.Response.WriteAsync("Hello WebAPI!");
});
}

For many projects, a "minimal" approach means we are giving up some of the features found in MVC. You would really have to weigh your options and see if you this design path is the right choice, as there is a balance between design pattern, convenience, maintainability, code footprint, and most importantly performance and latency. Simply put: a "minimal" solution would mean minimizing the services and middleware between your code and the request.

Pure. A "pure" solution (as far as the context of this answer) is to avoid all the default services and middleware that come "pre-bundled" with AddMvc() by not implementing it in the first place. Instead, we use AddMvcCore(), which is explained further in the next section:


Implementing our own services / middleware with AddMvcCore()

The first thing to get started is to setup ConfigureServices to using AddMvcCore(). If you look at the GitHub repository, you can see that AddMvc() calls AddMvcCore() with a standard set of services / middleware:

Here are some of the services / middleware that stands out as "unneeded":

var builder = services.AddMvcCore();

builder.AddViews();
builder.AddRazorViewEngine();
builder.AddRazorPages();

Many of these default services are great for a general web project, but are usually undesirable for a "pure" Web API.

Here is a sample implementation of ConfigureServices using AddMvcCore() for a Web API:

public void ConfigureServices(IServiceCollection services)
{
// Build a customized MVC implementation, without using the default AddMvc(),
// instead use AddMvcCore(). The repository link is below:
// https://github.com/aspnet/Mvc/blob/release/2.2/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs

services
.AddMvcCore(options =>
{
options.RequireHttpsPermanent = true; // this does not affect api requests
options.RespectBrowserAcceptHeader = true; // false by default
//options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();

// these two are here to show you where to include custom formatters
options.OutputFormatters.Add(new CustomOutputFormatter());
options.InputFormatters.Add(new CustomInputFormatter());
})
//.AddApiExplorer()
//.AddAuthorization()
.AddFormatterMappings()
//.AddCacheTagHelper()
//.AddDataAnnotations()
//.AddCors()
.AddJsonFormatters();
}

The implementation above is mostly a duplicate of the AddMvc() extension method, however I have added a few new areas so that others can see the added benefits of doing this.

  • Custom Input/Output Formatters. This is where you can do your own highly optimized serializers (such as Protobuf, Thrift, Avro, Etc) instead of using JSON (or worse XML) serialization.
  • Request Header Handling. You can make sure that the Accept header is recognized, or not.
  • Authorization Handling. You can implement your own custom authorization or can take advantage of the built-in features.
  • ApiExplorer. For some projects, you may likely include it, otherwise some WebAPI's may not want to this feature.
  • Cross-Origin Requests (CORS). If you need a more relaxed security on your WebAPI, you could enable it.

Hopefully with this example of a "pure" solution, you can see the benefits of using AddMvcCore() and be comfortable with using it.

If you're serious about control over performance and latency while working on top of ASP.NET Core's web host maybe a deep dive into a "minimal" solution is where you're dealing right at the edge of the request pipeline, rather than letting it get bogged down by the MVC middleware.


Additional Reading

A visual look at how the middleware pipeline looks like... As per my definitions, less layers means "minimal", whereas "pure" is just a clean version of MVC.

Sample Image

You can read more about it on the Microsoft Documents: ASP.NET Core Middleware Fundamentals

Increased latency by time - C# ASP.NET Core Web API

From Yarp docs:

Middleware should avoid interacting with the request or response bodies. Bodies are not buffered by default, so interacting with them can prevent them from reaching their destinations. While enabling buffering is possible, it's discouraged as it can add significant memory and latency overhead. Using a wrapped, streaming approach is recommended if the body must be examined or modified. See the ResponseCompression middleware for an example.

I'd recommend to use this library instead of a custom solution.
https://microsoft.github.io/reverse-proxy/articles/getting-started.html

Async processing in asp.net core and kestrel thread pool

By default when there is not SynchronizationContext or when ConfigureAwait(false) is called on awaited task, its continuation will run on CLR maintained thread-pool that can be accessed using static methods from ThreadPool class

in .net core methods from ThreadPool to control its size (setMaxThreads and setMinThreads) are not implemented yet:

https://github.com/dotnet/cli/issues/889

https://github.com/dotnet/corefx/issues/5920

it is possible however to set its size statically in project.json file as explained here:

https://github.com/dotnet/cli/blob/rel/1.0.0/Documentation/specs/runtime-configuration-file.md#runtimeoptions-section-runtimeconfigjson

kestrel has its own pool for async processing of requests with libuv: it's controlled by static ThreadCount property of KestrelServerOptions class.

related articles:

http://blog.stephencleary.com/2012/02/async-and-await.html

http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/

https://msdn.microsoft.com/en-us/magazine/gg598924.aspx

thx to @Servy and @Damien_The_Unbeliever for the hints that allowed me to find it



Related Topics



Leave a reply



Submit