/
dev-npgsql
/
npgsql
Обзор
Документация
Войти
/
dev-npgsql
/
npgsql
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
v9.0.5
src/Npgsql/Util/ResettableCancellationTokenSource.cs
230 строк
7 KB
Shay Rojansky
Apply primary constructors to solution (#5908)
30 окт 2024, 18:30
Не верифицирован
30 окт 2024, 18:30
40cc1a1
Код
Авторство
О чём код?
using System; using System.Diagnostics; using System.Threading; using static System.Threading.Timeout; namespace Npgsql.Util; /// <summary> /// A wrapper around <see cref="CancellationTokenSource"/> to simplify reset management. /// </summary> /// <remarks> /// Since there's no way to reset a <see cref="CancellationTokenSource"/> once it was cancelled, /// we need to make sure that an existing cancellation token source hasn't been cancelled, /// every time we start it (see https://github.com/dotnet/runtime/issues/4694). /// </remarks> sealed class ResettableCancellationTokenSource(TimeSpan timeout) : IDisposable { bool isDisposed; public TimeSpan Timeout { get; set; } = timeout; CancellationTokenSource _cts = new(); CancellationTokenRegistration? _registration; /// <summary> /// Used, so we wouldn't concurrently use the cts for the cancellation, while it's being disposed /// </summary> readonly object lockObject = new(); #if DEBUG bool _isRunning; #endif public ResettableCancellationTokenSource() : this(InfiniteTimeSpan) { } /// <summary> /// Set the timeout on the wrapped <see cref="CancellationTokenSource"/> /// and make sure that it hasn't been cancelled yet /// </summary> /// <param name="cancellationToken"> /// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>. /// </param> /// <returns>The <see cref="CancellationToken"/> from the wrapped <see cref="CancellationTokenSource"/></returns> public CancellationToken Start(CancellationToken cancellationToken = default) { #if DEBUG Debug.Assert(!_isRunning); #endif lock (lockObject) { // if there was an attempt to cancel while the connector was breaking // we do nothing and return the default token // as we're going to fail while reading or writing anyway if (isDisposed) { #if DEBUG _isRunning = true; #endif return CancellationToken.None; } _cts.CancelAfter(Timeout); if (_cts.IsCancellationRequested) { _cts.Dispose(); _cts = new CancellationTokenSource(Timeout); } } if (cancellationToken.CanBeCanceled) _registration = cancellationToken.Register(cts => ((CancellationTokenSource)cts!).Cancel(), _cts); #if DEBUG _isRunning = true; #endif return _cts.Token; } /// <summary> /// Restart the timeout on the wrapped <see cref="CancellationTokenSource"/> without reinitializing it, /// even if <see cref="IsCancellationRequested"/> is already set to <see langword="true"/> /// </summary> public void RestartTimeoutWithoutReset() { lock (lockObject) { // if there was an attempt to cancel while the connector was breaking // we do nothing and return the default token // as we're going to fail while reading or writing anyway if (!isDisposed) _cts.CancelAfter(Timeout); } } /// <summary> /// Reset the wrapper to contain a unstarted and uncancelled <see cref="CancellationTokenSource"/> /// in order make sure the next call to <see cref="Start"/> will not invalidate /// the cancellation token. /// </summary> /// <returns>The <see cref="CancellationToken"/> from the wrapped <see cref="CancellationTokenSource"/></returns> public CancellationToken Reset() { _registration?.Dispose(); _registration = null; lock (lockObject) { // if there was an attempt to cancel while the connector was breaking // we do nothing and return // as we're going to fail while reading or writing anyway if (isDisposed) { #if DEBUG _isRunning = false; #endif return CancellationToken.None; } _cts.CancelAfter(InfiniteTimeSpan); if (_cts.IsCancellationRequested) { _cts.Dispose(); _cts = new CancellationTokenSource(); } } #if DEBUG _isRunning = false; #endif return _cts.Token; } /// <summary> /// Reset the wrapper to contain a unstarted and uncancelled <see cref="CancellationTokenSource"/> /// in order make sure the next call to <see cref="Start"/> will not invalidate /// the cancellation token. /// </summary> public void ResetCts() { if (_cts.IsCancellationRequested) { _cts.Dispose(); _cts = new CancellationTokenSource(); } } /// <summary> /// Set the timeout on the wrapped <see cref="CancellationTokenSource"/> /// to <see cref="System.Threading.Timeout.InfiniteTimeSpan"/> /// </summary> /// <remarks> /// <see cref="IsCancellationRequested"/> can still arrive at a state /// where it's value is <see langword="true"/> if the <see cref="CancellationToken"/> /// passed to <see cref="Start"/> gets a cancellation request. /// If this is the case it will be resolved upon the next call to <see cref="Start"/> /// or <see cref="Reset"/>. Calling <see cref="Stop"/> multiple times or without calling /// <see cref="Start"/> first will do no any harm (besides eating a tiny amount of CPU cycles). /// </remarks> public void Stop() { _registration?.Dispose(); _registration = null; lock (lockObject) { // if there was an attempt to cancel while the connector was breaking // we do nothing if (!isDisposed) _cts.CancelAfter(InfiniteTimeSpan); } #if DEBUG _isRunning = false; #endif } /// <summary> /// Cancel the wrapped <see cref="CancellationTokenSource"/> /// </summary> public void Cancel() { lock (lockObject) { // if there was an attempt to cancel while the connector was breaking // we do nothing if (isDisposed) return; _cts.Cancel(); } } /// <summary> /// Cancel the wrapped <see cref="CancellationTokenSource"/> after delay /// </summary> public void CancelAfter(int delay) { lock (lockObject) { // if there was an attempt to cancel while the connector was breaking // we do nothing if (isDisposed) return; _cts.CancelAfter(delay); } } /// <summary> /// The <see cref="CancellationToken"/> from the wrapped /// <see cref="CancellationTokenSource"/> . /// </summary> /// <remarks> /// The token is only valid after calling <see cref="Start"/> /// and before calling <see cref="Start"/> the next time. /// Otherwise you may end up with a token that has already been /// cancelled or belongs to a cancellation token source that has /// been disposed. /// </remarks> public CancellationToken Token => _cts.Token; public bool IsCancellationRequested => _cts.IsCancellationRequested; public void Dispose() { Debug.Assert(!isDisposed); lock (lockObject) { _registration?.Dispose(); _cts.Dispose(); isDisposed = true; } } }