Beispiel #1
0
 public StateSnapshot(IComputed <T> computed)
 {
     Computed          = computed;
     LastValueComputed = computed;
     UpdateCount       = 0;
     FailureCount      = 0;
     RetryCount        = 0;
 }
Beispiel #2
0
 internal WhenInvalidatedClosure(TaskSource <Unit> taskSource, IComputed computed, CancellationToken cancellationToken)
 {
     _taskSource                    = taskSource;
     _computed                      = computed;
     _onInvalidatedHandler          = OnInvalidated;
     _computed.Invalidated         += _onInvalidatedHandler;
     _cancellationTokenRegistration = cancellationToken.Register(OnUnregister);
 }
 private async Task <IComputed <T> > Update <T>(IComputed <T> computed, CancellationToken cancellationToken = default)
 {
     if (computed is IReplicaMethodComputed rc)
     {
         await rc.Replica !.RequestUpdate(cancellationToken);
     }
     return(await computed.Update(false, cancellationToken));
 }
Beispiel #4
0
 internal static void UseNew(this IComputed computed, ComputeContext context, IComputed?usedBy)
 {
     ((IComputedImpl?)usedBy)?.AddUsed((IComputedImpl)computed);
     if ((context.CallOptions & CallOptions.Capture) != 0)
     {
         Interlocked.Exchange(ref context.CapturedComputed, computed);
     }
 }
        protected override async ValueTask <IComputed <T> > ComputeAsync(
            InterceptedInput input, IComputed <T>?cached,
            CancellationToken cancellationToken)
        {
            var tag    = LTagGenerator.Next();
            var method = Method;
            var output = new Computed <InterceptedInput, T>(method.Options, input, tag);

            try {
                using var _ = Computed.ChangeCurrent(output);
                var resultTask = input.InvokeOriginalFunction(cancellationToken);
                if (method.ReturnsComputed)
                {
                    if (method.ReturnsValueTask)
                    {
                        var task = (ValueTask <IComputed <T> >)resultTask;
                        await task.ConfigureAwait(false);

                        // output == task.Result here, so no need to call output.TrySetOutput(...)
                    }
                    else
                    {
                        var task = (Task <IComputed <T> >)resultTask;
                        await task.ConfigureAwait(false);

                        // output == task.Result here, so no need to call output.TrySetOutput(...)
                    }
                }
                else
                {
                    if (method.ReturnsValueTask)
                    {
                        var task  = (ValueTask <T>)resultTask;
                        var value = await task.ConfigureAwait(false);

                        output.TrySetOutput(value !);
                    }
                    else
                    {
                        var task  = (Task <T>)resultTask;
                        var value = await task.ConfigureAwait(false);

                        output.TrySetOutput(value !);
                    }
                }
            }
            catch (OperationCanceledException) {
                throw;
            }
            catch (Exception e) {
                output.TrySetOutput(Result.Error <T>(e));
                // Weird case: if the output is already set, all we can
                // is to ignore the exception we've just caught;
                // throwing it further will probably make it just worse,
                // since the the caller have to take this scenario into acc.
            }
            return(output);
        }
Beispiel #6
0
        public static Task InvalidatedAsync <T>(this IComputed <T> computed, CancellationToken cancellationToken = default)
        {
            if (computed.State == ComputedState.Invalidated)
            {
                return(Task.CompletedTask);
            }
            var ts = TaskSource.New <Unit>(true);

            computed.Invalidated += c => ts.SetResult(default);
Beispiel #7
0
 public StateSnapshot(IState <T> state, IComputed <T> computed)
 {
     State    = state;
     Computed = computed;
     LatestNonErrorComputed = computed;
     WhenUpdatingSource     = TaskSource.New <Unit>(true);
     WhenUpdatedSource      = TaskSource.New <Unit>(true);
     UpdateCount            = 0;
     ErrorCount             = 0;
     RetryCount             = 0;
 }
Beispiel #8
0
 internal static bool TryUseExistingFromUse <T>(this IComputed <T>?existing, ComputeContext context, IComputed?usedBy)
 {
     if (existing == null || !existing.IsConsistent())
     {
         return(false);
     }
     context.TryCapture(existing);
     ((IComputedImpl?)usedBy)?.AddUsed((IComputedImpl)existing !);
     ((IComputedImpl?)existing)?.RenewTimeouts();
     return(true);
 }
Beispiel #9
0
        public virtual void Register(IComputed computed)
        {
            // Debug.WriteLine($"{nameof(Register)}: {computed}");
            var key    = computed.Input;
            var random = Randomize(key.HashCode);

            OnOperation(random);

            var   spinWait = new SpinWait();
            Entry?newEntry = null;

            while (computed.State != ComputedState.Invalidated)
            {
                if (_storage.TryGetValue(key, out var entry))
                {
                    var handle = entry.Handle;
                    var target = (IComputed?)handle.Target;
                    if (target == computed)
                    {
                        break;
                    }
                    if (target == null || target.State == ComputedState.Invalidated)
                    {
                        if (_storage.TryRemove(key, entry))
                        {
                            _gcHandlePool.Release(handle, random);
                        }
                    }
                    else
                    {
                        // This typically triggers Unregister -
                        // except for ReplicaClientComputed.
                        target.Invalidate();
                    }
                }
                else
                {
                    newEntry ??= new Entry(computed, _gcHandlePool.Acquire(computed, random));
                    if (_storage.TryAdd(key, newEntry.GetValueOrDefault()))
                    {
                        if (computed.State == ComputedState.Invalidated)
                        {
                            if (_storage.TryRemove(key, entry))
                            {
                                _gcHandlePool.Release(entry.Handle, random);
                            }
                        }
                        break;
                    }
                }
                spinWait.SpinOnce();
            }
        }
Beispiel #10
0
 public StateSnapshot(IComputed <T> computed, StateSnapshot <T> lastSnapshot)
 {
     Computed = computed;
     if (computed.HasValue)
     {
         LastValueComputed = computed;
         UpdateCount       = 1 + lastSnapshot.UpdateCount;
         FailureCount      = lastSnapshot.FailureCount;
         RetryCount        = 0;
     }
     else
     {
         LastValueComputed = lastSnapshot.LastValueComputed;
         UpdateCount       = 1 + lastSnapshot.UpdateCount;
         FailureCount      = 1 + lastSnapshot.FailureCount;
         RetryCount        = 1 + lastSnapshot.RetryCount;
     }
 }
 public PublicationState(IPublication <T> publication, IComputed <T> computed, Moment createdAt, bool isDisposed,
                         TaskSource <Unit> whenInvalidatedSource = default,
                         TaskSource <Unit> whenOutdatedSource    = default)
 {
     if (whenInvalidatedSource.IsEmpty)
     {
         whenInvalidatedSource = TaskSource.New <Unit>(true);
     }
     if (whenOutdatedSource.IsEmpty)
     {
         whenOutdatedSource = TaskSource.New <Unit>(true);
     }
     Publication           = publication;
     CreatedAt             = createdAt;
     IsDisposed            = isDisposed;
     WhenInvalidatedSource = whenInvalidatedSource;
     WhenOutdatedSource    = whenOutdatedSource;
     Computed              = computed;
     computed.Invalidated += _ => WhenInvalidatedSource.TrySetResult(default);
Beispiel #12
0
 public StateSnapshot(StateSnapshot <T> prevSnapshot, IComputed <T> computed)
 {
     State              = prevSnapshot.State;
     Computed           = computed;
     WhenUpdatingSource = TaskSource.New <Unit>(true);
     WhenUpdatedSource  = TaskSource.New <Unit>(true);
     if (computed.HasValue)
     {
         LatestNonErrorComputed = computed;
         UpdateCount            = 1 + prevSnapshot.UpdateCount;
         ErrorCount             = prevSnapshot.ErrorCount;
         RetryCount             = 0;
     }
     else
     {
         LatestNonErrorComputed = prevSnapshot.LatestNonErrorComputed;
         UpdateCount            = 1 + prevSnapshot.UpdateCount;
         ErrorCount             = 1 + prevSnapshot.ErrorCount;
         RetryCount             = 1 + prevSnapshot.RetryCount;
     }
 }
        protected override async ValueTask <IComputed <T> > Compute(
            ComputeMethodInput input, IComputed <T>?existing,
            CancellationToken cancellationToken)
        {
            var tag      = VersionGenerator.Next();
            var computed = CreateComputed(input, tag);

            try {
                using var _ = Computed.ChangeCurrent(computed);
                var result = input.InvokeOriginalFunction(cancellationToken);
                if (input.Method.ReturnsValueTask)
                {
                    var output = await((ValueTask <T>)result).ConfigureAwait(false);
                    computed.TrySetOutput(output);
                }
                else
                {
                    var output = await((Task <T>)result).ConfigureAwait(false);
                    computed.TrySetOutput(output);
                }
            }
            catch (OperationCanceledException e) {
                computed.TrySetOutput(Result.Error <T>(e));
                computed.Invalidate();
                throw;
            }
            catch (Exception e) {
                if (e is AggregateException ae)
                {
                    e = ae.GetFirstInnerException();
                }
                computed.TrySetOutput(Result.Error <T>(e));
                // Weird case: if the output is already set, all we can
                // is to ignore the exception we've just caught;
                // throwing it further will probably make it just worse,
                // since the the caller have to take this scenario into acc.
            }
            return(computed);
        }
Beispiel #14
0
        public static void Invalidate(this IComputed computed, TimeSpan delay, bool?usePreciseTimer = null)
        {
            if (delay <= TimeSpan.Zero)
            {
                computed.Invalidate();
                return;
            }

            var bPrecise = usePreciseTimer ?? delay <= TimeSpan.FromSeconds(1);

            if (!bPrecise)
            {
                Timeouts.Invalidate.AddOrUpdateToEarlier(computed, Timeouts.Clock.Now + delay);
                computed.Invalidated += c => Timeouts.Invalidate.Remove(c);
                return;
            }

            var cts = new CancellationTokenSource(delay);

            computed.Invalidated += _ => {
                try {
                    if (!cts.IsCancellationRequested)
                    {
                        cts.Cancel(true);
                    }
                } catch {
                    // Intended: this method should never throw any exceptions
                }
            };
            cts.Token.Register(() => {
                // No need to schedule this via Task.Run, since this code is
                // either invoked from Invalidate method (via Invalidated handler),
                // so Invalidate() call will do nothing & return immediately,
                // or it's invoked via one of timer threads, i.e. where it's
                // totally fine to invoke Invalidate directly as well.
                computed.Invalidate();
                cts.Dispose();
            });
        }
Beispiel #15
0
        public void Store(IComputed value)
        {
            if (!value.IsConsistent) // It could be invalidated on the way here :)
            {
                return;
            }
            var key    = value.Input;
            var random = Randomize(key.HashCode);

            OnOperation(random);
            _storage.AddOrUpdate(
                key,
                (key1, s) => new Entry(s.Value, s.This._gcHandlePool.Acquire(s.Value, s.Random)),
                (key1, entry, s) => {
                // Not sure how we can reach this point,
                // but if we are here somehow, let's reuse the handle.
                var handle    = entry.Handle;
                handle.Target = s.Value;
                return(new Entry(s.Value, handle));
            },
                (This: this, Value: value, Random: random));
        }
Beispiel #16
0
        public virtual IPublication Publish(IComputed computed)
        {
            ThrowIfDisposedOrDisposing();
            var spinWait = new SpinWait();

            while (true)
            {
                var p = Publications.GetOrAddChecked(
                    computed.Input,
                    (key, arg) => {
                    var(this1, computed1) = arg;
                    var id = this1.PublicationIdGenerator.Next();
                    var p1 = this1.PublicationFactory.Create(this1.PublicationGeneric, this1, computed1, id, Clock);
                    this1.PublicationsById[id] = p1;
                    p1.RunAsync();
                    return(p1);
                }, (this, computed));
                if (p.Touch())
                {
                    return(p);
                }
                spinWait.SpinOnce();
            }
        }
Beispiel #17
0
 internal static T Strip <T>(this IComputed <T>?computed)
 => computed != null ? computed.Value : default !;
Beispiel #18
0
 private void OnInvalidated(IComputed _)
 {
     _taskSource.TrySetResult(default);
Beispiel #19
0
    // Protected & private

    protected abstract ValueTask <IComputed <TOut> > Compute(
        TIn input, IComputed <TOut>?existing, CancellationToken cancellationToken);
Beispiel #20
0
 internal static T Strip <T>(this IComputed <T>?computed, ComputeContext context)
 {
     if (computed == null)
     {
         return(default !);
Beispiel #21
0
 internal static void UseNew <T>(this IComputed <T> computed, ComputeContext context, IComputed?usedBy)
 {
     ((IComputedImpl?)usedBy)?.AddUsed((IComputedImpl)computed);
     ((IComputedImpl?)computed)?.RenewTimeouts();
 }
Beispiel #22
0
    protected override async ValueTask <IComputed <T> > Compute(
        ComputeMethodInput input, IComputed <T>?existing,
        CancellationToken cancellationToken)
    {
        var                       method = input.Method;
        IReplica <T>              replica;
        IReplicaComputed <T>      replicaComputed;
        ReplicaMethodComputed <T> result;

        // 1. Trying to update the Replica first
        if (existing is IReplicaMethodComputed <T> rsc && rsc.Replica != null)
        {
            try {
                replica         = rsc.Replica;
                replicaComputed = (IReplicaComputed <T>)
                                  await replica.Computed.Update(cancellationToken).ConfigureAwait(false);

                result = new (method.Options, input, replicaComputed);
                ComputeContext.Current.TryCapture(result);
                return(result);
            }
            catch (OperationCanceledException) {
                throw;
            }
            catch (Exception e) {
                DebugLog?.LogError(e, "ComputeAsync: error on Replica update");
            }
        }

        // 2. Replica update failed, let's refresh it
        Result <T>           output;
        PublicationStateInfo?psi;

        using (var psiCapture = new PublicationStateInfoCapture()) {
            try {
                var rpcResult = input.InvokeOriginalFunction(cancellationToken);
                if (method.ReturnsValueTask)
                {
                    var task = (ValueTask <T>)rpcResult;
                    output = Result.Value(await task.ConfigureAwait(false));
                }
                else
                {
                    var task = (Task <T>)rpcResult;
                    output = Result.Value(await task.ConfigureAwait(false));
                }
            }
            catch (OperationCanceledException) {
                throw;
            }
            catch (Exception e) {
                DebugLog?.LogError(e, "ComputeAsync: error on update");
                if (e is AggregateException ae)
                {
                    e = ae.GetFirstInnerException();
                }
                output = Result.Error <T>(e);
            }
            psi = psiCapture.Captured;
        }

        if (psi == null)
        {
            output = new Result <T>(default !, Errors.NoPublicationStateInfo());
Beispiel #23
0
 internal static Task <T> StripToTask <T>(this IComputed <T>?computed)
 => computed?.Output.AsTask() ?? Task.FromResult(default(T) !);
Beispiel #24
0
 protected void Unregister(IComputed <TIn, TOut> computed)
 => ComputedRegistry.Remove(computed);
        protected override async ValueTask <IComputed <T> > ComputeAsync(
            InterceptedInput input, IComputed <T>?cached,
            CancellationToken cancellationToken)
        {
            var method = input.Method;

            // 1. Trying to update the Replica first
            if (cached is IReplicaServiceComputed <T> rsc && rsc.Replica != null)
            {
                try {
                    var replica  = rsc.Replica;
                    var computed = await replica.Computed
                                   .UpdateAsync(true, cancellationToken).ConfigureAwait(false);

                    var replicaComputed = (IReplicaComputed <T>)computed;
                    var output          = new ReplicaServiceComputed <T>(
                        method.Options, replicaComputed, input);
                    return(output);
                }
                catch (OperationCanceledException) {
                    if (_isLogDebugEnabled)
                    {
                        _log.LogDebug($"{nameof(ComputeAsync)}: Cancelled (1).");
                    }
                    throw;
                }
                catch (Exception e) {
                    if (_isLogDebugEnabled)
                    {
                        _log.LogError(e, $"{nameof(ComputeAsync)}: Error on Replica update.");
                    }
                }
            }

            // 2. Replica update failed, let's refresh it
            try {
                using var replicaCapture = new ReplicaCapture();
                var result = input.InvokeOriginalFunction(cancellationToken);
                if (method.ReturnsComputed)
                {
                    if (method.ReturnsValueTask)
                    {
                        var task = (ValueTask <IComputed <T> >)result;
                        await task.ConfigureAwait(false);
                    }
                    else
                    {
                        var task = (Task <IComputed <T> >)result;
                        await task.ConfigureAwait(false);
                    }
                }
                else
                {
                    if (method.ReturnsValueTask)
                    {
                        var task = (ValueTask <T>)result;
                        await task.ConfigureAwait(false);
                    }
                    else
                    {
                        var task = (Task <T>)result;
                        await task.ConfigureAwait(false);
                    }
                }
                var replica  = replicaCapture.GetCapturedReplica <T>();
                var computed = await replica.Computed
                               .UpdateAsync(true, cancellationToken).ConfigureAwait(false);

                var replicaComputed = (IReplicaComputed <T>)computed;
                var output          = new ReplicaServiceComputed <T>(
                    method.Options, replicaComputed, input);
                return(output);
            }
            catch (OperationCanceledException) {
                if (_isLogDebugEnabled)
                {
                    _log.LogDebug($"{nameof(ComputeAsync)}: Cancelled (2).");
                }
                throw;
            }
            catch (Exception e) {
                if (_isLogDebugEnabled)
                {
                    _log.LogError(e, $"{nameof(ComputeAsync)}: Error on update.");
                }
                // We need a unique LTag here, so we use a range that's supposed
                // to be unused by LTagGenerators.
                var lTag   = new LTag(LTagGenerator.Next().Value ^ (1L << 62));
                var output = new ReplicaServiceComputed <T>(
                    method.Options, null, input, new Result <T>(default !, e), lTag);
Beispiel #26
0
        // Protected & private

        protected abstract ValueTask <IComputed <TOut> > ComputeAsync(
            TIn input, IComputed <TOut>?cached, CancellationToken cancellationToken);
        protected override async ValueTask <IComputed <T> > ComputeAsync(
            InterceptedInput input, IComputed <T>?existing,
            CancellationToken cancellationToken)
        {
            var                  method = input.Method;
            IReplica <T>         replica;
            IReplicaComputed <T> replicaComputed;

            // 1. Trying to update the Replica first
            if (existing is IReplicaClientComputed <T> rsc && rsc.Replica != null)
            {
                try {
                    replica         = rsc.Replica;
                    replicaComputed = (IReplicaComputed <T>) await replica.Computed
                                      .UpdateAsync(true, cancellationToken).ConfigureAwait(false);

                    return(new ReplicaClientComputed <T>(method.Options, input, replicaComputed));
                }
                catch (OperationCanceledException) {
                    if (IsLogDebugEnabled)
                    {
                        Log.LogDebug($"{nameof(ComputeAsync)}: Cancelled (1).");
                    }
                    throw;
                }
                catch (Exception e) {
                    if (IsLogDebugEnabled)
                    {
                        Log.LogError(e, $"{nameof(ComputeAsync)}: Error on Replica update.");
                    }
                }
            }

            // 2. Replica update failed, let's refresh it
            using var psiCapture = new PublicationStateInfoCapture();
            Result <T> output;

            try {
                var result = input.InvokeOriginalFunction(cancellationToken);
                if (method.ReturnsValueTask)
                {
                    var task = (ValueTask <T>)result;
                    output = Result.Value(await task.ConfigureAwait(false));
                }
                else
                {
                    var task = (Task <T>)result;
                    output = Result.Value(await task.ConfigureAwait(false));
                }
            }
            catch (OperationCanceledException) {
                if (IsLogDebugEnabled)
                {
                    Log.LogDebug($"{nameof(ComputeAsync)}: Cancelled (2).");
                }
                throw;
            }
            catch (Exception e) {
                if (IsLogDebugEnabled)
                {
                    Log.LogError(e, $"{nameof(ComputeAsync)}: Error on update.");
                }
                if (e is AggregateException ae)
                {
                    e = ae.GetFirstInnerException();
                }
                output = Result.Error <T>(e);
            }

            var psi = psiCapture.Captured;

            if (psi == null)
            {
                output = new Result <T>(default !, Errors.NoPublicationStateInfoCaptured());
Beispiel #28
0
 public IPublication Create(Type genericType,
                            IPublisher publisher, IComputed computed,
                            Symbol publicationId, IMomentClock clock)
 => ConstructorCache
 .GetOrAddChecked(genericType, CreateCache)
 .Invoke(publisher, computed, publicationId, clock);
Beispiel #29
0
 protected void Register(IComputed <TIn, TOut> computed)
 => ComputedRegistry.Store(computed);