From efa4844049accf55c813e68e40daa5776dad1951 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 20 Oct 2026 06:02:16 -0400 Subject: [PATCH] test(benchmark): prove parallelism reach with a rendezvous, not overlapping sleeps Run_NeverExceedsMaxParallelism_ButReachesIt asserted two 200 ms solves overlapped, which depends on the thread pool adding a second worker in time; hosted Windows release run 39400265218 saw only one. Each fake solve now waits (up to 10 s) until two solves have entered, so a parallel runner always reaches the limit and a serial one fails once after the timeout. Forcing MaxDegreeOfParallelism = 1 fails it (Expected 2, Actual 1). Project Memory b534b51c. --- .../Benchmark/BenchmarkRunnerTests.cs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/OpenNest.Tests/Benchmark/BenchmarkRunnerTests.cs b/OpenNest.Tests/Benchmark/BenchmarkRunnerTests.cs index 6118ac8a..5c5602bd 100644 --- a/OpenNest.Tests/Benchmark/BenchmarkRunnerTests.cs +++ b/OpenNest.Tests/Benchmark/BenchmarkRunnerTests.cs @@ -237,11 +237,13 @@ public sealed class BenchmarkRunnerTests : IDisposable [Fact] public void Run_NeverExceedsMaxParallelism_ButReachesIt() { - var probe = new ConcurrencyProbe(); + // Each solve waits until two solves overlap, so reaching the limit does not depend on + // how fast the thread pool adds a second worker. A serial runner times out and fails. + var probe = new ConcurrencyProbe(rendezvousAt: 2); var results = BenchmarkRunner.Run( LoadJob(), - FakeEngines(probe, 200, 200, 200, 200, 200, 200), + FakeEngines(probe, 50, 50, 50, 50, 50, 50), maxParallelism: 2 ); @@ -330,8 +332,10 @@ public sealed class BenchmarkRunnerTests : IDisposable } } - private sealed class ConcurrencyProbe + private sealed class ConcurrencyProbe(int rendezvousAt = 0) { + private static readonly TimeSpan RendezvousTimeout = TimeSpan.FromSeconds(10); + private readonly ManualResetEventSlim _rendezvous = new(rendezvousAt <= 1); private int _current; private int _max; @@ -346,6 +350,18 @@ public sealed class BenchmarkRunnerTests : IDisposable if (Interlocked.CompareExchange(ref _max, now, seen) == seen) break; } + if (now >= rendezvousAt) + _rendezvous.Set(); + } + + /// + /// Blocks until rendezvousAt solves have overlapped. On timeout it releases every + /// later waiter, so a runner that never overlaps fails once instead of once per solve. + /// + public void WaitForRendezvous() + { + if (!_rendezvous.Wait(RendezvousTimeout)) + _rendezvous.Set(); } public void Exit() => Interlocked.Decrement(ref _current); @@ -363,6 +379,7 @@ public sealed class BenchmarkRunnerTests : IDisposable probe.Enter(); try { + probe.WaitForRendezvous(); Thread.Sleep(delayMs); return new NestJobResult( NestJobStatus.Incomplete,