Use systemfd to Boost ASP.NET Core Development

I once tried the development experience on Rust with actix-web. The article Auto-Reloading Development Server amazed me as it provides an uninterrupted reloading experience with systemfd, which continously listens to a specific address and passes the socket to the spawn web server process.

Now I am working on a course system with ASP.NET Core. The interruption of dotnet watch really annoys me as I have to continously try to send HTTP request to test an endpoint, or wait for a small log information of “server ready”. I wonder if I can achieve similiar experience to Rust.

It took me a lot of time to find there is exactly ListenHandle which fufills my requirement. The magic of systemfd is it listens to a specific port and passes the file descriptor to its child, so the web server application is required to listen to a specific file descriptor. The method ListenHandle() of Kestrel makes the server instance to listen to the given file descriptor.

So I tried the following code:

using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace KestrelSample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureKestrel(serverOptions =>
                {
                    var fds = Environment.GetEnvironmentVariable("LISTEN_FDS");
                    var fd = ulong.Parse(fds);

                    serverOptions.ListenHandle(fd);
                })
                .UseStartup<Startup>();
    }
}

But it gave me out this:

crit: Microsoft.AspNetCore.Server.Kestrel[0]
      Unable to start Kestrel.
System.NotImplementedException: This property is not implemented by this class.
   at System.Net.EndPoint.get_AddressFamily()

And I wondered if this is a bug or document issue in ASP.NET Core, so I opened aspnet/AspNetCore.Docs#13744, as the code above is exactly part of ASP.NET Core samples. After a discussion in aspnet/AspNetCore#13020, I realized that the NotImplementedException is due to the lack of libuv, so I reminded ASP.NET Core members to fix their samples. After the pull request get merged, the exception will not look so confusing.

So a single line of using libuv would fix the code:

using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace KestrelSample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureKestrel(serverOptions =>
                {
                    var fds = Environment.GetEnvironmentVariable("LISTEN_FDS");
                    var fd = ulong.Parse(fds);

                    serverOptions.ListenHandle(fd);
                })
                .UseStartup<Startup>();
    }
}

But the code still does not work, prompting invalid file descriptor. So I looked into listenfd and found that the file descriptor begins at 3, and LISTEN_FDS is just indicating how many file descriptors are managed by systemfd. As I am only using a single listen address, the file descriptor I should listen to is exactly 3.

After this fix and I enhanced the code by compile gates, the final code looks like this:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace Matrix.Generic.Se
{
    public class Program
    {
        public static Task Main(string[] args) => CreateHostBuilder(args).Build().RunAsync();

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host
                .CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
#if DEBUG
                        // systemfd debug support, only on Unix system
                        // run the following command to get continous
                        // socket listening
                        //   systemfd --no-pid -s http::5000 -- dotnet watch run
                    #region systemfd
                        // enable libuv to support ListenHandle
                        .UseLibuv()
                        .ConfigureKestrel(serverOptions =>
                        {
                            // detect systemfd environment
                            var fds = Environment.GetEnvironmentVariable("LISTEN_FDS");
                            if (fds != null)
                                // listen to given file handle
                                // file handle begins at 3
                                serverOptions.ListenHandle(3);
                        })
                    #endregion
#endif
                        .UseStartup<Startup>();
                });
    }
}

You may start the watch process by systemfd --no-pid -s http::5000 -- dotnet watch run, and there won’t be any breaks between server restarting. Besides, only Debug configuration will have this feature, so you do not need to worry the performance drop of libuv in production.