Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SIANXSVC-1186: Terminate application after writing metadata #20

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading