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