From bc134d7a4fb50086c56d6de5119165ca15dc2190 Mon Sep 17 00:00:00 2001 From: Nadia Aina <113453463+Nadia-Adaptive@users.noreply.github.com> Date: Fri, 18 Sep 2026 13:04:03 +0100 Subject: [PATCH] Close java processes if they fail on start-up --- .../Infrastructure/EmbeddedArchive.cs | 69 ++++++++----------- .../Infrastructure/EmbeddedMediaDriver.cs | 24 +++++-- .../Infrastructure/EmbeddedProcess.cs | 57 +++++++++++++++ 3 files changed, 102 insertions(+), 48 deletions(-) create mode 100644 src/Adaptive.Archiver.IntegrationTests/Infrastructure/EmbeddedProcess.cs diff --git a/src/Adaptive.Archiver.IntegrationTests/Infrastructure/EmbeddedArchive.cs b/src/Adaptive.Archiver.IntegrationTests/Infrastructure/EmbeddedArchive.cs index c462e9fe..42dfbd24 100644 --- a/src/Adaptive.Archiver.IntegrationTests/Infrastructure/EmbeddedArchive.cs +++ b/src/Adaptive.Archiver.IntegrationTests/Infrastructure/EmbeddedArchive.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2014 - 2026 Adaptive Financial Consulting Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -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 @@ -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}"; } @@ -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; @@ -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 { @@ -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(); @@ -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); @@ -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}"); } diff --git a/src/Adaptive.Archiver.IntegrationTests/Infrastructure/EmbeddedMediaDriver.cs b/src/Adaptive.Archiver.IntegrationTests/Infrastructure/EmbeddedMediaDriver.cs index 01abbf63..730e9b91 100644 --- a/src/Adaptive.Archiver.IntegrationTests/Infrastructure/EmbeddedMediaDriver.cs +++ b/src/Adaptive.Archiver.IntegrationTests/Infrastructure/EmbeddedMediaDriver.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2014 - 2026 Adaptive Financial Consulting Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -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; @@ -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 = @@ -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(); diff --git a/src/Adaptive.Archiver.IntegrationTests/Infrastructure/EmbeddedProcess.cs b/src/Adaptive.Archiver.IntegrationTests/Infrastructure/EmbeddedProcess.cs new file mode 100644 index 00000000..989926ff --- /dev/null +++ b/src/Adaptive.Archiver.IntegrationTests/Infrastructure/EmbeddedProcess.cs @@ -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. + } + } + } +}