Skip to content

Commit

Permalink
SIANXSVC-1186: Terminate application after writing metadata (#20)
Browse files Browse the repository at this point in the history
* SIANXSVC-1186: Update documentation for dotnet-e5e for service initialization
* SIANXSVC-1186: Exit application after writing metadata

Closes SIANXSVC-1186
  • Loading branch information
nachtjasmin authored Feb 2, 2024
1 parent 07038d8 commit 25b1009
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 7 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ await host.RunAsync();

Further examples can be found in the [examples folder](./examples).

### Note on startup initialization

Due to the fact that the communication with e5e relies on the `IHost` to run, any initialization code should
**not** be done before starting the host. It **must** be done after starting the host, for example by splitting
up the `RunAsync` call like in the example below.

```csharp
using var host = /* ...omitted for brevity */

// Don't do this or bad things are going to happen.
await SomeExtremelyLongStartupTask();

// Instead do this:
await host.StartAsync();
await SomeExtremelyLongStartupTask();
await host.WaitForShutdownAsync();
```

# Supported versions

| | Supported |
Expand Down
5 changes: 5 additions & 0 deletions src/Anexia.E5E.Tests/Integration/StartupTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
using System.Threading;
using System.Threading.Tasks;

using Anexia.E5E.Abstractions.Termination;
using Anexia.E5E.Runtime;
using Anexia.E5E.Serialization;
using Anexia.E5E.Tests.TestHelpers;

using Microsoft.Extensions.DependencyInjection;

using Xunit;
using Xunit.Abstractions;

Expand Down Expand Up @@ -36,5 +39,7 @@ public async Task TerminatesAutomatically()
cts.Token.Register(() => Assert.Fail("Shutdown took longer than three seconds"), true);

await Host.WaitForShutdownAsync(cts.Token);
var terminator = Host.Inner.Services.GetRequiredService<ITerminator>() as TerminatorMock;
Assert.True(terminator?.Called);
}
}
10 changes: 10 additions & 0 deletions src/Anexia.E5E.Tests/TestHelpers/TerminatorMock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Anexia.E5E.Abstractions.Termination;

namespace Anexia.E5E.Tests.TestHelpers;

public class TerminatorMock : ITerminator
{
public void Exit() { Called = true; }

public bool Called { get; private set; }
}
7 changes: 6 additions & 1 deletion src/Anexia.E5E.Tests/TestHelpers/TestHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading.Tasks;

using Anexia.E5E.Abstractions;
using Anexia.E5E.Abstractions.Termination;
using Anexia.E5E.Extensions;
using Anexia.E5E.Functions;
using Anexia.E5E.Runtime;
Expand Down Expand Up @@ -53,7 +54,11 @@ public TestHostBuilder ConfigureEndpoints(Action<IE5EEntrypointBuilder> configur
public Task StartAsync(int maximumLifetimeMs = 5000)
{
// In order to override the default implementation, we need to call this just before we build our host.
_hb.ConfigureServices(services => services.AddSingleton<IConsoleAbstraction, TestConsoleAbstraction>());
_hb.ConfigureServices(services =>
{
services.AddSingleton<IConsoleAbstraction, TestConsoleAbstraction>();
services.AddSingleton<ITerminator, TerminatorMock>();
});
_host = _hb.Build();

// Shutdown the host automatically after five seconds, there's no test that should run that long.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Anexia.E5E.Abstractions.Termination;

internal sealed class EnvironmentTerminator : ITerminator
{
public void Exit() => Environment.Exit(0);
}
10 changes: 10 additions & 0 deletions src/Anexia.E5E/Abstractions/Termination/ITerminator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Anexia.E5E.Abstractions.Termination;

/// <summary>
/// Because the usage of <see cref="Environment.Exit"/> would also crash our test processes, it is abstracted.
/// By default, the <see cref="EnvironmentTerminator"/> is used as implementation.
/// </summary>
internal interface ITerminator
{
void Exit();
}
5 changes: 5 additions & 0 deletions src/Anexia.E5E/Anexia.E5E.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,9 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0"/>
<PackageReference Include="DotNet.ReproducibleBuilds" Version="1.1.1" PrivateAssets="All"/>
</ItemGroup>

<ItemGroup>
<!-- Make the internal members visible to Anexia.E5E.Tests -->
<InternalsVisibleTo Include="$(AssemblyName).Tests"/>
</ItemGroup>
</Project>
14 changes: 8 additions & 6 deletions src/Anexia.E5E/Hosting/E5EHostWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Text.Json;

using Anexia.E5E.Abstractions;
using Anexia.E5E.Abstractions.Termination;
using Anexia.E5E.Runtime;
using Anexia.E5E.Serialization;

Expand All @@ -12,6 +13,7 @@ namespace Anexia.E5E.Hosting;
internal sealed class E5EHostWrapper : IHost
{
private readonly IConsoleAbstraction _console;
private readonly ITerminator _terminator;
private readonly IHost _host;
private readonly E5ERuntimeOptions _options;

Expand All @@ -20,6 +22,7 @@ public E5EHostWrapper(IHost host)
_host = host;
_options = Services.GetRequiredService<E5ERuntimeOptions>();
_console = Services.GetRequiredService<IConsoleAbstraction>();
_terminator = Services.GetService<ITerminator>() ?? new EnvironmentTerminator();
}

public void Dispose()
Expand All @@ -35,21 +38,20 @@ public async Task StartAsync(CancellationToken cancellationToken = default)
return;
}

string metadata = "";
#if NET8_0_OR_GREATER
metadata = JsonSerializer.Serialize(new E5ERuntimeMetadata(),
var metadata = JsonSerializer.Serialize(new E5ERuntimeMetadata(),
E5ESerializationContext.Default.E5ERuntimeMetadata);
#else
metadata = JsonSerializer.Serialize(new E5ERuntimeMetadata(), E5EJsonSerializerOptions.Default);
var metadata = JsonSerializer.Serialize(new E5ERuntimeMetadata(), E5EJsonSerializerOptions.Default);
#endif

_console.Open();
await _console.WriteToStdoutAsync(metadata).ConfigureAwait(false);
_console.Close();

// If we wrote the metadata, circumvent the default host mechanism as used by Run/RunAsync extensions
// and just stop the application.
_host.Services.GetRequiredService<IHostApplicationLifetime>().StopApplication();
// After we wrote the metadata, close the application immediately(!). Any startup tasks that occur after the startup
// (e.g. a long initialization task) won't be executed and the metadata is returned to e5e.
_terminator.Exit();
}

public Task StopAsync(CancellationToken cancellationToken = default)
Expand Down

0 comments on commit 25b1009

Please sign in to comment.