protected override async Task WakeUpAsync(CancellationToken cancellationToken) { var minCommitTime = (MaxKnownCommitTime - MaxCommitDuration).ToDateTime(); // Fetching potentially new operations var operations = await DbOperationLog .ListNewlyCommittedAsync(minCommitTime, cancellationToken) .ConfigureAwait(false); // var secondsAgo = (Clock.Now.ToDateTime() - minCommitTime).TotalSeconds; // Log.LogInformation("({Ago:F2}s ago ... now): {OpCount} operations", // secondsAgo, operations.Count); // Processing them foreach (var operation in operations) { OperationCompletionNotifier.NotifyCompleted(operation); var commitTime = operation.CommitTime.ToMoment(); if (MaxKnownCommitTime < commitTime) { // This update should happen even for locally executed operations, // i.e. when NotifyCompleted(...) returns false! MaxKnownCommitTime = commitTime; } } }
public async Task OnCommand(ICommand command, CommandContext context, CancellationToken cancellationToken) { var operationRequired = context.OuterContext == null && // Should be a top-level command !(command is IMetaCommand) && // No operations for meta commands !Computed.IsInvalidating(); if (!operationRequired) { await context.InvokeRemainingHandlers(cancellationToken).ConfigureAwait(false); return; } var scope = Services.GetRequiredService <TransientOperationScope>(); await using var _ = scope.ConfigureAwait(false); var operation = scope.Operation; operation.Command = command; context.Items.Set(scope); context.SetOperation(operation); try { await context.InvokeRemainingHandlers(cancellationToken).ConfigureAwait(false); await scope.Commit(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } catch (Exception e) { if (scope.IsUsed) { Log.LogError(e, "Operation failed: {Command}", command); } await scope.Rollback().ConfigureAwait(false); throw; } // Since this is the outermost scope handler, it's reasonable to // call OperationCompletionNotifier.NotifyCompleted from it var actualOperation = context.Items.GetOrDefault <IOperation>(operation); await OperationCompletionNotifier.NotifyCompleted(actualOperation).ConfigureAwait(false); }
protected override async Task WakeUp(CancellationToken cancellationToken) { var minCommitTime = (MaxKnownCommitTime - MaxCommitDuration).ToDateTime(); // Fetching potentially new operations var operations = await DbOperationLog .ListNewlyCommitted(minCommitTime, BatchSize, cancellationToken) .ConfigureAwait(false); // Processing them var tasks = new Task[operations.Count]; for (var i = 0; i < operations.Count; i++) { var operation = operations[i]; var isLocal = operation.AgentId == AgentInfo.Id.Value; // Local completions are invoked by TransientOperationScopeProvider // _inside_ the command processing pipeline. Trying to trigger them here // means a tiny chance of running them _outside_ of command processing // pipeline, which makes it possible to see command completing // prior to its invalidation logic completion. tasks[i] = isLocal ? Task.CompletedTask // Skips local operation! : OperationCompletionNotifier.NotifyCompleted(operation); var commitTime = operation.CommitTime.ToMoment(); if (MaxKnownCommitTime < commitTime) { MaxKnownCommitTime = commitTime; } } // Let's wait when all of these tasks complete, otherwise // we might end up creating too many tasks await Task.WhenAll(tasks).ConfigureAwait(false); LastCount = operations.Count; }
protected override async Task WakeAsync(CancellationToken cancellationToken) { var minCommitTime = (MaxKnownCommitTime - MaxCommitDuration).ToDateTime(); // Fetching potentially new operations await using var dbContext = CreateDbContext(); var operations = await DbOperationLog .ListNewlyCommittedAsync(dbContext, minCommitTime, cancellationToken) .ConfigureAwait(false); // Processing them foreach (var operation in operations) { if (!OperationCompletionNotifier.NotifyCompleted(operation)) { continue; // We saw this operation already } var commitTime = operation.CommitTime.ToMoment(); if (MaxKnownCommitTime < commitTime) { MaxKnownCommitTime = commitTime; } } }
public async Task OnCommandAsync(ICommand command, CommandContext context, CancellationToken cancellationToken) { var operationRequired = context.OuterContext == null && // Should be top-level command !(command is IMetaCommand) && // No operations for "second-order" commands !Computed.IsInvalidating(); if (!operationRequired) { await context.InvokeRemainingHandlersAsync(cancellationToken).ConfigureAwait(false); return; } var tScope = typeof(DbOperationScope <TDbContext>); if (context.Items[tScope] != null) // Safety check { throw Stl.Internal.Errors.InternalError($"'{tScope}' scope is already provided. Duplicate handler?"); } await using var scope = Services.GetRequiredService <DbOperationScope <TDbContext> >(); var operation = scope.Operation; operation.Command = command; context.Items.Set(scope); var logEnabled = LogLevel != LogLevel.None && Log.IsEnabled(LogLevel); try { await context.InvokeRemainingHandlersAsync(cancellationToken).ConfigureAwait(false); await scope.CommitAsync(cancellationToken); } catch (OperationCanceledException) { throw; } catch (Exception e) { if (scope.IsUsed) { Log.LogError(e, "Operation failed: {Command}", command); } try { await scope.RollbackAsync(); } catch { // Intended } throw; } if (scope.IsUsed) { if (logEnabled) { Log.Log(LogLevel, "Operation succeeded: {Command}", command); } var completion = Completion.New(operation); context.Items.Set(completion); OperationCompletionNotifier.NotifyCompleted(operation); try { await context.Commander.CallAsync(completion, true, default).ConfigureAwait(false); } catch (Exception e) { Log.LogError(e, "Local operation completion failed! Command: {Command}", command); // No throw: the operation itself succeeded } } }
public async Task OnCommandAsync(ICommand command, CommandContext context, CancellationToken cancellationToken) { var operationRequired = context.OuterContext == null && // Should be top-level command !(command is IMetaCommand) && // No operations for "second-order" commands !Computed.IsInvalidating(); if (!operationRequired) { await context.InvokeRemainingHandlersAsync(cancellationToken).ConfigureAwait(false); return; } await using var scope = Services.GetRequiredService <LocalOperationScope>(); var operation = scope.Operation; operation.Command = command; context.Items.Set(scope); context.SetOperation(operation); var logEnabled = LogLevel != LogLevel.None && Log.IsEnabled(LogLevel); try { await context.InvokeRemainingHandlersAsync(cancellationToken).ConfigureAwait(false); await scope.CommitAsync(cancellationToken); } catch (OperationCanceledException) { throw; } catch (Exception e) { if (scope.IsUsed) { Log.LogError(e, "Operation failed: {Command}", command); } await scope.RollbackAsync(); throw; } // Since this is the outermost scope handler, it's reasonable to run the // ICompletion command right from it for any other scope too. var completion = context.Items.TryGet <ICompletion>(); if (completion == null) // Also means scope.IsUsed == true { if (logEnabled) { Log.Log(LogLevel, "Operation succeeded: {Command}", command); } completion = Completion.New(operation); OperationCompletionNotifier.NotifyCompleted(operation); try { await context.Commander.CallAsync(completion, true, default).ConfigureAwait(false); } catch (Exception e) { Log.LogError(e, "Local operation completion failed! Command: {Command}", command); // No throw: the operation itself succeeded } } }
public async Task OnCommandAsync(ICommand command, CommandContext context, CancellationToken cancellationToken) { var skip = context.OuterContext != null || // Should be top-level command command is IInvalidateCommand || // Second handler here will take care of it Computed.IsInvalidating(); if (skip) { await context.InvokeRemainingHandlersAsync(cancellationToken).ConfigureAwait(false); return; } var tScope = typeof(IDbOperationScope <TDbContext>); if (context.Items[tScope] != null) // Safety check { throw Stl.Internal.Errors.InternalError($"'{tScope}' scope is already provided. Duplicate handler?"); } var logEnabled = LogLevel != LogLevel.None && Log.IsEnabled(LogLevel); await using var scope = Services.GetRequiredService <IDbOperationScope <TDbContext> >(); scope.Command = command; context.Items.Set(scope); if (logEnabled) { Log.Log(LogLevel, "+ Operation started: {0}", command); } IOperation?operation = null; try { await context.InvokeRemainingHandlersAsync(cancellationToken).ConfigureAwait(false); // Building IOperation.Items from CommandContext.Items foreach (var(key, value) in context.Items.Items) { if (value is IOperationItem) { scope.Items = scope.Items.Set(key, value); } } operation = await scope.CommitAsync(cancellationToken); if (logEnabled) { Log.Log(LogLevel, "- Operation succeeded: {0}", command); } } catch (OperationCanceledException) { throw; } catch (Exception e) { Log.LogError(e, "! Operation failed: {0}", command); try { await scope.RollbackAsync(); } catch { // Intended } throw; } if (operation != null) { if (InvalidationInfoProvider?.RequiresInvalidation(command) ?? false) { context.Items.Set(InvalidateCommand.New(command, operation)); } OperationCompletionNotifier?.NotifyCompleted(operation); } }