Skip to content

Commit

Permalink
Make .NET server stop if Tauri app exits ungracefully
Browse files Browse the repository at this point in the history
  • Loading branch information
tareqimbasher committed Oct 1, 2024
1 parent 0c49f21 commit 677061e
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 66 deletions.
69 changes: 69 additions & 0 deletions src/Apps/NetPad.Apps.App/ParentProcessTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Diagnostics;
using Microsoft.Extensions.Hosting;

namespace NetPad;

/// <summary>
/// Used to exit this program when the parent host process that started it exits.
/// </summary>
public static class ParentProcessTracker
{
private static bool _initialized;
private static IHost? _thisHost;

public static void ExitWhenParentProcessExists(int parentPid)
{
if (_initialized)
{
throw new InvalidOperationException("Already initialized");
}

Process? parentProcess = null;

try
{
parentProcess = Process.GetProcessById(parentPid);
parentProcess.EnableRaisingEvents = true;
}
catch
{
// ignore
}

if (parentProcess != null)
{
parentProcess.Exited += (_, _) =>
{
try
{
_thisHost?.StopAsync(TimeSpan.FromSeconds(10));
}
catch
{
// ignore
}
finally
{
Environment.Exit((int)ProgramExitCode.Success);
}
};

_initialized = true;
}
else
{
Console.WriteLine($"Parent process with ID '{parentPid}' is not running");
Environment.Exit((int)ProgramExitCode.ParentProcessIsNotRunning);
}
}

public static void SetThisHost(IHost host)
{
if (!_initialized)
{
throw new InvalidOperationException("Not initialized");
}

_thisHost = host;
}
}
114 changes: 58 additions & 56 deletions src/Apps/NetPad.Apps.App/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
using NetPad.Application;
using NetPad.Apps;
using NetPad.Apps.Shells;
using NetPad.Apps.Shells.Electron;
using NetPad.Apps.Shells.Tauri;
using NetPad.Apps.Shells.Web;
using NetPad.Configuration;
using NetPad.Swagger;
using Serilog;
Expand All @@ -19,21 +16,48 @@ namespace NetPad;

public static class Program
{
internal static IShell Shell { get; private set; } = null!;
private static bool _isSwaggerCodeGenMode;
internal static IShell? Shell;

public static async Task<int> Main(string[] args)
public static async Task<int> Main(string[] rawArgs)
{
if (args.Contains("--swagger"))
ProgramExitCode result;
var args = new ProgramArgs(rawArgs);

if (args.RunMode == RunMode.SwaggerGen)
{
_isSwaggerCodeGenMode = true;
return await GenerateSwaggerClientCodeAsync(args);
result = await GenerateSwaggerClientCodeAsync(args);
}
else
{
try
{
RunApp(args);
result = ProgramExitCode.Success;
}
catch (IOException ioException) when (ioException.Message.ContainsIgnoreCase("address already in use"))
{
Console.WriteLine($"Another instance is already running. {ioException.Message}");
Shell?.ShowErrorDialog(
$"{AppIdentifier.AppName} Already Running",
$"{AppIdentifier.AppName} is already running. You cannot open multiple instances of {AppIdentifier.AppName}.");
result = ProgramExitCode.PortUnavailable;
}
catch (Exception ex)
{
Console.WriteLine($"Host terminated unexpectedly with error:\n{ex}");
Log.Fatal(ex, "Host terminated unexpectedly");
result = ProgramExitCode.UnexpectedError;
}
finally
{
Log.CloseAndFlush();
}
}

return RunApp(args);
return (int)result;
}

private static async Task<int> GenerateSwaggerClientCodeAsync(string[] args)
private static async Task<ProgramExitCode> GenerateSwaggerClientCodeAsync(ProgramArgs args)
{
try
{
Expand Down Expand Up @@ -65,83 +89,61 @@ private static async Task<int> GenerateSwaggerClientCodeAsync(string[] args)
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
return 1;
return ProgramExitCode.SwaggerGenError;
}
}

await host.StopAsync();
return 0;
return ProgramExitCode.Success;
}
catch (Exception e)
{
Console.WriteLine(e);
return 1;
return ProgramExitCode.SwaggerGenError;
}
}

private static int RunApp(string[] args)
private static void RunApp(ProgramArgs args)
{
try
if (args.ParentPid.HasValue)
{
Shell = CreateShell(args);

var builder = CreateHostBuilder(args);
ParentProcessTracker.ExitWhenParentProcessExists(args.ParentPid.Value);
}

builder.ConfigureServices(s => s.AddSingleton(Shell));
Shell = args.CreateShell();

var host = builder.Build();
var builder = CreateHostBuilder(args);

host.Run();
return 0;
}
catch (IOException ioException) when (ioException.Message.ContainsIgnoreCase("address already in use"))
{
Console.WriteLine($"Another instance is already running. {ioException.Message}");
Shell.ShowErrorDialog(
$"{AppIdentifier.AppName} Already Running",
$"{AppIdentifier.AppName} is already running. You cannot open multiple instances of {AppIdentifier.AppName}.");
return 1;
}
catch (Exception ex)
{
Console.WriteLine($"Host terminated unexpectedly with error:\n{ex}");
Log.Fatal(ex, "Host terminated unexpectedly");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
builder.ConfigureServices(s => s.AddSingleton(Shell));

private static IShell CreateShell(string[] args)
{
if (args.Any(a => a.ContainsIgnoreCase("/ELECTRONPORT")))
{
return new ElectronShell();
}
var host = builder.Build();

if (args.Any(a => a.EqualsIgnoreCase("--tauri")))
if (args.ParentPid.HasValue)
{
return new TauriShell();
ParentProcessTracker.SetThisHost(host);
}

return new WebBrowserShell();
host.Run();
}

private static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
private static IHostBuilder CreateHostBuilder(ProgramArgs args) =>
Host.CreateDefaultBuilder(args.Raw)
.ConfigureAppConfiguration(config => { config.AddJsonFile("appsettings.Local.json", true); })
.UseSerilog((ctx, config) => { ConfigureLogging(config, ctx.Configuration); })
.ConfigureWebHostDefaults(webBuilder =>
{
if (_isSwaggerCodeGenMode)
if (args.RunMode == RunMode.SwaggerGen)
{
webBuilder.UseStartup<SwaggerCodeGenerationStartup>();
}
else
{
Shell.ConfigureWebHost(webBuilder, args);
if (Shell == null)
{
throw new Exception("Shell has not been initialized");
}

Shell.ConfigureWebHost(webBuilder, args.Raw);
webBuilder.UseStartup<Startup>();
}
});
Expand Down
61 changes: 61 additions & 0 deletions src/Apps/NetPad.Apps.App/ProgramArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Linq;
using NetPad.Apps.Shells;
using NetPad.Apps.Shells.Electron;
using NetPad.Apps.Shells.Tauri;
using NetPad.Apps.Shells.Web;

namespace NetPad;

public class ProgramArgs
{
public ProgramArgs(string[] args)
{
Raw = args;

RunMode = args.Contains("--swagger") ? RunMode.SwaggerGen : RunMode.Normal;

var parentPidArg = Array.IndexOf(args, "--parent-pid");
if (parentPidArg >= 0 && parentPidArg + 1 < args.Length)
{
if (int.TryParse(args[parentPidArg + 1], out var parentPid))
{
ParentPid = parentPid;
}
else
{
Console.WriteLine($"Invalid parent pid: {args[parentPidArg + 1]}");
Environment.Exit((int)ProgramExitCode.InvalidParentProcessPid);
}
}
}

/// <summary>
/// The raw args passed to the program.
/// </summary>
public string[] Raw { get; }

/// <summary>
/// The pid of the process that started this program.
/// </summary>
public int? ParentPid { get; }

/// <summary>
/// The mode to run the program in.
/// </summary>
public RunMode RunMode { get; }

public IShell CreateShell()
{
if (Raw.Any(a => a.ContainsIgnoreCase("/ELECTRONPORT")))
{
return new ElectronShell();
}

if (Raw.Any(a => a.EqualsIgnoreCase("--tauri")))
{
return new TauriShell();
}

return new WebBrowserShell();
}
}
11 changes: 11 additions & 0 deletions src/Apps/NetPad.Apps.App/ProgramExitCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace NetPad;

public enum ProgramExitCode
{
Success = 0,
UnexpectedError = 1,
SwaggerGenError = 2,
PortUnavailable = 3,
InvalidParentProcessPid = 4,
ParentProcessIsNotRunning = 5,
}
7 changes: 7 additions & 0 deletions src/Apps/NetPad.Apps.App/RunMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace NetPad;

public enum RunMode
{
Normal,
SwaggerGen
}
8 changes: 4 additions & 4 deletions src/Apps/NetPad.Apps.App/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironm
Console.WriteLine($" - Environment: {webHostEnvironment.EnvironmentName}");
Console.WriteLine($" - WebRootPath: {webHostEnvironment.WebRootPath}");
Console.WriteLine($" - ContentRootPath: {webHostEnvironment.ContentRootPath}");
Console.WriteLine($" - Shell: {Program.Shell.GetType().Name}");
Console.WriteLine($" - Shell: {Program.Shell?.GetType().Name}");
}

public IConfiguration Configuration { get; }
Expand Down Expand Up @@ -163,7 +163,7 @@ public void ConfigureServices(IServiceCollection services)
#endif

// Allow Shell to add/modify any service registrations it needs
Program.Shell.ConfigureServices(services);
Program.Shell?.ConfigureServices(services);
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
Expand Down Expand Up @@ -195,7 +195,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

app.UseRouting();

Program.Shell.ConfigureRequestPipeline(app, env);
Program.Shell?.ConfigureRequestPipeline(app, env);

app.UseEndpoints(endpoints =>
{
Expand All @@ -214,7 +214,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
#endif
});

Program.Shell.Initialize(app, env);
Program.Shell?.Initialize(app, env);
}

private static void InitializeHostInfo(IApplicationBuilder app, IWebHostEnvironment env)
Expand Down
Loading

0 comments on commit 677061e

Please sign in to comment.