public StateSnapshot(IComputed <T> computed) { Computed = computed; LastValueComputed = computed; UpdateCount = 0; FailureCount = 0; RetryCount = 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)); }
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); }
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);
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; }
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); }
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(); } }
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);
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); }
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(); }); }
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)); }
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(); } }
internal static T Strip <T>(this IComputed <T>?computed) => computed != null ? computed.Value : default !;
private void OnInvalidated(IComputed _) { _taskSource.TrySetResult(default);
// Protected & private protected abstract ValueTask <IComputed <TOut> > Compute( TIn input, IComputed <TOut>?existing, CancellationToken cancellationToken);
internal static T Strip <T>(this IComputed <T>?computed, ComputeContext context) { if (computed == null) { return(default !);
internal static void UseNew <T>(this IComputed <T> computed, ComputeContext context, IComputed?usedBy) { ((IComputedImpl?)usedBy)?.AddUsed((IComputedImpl)computed); ((IComputedImpl?)computed)?.RenewTimeouts(); }
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());
internal static Task <T> StripToTask <T>(this IComputed <T>?computed) => computed?.Output.AsTask() ?? Task.FromResult(default(T) !);
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);
// 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());
public IPublication Create(Type genericType, IPublisher publisher, IComputed computed, Symbol publicationId, IMomentClock clock) => ConstructorCache .GetOrAddChecked(genericType, CreateCache) .Invoke(publisher, computed, publicationId, clock);
protected void Register(IComputed <TIn, TOut> computed) => ComputedRegistry.Store(computed);