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);
}
}