using System;
using System.Threading;
using System.Threading.Tasks;
using Npgsql.Util;
namespace Npgsql;
static class TaskExtensions
{
///
/// Utility that simplifies awaiting a task with a timeout. If the given task does not
/// complete within , a is thrown.
///
/// The task to be awaited
/// How much time to allow to complete before throwing a
/// An awaitable task that represents the original task plus the timeout
internal static async Task WithTimeout(this Task task, NpgsqlTimeout timeout)
{
if (!timeout.IsSet)
return await task;
var timeLeft = timeout.CheckAndGetTimeLeft();
if (task != await Task.WhenAny(task, Task.Delay(timeLeft)))
throw new TimeoutException();
return await task;
}
///
/// Allows you to cancel awaiting for a non-cancellable task.
///
///
/// Read https://blogs.msdn.com/b/pfxteam/archive/2012/10/05/how-do-i-cancel-non-cancelable-async-operations.aspx
/// and be very careful with this.
///
internal static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource();
using (cancellationToken.Register(s => ((TaskCompletionSource)s!).TrySetResult(true), tcs))
if (task != await Task.WhenAny(task, tcs.Task))
throw new TaskCanceledException(task);
return await task;
}
internal static Task WithCancellationAndTimeout(this Task task, NpgsqlTimeout timeout, CancellationToken cancellationToken)
=> task.WithCancellation(cancellationToken).WithTimeout(timeout);
#if !NET5_0_OR_GREATER
///
/// Utility that simplifies awaiting a task with a timeout. If the given task does not
/// complete within , a is thrown.
///
/// The task to be awaited
/// How much time to allow to complete before throwing a
/// An awaitable task that represents the original task plus the timeout
internal static async Task WithTimeout(this Task task, NpgsqlTimeout timeout)
{
if (!timeout.IsSet)
{
await task;
return;
}
var timeLeft = timeout.CheckAndGetTimeLeft();
if (task != await Task.WhenAny(task, Task.Delay(timeLeft)))
throw new TimeoutException();
await task;
}
///
/// Allows you to cancel awaiting for a non-cancellable task.
///
///
/// Read https://blogs.msdn.com/b/pfxteam/archive/2012/10/05/how-do-i-cancel-non-cancelable-async-operations.aspx
/// and be very careful with this.
///
internal static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource();
using (cancellationToken.Register(s => ((TaskCompletionSource)s!).TrySetResult(true), tcs))
if (task != await Task.WhenAny(task, tcs.Task))
throw new TaskCanceledException(task);
await task;
}
internal static Task WithCancellationAndTimeout(this Task task, NpgsqlTimeout timeout, CancellationToken cancellationToken)
=> task.WithCancellation(cancellationToken).WithTimeout(timeout);
#endif
internal static async Task ExecuteWithTimeout(Func func, NpgsqlTimeout timeout, CancellationToken cancellationToken)
{
CancellationTokenSource? combinedCts = null;
try
{
var combinedCancellationToken = GetCombinedCancellationToken(ref combinedCts, timeout, cancellationToken);
await func(combinedCancellationToken);
}
finally
{
combinedCts?.Dispose();
}
}
internal static async Task ExecuteWithTimeout(Func> func, NpgsqlTimeout timeout, CancellationToken cancellationToken)
{
CancellationTokenSource? combinedCts = null;
try
{
var combinedCancellationToken = GetCombinedCancellationToken(ref combinedCts, timeout, cancellationToken);
return await func(combinedCancellationToken);
}
finally
{
combinedCts?.Dispose();
}
}
static CancellationToken GetCombinedCancellationToken(ref CancellationTokenSource? combinedCts, NpgsqlTimeout timeout, CancellationToken cancellationToken)
{
var finalCt = cancellationToken;
if (timeout.IsSet)
{
combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
combinedCts.CancelAfter((int)timeout.CheckAndGetTimeLeft().TotalMilliseconds);
finalCt = combinedCts.Token;
}
return finalCt;
}
}