namespace NATS.Server.TestUtilities; /// /// Provides bounded polling helpers for test fixtures that need to wait /// for asynchronous conditions across independent components (e.g. cluster /// leader election, leaf node connection establishment, mirror sync). /// These use with timed waits to yield the /// thread between polls rather than raw Task.Delay. /// public static class PollHelper { /// /// Polls at intervals /// until it returns true or elapses. /// Returns true if the condition was met, false on timeout. /// public static async Task WaitUntilAsync( Func condition, int timeoutMs = 5000, int intervalMs = 10) { using var cts = new CancellationTokenSource(timeoutMs); using var gate = new SemaphoreSlim(0, 1); while (!cts.IsCancellationRequested) { if (condition()) return true; try { await gate.WaitAsync(intervalMs, cts.Token); } catch (OperationCanceledException) { break; } } return condition(); } /// /// Polls (async overload) at /// intervals until it returns true or elapses. /// Returns true if the condition was met, false on timeout. /// public static async Task WaitUntilAsync( Func> condition, int timeoutMs = 5000, int intervalMs = 10) { using var cts = new CancellationTokenSource(timeoutMs); using var gate = new SemaphoreSlim(0, 1); while (!cts.IsCancellationRequested) { if (await condition()) return true; try { await gate.WaitAsync(intervalMs, cts.Token); } catch (OperationCanceledException) { break; } } return await condition(); } /// /// Polls and throws /// with if the condition is not met within . /// public static async Task WaitOrThrowAsync( Func condition, string message, int timeoutMs = 5000, int intervalMs = 10) { if (!await WaitUntilAsync(condition, timeoutMs, intervalMs)) throw new TimeoutException(message); } /// /// Polls (async overload) and throws /// with if the condition /// is not met within . /// public static async Task WaitOrThrowAsync( Func> condition, string message, int timeoutMs = 5000, int intervalMs = 10) { if (!await WaitUntilAsync(condition, timeoutMs, intervalMs)) throw new TimeoutException(message); } /// /// Yields the current task for approximately using a /// semaphore-based timed wait rather than Task.Delay. /// public static async Task YieldForAsync(int delayMs) { using var gate = new SemaphoreSlim(0, 1); await gate.WaitAsync(delayMs); } }