Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* Copyright 2014 - 2026 Adaptive Financial Consulting Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -32,7 +32,6 @@ namespace Adaptive.Archiver.IntegrationTests.Infrastructure
internal sealed class EmbeddedArchive : IDisposable
{
private const int StartupTimeoutMs = 30_000;
private const int ShutdownTimeoutMs = 10_000;

// Each instance binds the archive's UDP control channel to a unique random port in the
// ephemeral range so that two tests running back-to-back can't collide if a teardown's
Expand Down Expand Up @@ -79,7 +78,8 @@ public EmbeddedArchive(
else
{
int port;
lock (PortPicker) { port = PortPicker.Next(MinPort, MaxPort); }
lock (PortPicker)
{ port = PortPicker.Next(MinPort, MaxPort); }
_controlChannel = $"aeron:udp?endpoint=localhost:{port}";
}

Expand Down Expand Up @@ -130,7 +130,16 @@ public EmbeddedArchive(

_archive = Process.Start(psi) ?? throw new InvalidOperationException("failed to start aeron archive");

WaitForArchiveReady(aeronDirectoryName, aeronClient);
try
{
WaitForArchiveReady(aeronDirectoryName, aeronClient);
}
catch
{
EmbeddedProcess.Shutdown(_archive, "EmbeddedArchive");
_archive.Dispose();
throw;
}
}

public string ArchiveDir => _archiveDir;
Expand All @@ -156,10 +165,12 @@ public void KillProcess()
{
// Dispose the probe before killing the JVM so its pub/sub are cleanly removed
// from the conductor before the process disappears under them.
try { _probeClient?.Dispose(); } catch { }
try
{ _probeClient?.Dispose(); }
catch { }
_probeClient = null;

ShutdownProcess(_archive, "EmbeddedArchive");
EmbeddedProcess.Shutdown(_archive, "EmbeddedArchive");

try
{
Expand All @@ -176,15 +187,19 @@ public void KillProcess()

public void Dispose()
{
ShutdownProcess(_archive, "EmbeddedArchive");
EmbeddedProcess.Shutdown(_archive, "EmbeddedArchive");

// Dispose the probe after the JVM is dead so there is no window between
// REMOVE_PUBLICATION and a subsequent ADD_PUBLICATION to the same channel.
try { _probeClient?.Dispose(); } catch { }
try
{ _probeClient?.Dispose(); }
catch { }
_probeClient = null;

bool exited = false;
try { exited = _archive.HasExited; } catch { }
try
{ exited = _archive.HasExited; }
catch { }

_archive.Dispose();

Expand All @@ -203,38 +218,6 @@ public void Dispose()
}
}

internal static void ShutdownProcess(Process process, string name)
{
try
{
if (!process.HasExited)
{
// Process.Kill maps to SIGKILL on Unix and TerminateProcess on Windows;
// neither can be ignored by the target. entireProcessTree is best-effort —
// safe on both platforms.
process.Kill(entireProcessTree: true);
}
}
catch
{
}

try { process.WaitForExit(ShutdownTimeoutMs); } catch { }

try
{
if (!process.HasExited)
{
NUnit.Framework.TestContext.Progress.WriteLine(
$"WARNING: {name} JVM pid={process.Id} did not exit within {ShutdownTimeoutMs}ms after Kill");
}
}
catch
{
// Process object may already be disposed (e.g. by an outer using); not our problem.
}
}

private void WaitForArchiveReady(string aeronDirectoryName, AeronClient aeronClient)
{
var deadline = DateTime.UtcNow.AddMilliseconds(StartupTimeoutMs);
Expand All @@ -245,7 +228,9 @@ private void WaitForArchiveReady(string aeronDirectoryName, AeronClient aeronCli
if (_archive.HasExited)
{
string stderr = "";
try { stderr = _archive.StandardError.ReadToEnd(); } catch { }
try
{ stderr = _archive.StandardError.ReadToEnd(); }
catch { }
throw new InvalidOperationException(
$"archive process exited prematurely with code {_archive.ExitCode}\nSTDERR:\n{stderr}");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* Copyright 2014 - 2026 Adaptive Financial Consulting Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -32,7 +32,6 @@ namespace Adaptive.Archiver.IntegrationTests.Infrastructure
internal sealed class EmbeddedMediaDriver : IDisposable
{
private const int StartupTimeoutMs = 15_000;
private const int ShutdownTimeoutMs = 10_000;

private readonly Process _driver;
private readonly string _aeronDir;
Expand Down Expand Up @@ -61,7 +60,9 @@ public EmbeddedMediaDriver(

if (Directory.Exists(_aeronDir))
{
try { Directory.Delete(_aeronDir, recursive: true); } catch { }
try
{ Directory.Delete(_aeronDir, recursive: true); }
catch { }
}

var rootDir =
Expand Down Expand Up @@ -125,17 +126,28 @@ public EmbeddedMediaDriver(

_driver = Process.Start(psi) ?? throw new InvalidOperationException("failed to start media driver");

WaitForDriverReady();
try
{
WaitForDriverReady();
}
catch
{
EmbeddedProcess.Shutdown(_driver, "EmbeddedMediaDriver");
_driver.Dispose();
throw;
}
}

public string AeronDirectoryName => _aeronDir;

public void Dispose()
{
EmbeddedArchive.ShutdownProcess(_driver, "EmbeddedMediaDriver");
EmbeddedProcess.Shutdown(_driver, "EmbeddedMediaDriver");

bool exited = false;
try { exited = _driver.HasExited; } catch { }
try
{ exited = _driver.HasExited; }
catch { }

_driver.Dispose();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2014 - 2026 Adaptive Financial Consulting Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Diagnostics;

namespace Adaptive.Archiver.IntegrationTests.Infrastructure
{
internal static class EmbeddedProcess
{
public static void Shutdown(Process process, string name, int shutdownTimeoutMs = 10_000)
{
try
{
if (!process.HasExited)
{
process.Kill(entireProcessTree: true);
}
}
catch
{
}

try
{
process.WaitForExit(shutdownTimeoutMs);
}
catch
{
}

try
{
if (!process.HasExited)
{
NUnit.Framework.TestContext.Progress.WriteLine(
$"WARNING: {name} JVM pid={process.Id} did not exit within {shutdownTimeoutMs}ms after Kill");
}
}
catch
{
// Process object may already be disposed; nothing to do here.
}
}
}
}
Loading