Closed JustArchi closed 9 months ago
ArchiCacheable doesn't support CancellationToken for now, making it impossible to cancel the task before given delay or signal.
ArchiCacheable
CancellationToken
// _ _ _ ____ _ _____ // / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ // / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // | // Copyright 2022-2023 Łukasz "JustArchi" Domeradzki // Contact: JustArchi@JustArchi.net using System; using System.ComponentModel; using System.Threading; using System.Threading.Tasks; namespace ArchiSteamFarmBackend.Helpers; internal sealed class ArchiCacheable<T> : IDisposable { private readonly TimeSpan CacheLifetime; private readonly SemaphoreSlim InitSemaphore = new(1, 1); private readonly Func<CancellationToken, Task<(bool Success, T? Result)>> ResolveFunction; private bool IsInitialized => InitializedAt > DateTime.MinValue; private bool IsPermanentCache => CacheLifetime == Timeout.InfiniteTimeSpan; private bool IsRecent => IsPermanentCache || (DateTime.UtcNow.Subtract(InitializedAt) < CacheLifetime); private DateTime InitializedAt; private T? InitializedValue; internal ArchiCacheable(Func<CancellationToken, Task<(bool Success, T? Result)>> resolveFunction, TimeSpan? cacheLifetime = null) { ResolveFunction = resolveFunction ?? throw new ArgumentNullException(nameof(resolveFunction)); CacheLifetime = cacheLifetime ?? Timeout.InfiniteTimeSpan; } public void Dispose() => InitSemaphore.Dispose(); internal async Task<(bool Success, T? Result)> GetValue(EFallback fallback = EFallback.DefaultForType, CancellationToken cancellationToken = default) { if (!Enum.IsDefined(typeof(EFallback), fallback)) { throw new InvalidEnumArgumentException(nameof(fallback), (int) fallback, typeof(EFallback)); } if (IsInitialized && IsRecent) { return (true, InitializedValue); } try { await InitSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException e) { Program.ArchiLogger.LogGenericDebuggingException(e); return ReturnFailedValueFor(fallback); } try { if (IsInitialized && IsRecent) { return (true, InitializedValue); } (bool success, T? result) = await ResolveFunction(cancellationToken).ConfigureAwait(false); if (!success) { return ReturnFailedValueFor(fallback, result); } InitializedValue = result; InitializedAt = DateTime.UtcNow; return (true, result); } catch (OperationCanceledException e) { Program.ArchiLogger.LogGenericDebuggingException(e); return ReturnFailedValueFor(fallback); } finally { InitSemaphore.Release(); } } internal async Task Reset() { if (!IsInitialized) { return; } await InitSemaphore.WaitAsync().ConfigureAwait(false); try { if (!IsInitialized) { return; } InitializedAt = DateTime.MinValue; } finally { InitSemaphore.Release(); } } private (bool Success, T? Result) ReturnFailedValueFor(EFallback fallback, T? result = default) { if (!Enum.IsDefined(typeof(EFallback), fallback)) { throw new InvalidEnumArgumentException(nameof(fallback), (int) fallback, typeof(EFallback)); } return fallback switch { EFallback.DefaultForType => (false, default(T?)), EFallback.FailedNow => (false, result), EFallback.SuccessPreviously => (false, InitializedValue), _ => throw new InvalidOperationException(nameof(fallback)) }; } }
Or equivalent.
Usually those calls are expensive, allow inheritors to implement their own cancellation if needed.
Yes, I can code the solution myself and send a pull request
Breaking change for all inheritors, don't ship before .NET 8 when we break more things.
Checklist
Enhancement purpose
ArchiCacheable
doesn't supportCancellationToken
for now, making it impossible to cancel the task before given delay or signal.Solution
Or equivalent.
Why currently available solutions are not sufficient?
Usually those calls are expensive, allow inheritors to implement their own cancellation if needed.
Can you help us with this enhancement idea?
Yes, I can code the solution myself and send a pull request
Additional info
Breaking change for all inheritors, don't ship before .NET 8 when we break more things.