async Task <Try <IEnumerable <Task> > > TryStartFilter <TClientMessage, TConnectRequest, TResponse, TFilterDefinition>( IReverseCallDispatcher <TClientMessage, FilterRuntimeToClientMessage, TConnectRequest, FilterRegistrationResponse, FilterEventRequest, TResponse> dispatcher, StreamProcessor streamProcessor, ScopeId scopeId, TFilterDefinition filterDefinition, Func <TenantId, IFilterProcessor <TFilterDefinition> > getFilterProcessor, CancellationToken cancellationToken) where TClientMessage : IMessage, new() where TConnectRequest : class where TResponse : class where TFilterDefinition : IFilterDefinition { _logger.StartingFilter(filterDefinition.TargetStream); try { var runningDispatcher = dispatcher.Accept(new FilterRegistrationResponse(), cancellationToken); await streamProcessor.Initialize().ConfigureAwait(false); await ValidateFilter( scopeId, filterDefinition, getFilterProcessor, cancellationToken).ConfigureAwait(false); return(new[] { streamProcessor.Start(), runningDispatcher }); } catch (Exception ex) { if (!cancellationToken.IsCancellationRequested) { _logger.ErrorWhileStartingFilter(ex, filterDefinition.TargetStream, scopeId); } return(ex); } }
async Task <bool> RejectIfInvalidDefinition( EmbeddingDefinition definition, IReverseCallDispatcher <EmbeddingClientToRuntimeMessage, EmbeddingRuntimeToClientMessage, EmbeddingRegistrationRequest, EmbeddingRegistrationResponse, EmbeddingRequest, EmbeddingResponse> dispatcher, CancellationToken cancellationToken) { _logger.ComparingEmbeddingDefinition(definition); var tenantsAndComparisonResult = await _embeddingDefinitionComparer.DiffersFromPersisted( new EmbeddingDefinition( definition.Embedding, definition.Events, definition.InititalState), cancellationToken).ConfigureAwait(false); if (tenantsAndComparisonResult.Values.All(_ => _.Succeeded)) { return(false); } var unsuccessfulComparisons = tenantsAndComparisonResult .Where(_ => !_.Value.Succeeded) .Select(_ => (_.Key, _.Value)); _logger.InvalidEmbeddingDefinition(definition, unsuccessfulComparisons); await dispatcher.Reject(CreateInvalidValidationResponse( unsuccessfulComparisons, definition.Embedding), cancellationToken).ConfigureAwait(false); return(true); }
async Task WriteEventsToEventHorizon( IReverseCallDispatcher <EventHorizonConsumerToProducerMessage, EventHorizonProducerToConsumerMessage, ConsumerSubscriptionRequest, Contracts.SubscriptionResponse, ConsumerRequest, ConsumerResponse> dispatcher, TenantId producerTenant, StreamId publicStream, PartitionId partition, StreamPosition streamPosition, CancellationToken cancellationToken) { try { _executionContextManager.CurrentFor( _thisMicroservice, producerTenant, _executionContextManager.Current.CorrelationId); var publicEvents = await _getEventFetchers().GetPartitionedFetcherFor( ScopeId.Default, new StreamDefinition(new PublicFilterDefinition(StreamId.EventLog, publicStream)), cancellationToken).ConfigureAwait(false); while (!cancellationToken.IsCancellationRequested && !_disposed) { try { var tryGetStreamEvent = await publicEvents.FetchInPartition(partition, streamPosition, cancellationToken).ConfigureAwait(false); if (!tryGetStreamEvent.Success) { await Task.Delay(250).ConfigureAwait(false); continue; } var streamEvent = tryGetStreamEvent.Result; var response = await dispatcher.Call( new ConsumerRequest { Event = streamEvent.ToEventHorizonEvent() }, cancellationToken).ConfigureAwait(false); if (response.Failure != null) { _logger.Warning( "An error occurred while handling request. FailureId: {FailureId} Reason: {Reason}", response.Failure.Id, response.Failure.Reason); return; } streamPosition = streamEvent.Position + 1; } catch (EventStoreUnavailable) { await Task.Delay(1000).ConfigureAwait(false); } } } catch (Exception ex) { _logger.Warning(ex, "An error ocurred while writing events to event horizon"); } }
/// <summary> /// Initializes a new instance of the <see cref="PublicFilterProcessor"/> class. /// </summary> /// <param name="definition">The <see cref="PublicFilterDefinition"/>.</param> /// <param name="dispatcher"><see cref="IReverseCallDispatcher{TClientMessage, TServerMessage, TConnectArguments, TConnectResponse, TRequest, TResponse}"/>.</param> /// <param name="eventsToPublicStreamsWriter">The <see cref="IWriteEventsToStreams">writer</see> for writing events.</param> /// <param name="logger"><see cref="ILogger"/> for logging.</param> public PublicFilterProcessor( PublicFilterDefinition definition, IReverseCallDispatcher <PublicFilterClientToRuntimeMessage, FilterRuntimeToClientMessage, PublicFilterRegistrationRequest, FilterRegistrationResponse, FilterEventRequest, PartitionedFilterResponse> dispatcher, IWriteEventsToPublicStreams eventsToPublicStreamsWriter, ILogger logger) : base(ScopeId.Default, definition, eventsToPublicStreamsWriter, logger) { _dispatcher = dispatcher; }
Task WriteFailedRegistrationResponse <TClientMessage, TConnectRequest, TResponse>( IReverseCallDispatcher <TClientMessage, FilterRuntimeToClientMessage, TConnectRequest, FilterRegistrationResponse, FilterEventRequest, TResponse> dispatcher, Failure failure, CancellationToken cancellationToken) where TClientMessage : IMessage, new() where TConnectRequest : class where TResponse : class => dispatcher.Reject(new FilterRegistrationResponse { Failure = failure }, cancellationToken);
/// <summary> /// Initializes a new instance of the <see cref="Projection"/> class. /// </summary> /// <param name="dispatcher">The <see cref="IReverseCallDispatcher{TClientMessage, TServerMessage, TConnectArguments, TConnectResponse, TRequest, TResponse}"/> dispatcher to use to send requests to the head.</param> /// <param name="projectionDefinition">The <see cref="ProjectionDefinition" />.</param> /// <param name="alias">The alias of the Projection (if provided) from the Client.</param> /// <param name="hasAlias">A value indicating whether an alias was provided by the Client.</param> public Projection( IReverseCallDispatcher <ProjectionClientToRuntimeMessage, ProjectionRuntimeToClientMessage, ProjectionRegistrationRequest, ProjectionRegistrationResponse, ProjectionRequest, ProjectionResponse> dispatcher, ProjectionDefinition projectionDefinition, ProjectionAlias alias, bool hasAlias) { Definition = projectionDefinition; _dispatcher = dispatcher; Alias = alias; HasAlias = hasAlias; }
/// <summary> /// Initializes a new instance of the <see cref="FilterProcessor"/> class. /// </summary> /// <param name="scope">The <see cref="ScopeId" />.</param> /// <param name="definition">The <see cref="FilterDefinition"/>.</param> /// <param name="dispatcher">The <see cref="IReverseCallDispatcher{TClientMessage, TServerMessage, TConnectArguments, TConnectResponse, TRequest, TResponse}" />.</param> /// <param name="eventsToStreamsWriter">The <see cref="IWriteEventsToStreams">writer</see> for writing events.</param> /// <param name="logger"><see cref="ILogger"/> for logging.</param> public FilterProcessor( ScopeId scope, FilterDefinition definition, IReverseCallDispatcher <FilterClientToRuntimeMessage, FilterRuntimeToClientMessage, FilterRegistrationRequest, FilterRegistrationResponse, FilterEventRequest, FilterResponse> dispatcher, IWriteEventsToStreams eventsToStreamsWriter, ILogger <FilterProcessor> logger) : base(scope, definition, eventsToStreamsWriter, logger) { _dispatcher = dispatcher; _logger = logger; }
/// <summary> /// Initializes a new instance of the <see cref="EventProcessor"/> class. /// </summary> /// <param name="scope">The <see cref="ScopeId" />.</param> /// <param name="id">The <see cref="EventProcessorId" />.</param> /// <param name="dispatcher"><see cref="IReverseCallDispatcher{TClientMessage, TServerMessage, TConnectArguments, TConnectResponse, TRequest, TResponse}"/> dispatcher.</param> /// <param name="logger">The <see cref="ILogger" />.</param> public EventProcessor( ScopeId scope, EventProcessorId id, IReverseCallDispatcher <EventHandlerClientToRuntimeMessage, EventHandlerRuntimeToClientMessage, EventHandlerRegistrationRequest, EventHandlerRegistrationResponse, HandleEventRequest, EventHandlerResponse> dispatcher, ILogger logger) { Scope = scope; Identifier = id; _dispatcher = dispatcher; _logger = logger; _logMessagePrefix = $"Event Processor '{Identifier}'"; }
Task <Try <ExecutionContext> > CreateExecutionContextOrReject <TClientMessage, TConnectRequest, TResponse>( IReverseCallDispatcher <TClientMessage, FilterRuntimeToClientMessage, TConnectRequest, FilterRegistrationResponse, FilterEventRequest, TResponse> dispatcher, ExecutionContext requestExecutionContext, CancellationToken cancellationToken) where TClientMessage : IMessage, new() where TConnectRequest : class where TResponse : class => _executionContextCreator .TryCreateUsing(requestExecutionContext) .Catch(async exception => { _logger.ExecutionContextIsNotValid(exception); var failure = new Failure(FiltersFailures.CannotRegisterFilterOnNonWriteableStream, $"Execution context is invalid: {exception.Message}"); await WriteFailedRegistrationResponse(dispatcher, failure, cancellationToken).ConfigureAwait(false); });
async Task <bool> RejectIfInvalidFilterId <TClientMessage, TConnectRequest, TResponse>( IReverseCallDispatcher <TClientMessage, FilterRuntimeToClientMessage, TConnectRequest, FilterRegistrationResponse, FilterEventRequest, TResponse> dispatcher, StreamId filterId, CancellationToken cancellationToken) where TClientMessage : IMessage, new() where TConnectRequest : class where TResponse : class { if (filterId.IsNonWriteable) { _logger.Warning("Filter: '{Filter}' is an invalid Stream Id", filterId); var failure = new Failure(FiltersFailures.CannotRegisterFilterOnNonWriteableStream, $"Filter Id: '{filterId}' is an invalid Stream Id"); await WriteFailedRegistrationResponse(dispatcher, failure, cancellationToken).ConfigureAwait(false); return(true); } return(false); }
async Task <Try <IEnumerable <Task> > > TryStartEventHandler <TClientMessage, TConnectRequest, TResponse, TFilterDefinition>( IReverseCallDispatcher <TClientMessage, EventHandlerRuntimeToClientMessage, TConnectRequest, EventHandlerRegistrationResponse, HandleEventRequest, TResponse> dispatcher, StreamProcessor filterStreamProcessor, StreamProcessor eventProcessorStreamProcessor, ScopeId scopeId, TFilterDefinition filterDefinition, Func <IFilterProcessor <TFilterDefinition> > getFilterProcessor, CancellationToken cancellationToken) where TClientMessage : IMessage, new() where TConnectRequest : class where TResponse : class where TFilterDefinition : IFilterDefinition { _logger.Debug("Starting Event Handler '{EventHandlerId}'", filterDefinition.TargetStream); try { var runningDispatcher = dispatcher.Accept(new EventHandlerRegistrationResponse(), cancellationToken); await filterStreamProcessor.Initialize().ConfigureAwait(false); await eventProcessorStreamProcessor.Initialize().ConfigureAwait(false); await ValidateFilter( scopeId, filterDefinition, getFilterProcessor, cancellationToken).ConfigureAwait(false); return(new[] { filterStreamProcessor.Start(), eventProcessorStreamProcessor.Start(), runningDispatcher }); } catch (Exception ex) { if (!cancellationToken.IsCancellationRequested) { _logger.Warning( ex, "Error occurred while trying to start Event Handler '{EventHandlerId}'", filterDefinition.TargetStream); } return(ex); } }
async Task <bool> RejectIfNotReceivedArguments <TClientMessage, TConnectRequest, TResponse>( IReverseCallDispatcher <TClientMessage, FilterRuntimeToClientMessage, TConnectRequest, FilterRegistrationResponse, FilterEventRequest, TResponse> dispatcher, CancellationToken cancellationToken) where TClientMessage : IMessage, new() where TConnectRequest : class where TResponse : class { _logger.Trace("Waiting for connection arguments..."); if (!await dispatcher.ReceiveArguments(cancellationToken).ConfigureAwait(false)) { const string message = "Connection arguments were not received"; _logger.Warning(message); var failure = new Failure(FiltersFailures.NoFilterRegistrationReceived, message); await WriteFailedRegistrationResponse(dispatcher, failure, cancellationToken).ConfigureAwait(false); return(true); } _logger.Trace("Received connection arguments"); return(false); }
/// <summary> /// Initializes a new instance of the <see cref="EventHorizon"/> class. /// </summary> /// <param name="id">The <see cref="EventHorizonId"/>.</param> /// <param name="consent">The <see cref="ConsentId"/>.</param> /// <param name="currentPosition">The initial current <see cref="StreamPosition"/> of the event horizon.</param> /// <param name="dispatcher">The reverse call dispatcher.</param> /// <param name="executionContexts">The <see cref="ICreateExecutionContexts"/>.</param> /// <param name="getEventFetchers">The <see cref="Func{TResult}"/> callback for getting <see cref="IEventFetchers"/> in a tenant container.</param> /// <param name="getStreamWaiter">The <see cref="Func{TResult}"/> callback for getting <see cref="IStreamEventWatcher"/> in a tenant container</param> /// <param name="metrics">The <see cref="IMetricsCollector"/>.</param> /// <param name="logger">The <see cref="ILogger"/>.</param> /// <param name="cancellationToken">The cancellation token.</param> public EventHorizon( EventHorizonId id, ConsentId consent, StreamPosition currentPosition, IReverseCallDispatcher <EventHorizonConsumerToProducerMessage, EventHorizonProducerToConsumerMessage, ConsumerSubscriptionRequest, SubscriptionResponse, ConsumerRequest, ConsumerResponse> dispatcher, ICreateExecutionContexts executionContexts, Func <TenantId, IEventFetchers> getEventFetchers, Func <TenantId, IStreamEventWatcher> getStreamWaiter, IMetricsCollector metrics, ILogger logger, CancellationToken cancellationToken) { Consent = consent; _dispatcher = dispatcher; _executionContexts = executionContexts; _getEventFetchers = getEventFetchers; _getStreamWaiter = getStreamWaiter; _metrics = metrics; _logger = logger; Id = id; CurrentPosition = currentPosition; _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); }
Task WriteFailedRegistrationResponse( IReverseCallDispatcher <EventHandlerClientToRuntimeMessage, EventHandlerRuntimeToClientMessage, EventHandlerRegistrationRequest, EventHandlerRegistrationResponse, HandleEventRequest, EventHandlerResponse> dispatcher, Failure failure, CancellationToken cancellationToken) => dispatcher.Reject(new EventHandlerRegistrationResponse { Failure = failure }, cancellationToken);
async Task RegisterFilter <TFilterDefinition, TClientMessage, TConnectRequest, TResponse>( IReverseCallDispatcher <TClientMessage, FilterRuntimeToClientMessage, TConnectRequest, FilterRegistrationResponse, FilterEventRequest, TResponse> dispatcher, ScopeId scopeId, TFilterDefinition filterDefinition, Func <IFilterProcessor <TFilterDefinition> > getFilterProcessor, CancellationToken externalCancellationToken) where TFilterDefinition : IFilterDefinition where TClientMessage : IMessage, new() where TConnectRequest : class where TResponse : class { _logger.Trace("Received Source Stream '{SourceStream}'", filterDefinition.SourceStream); _logger.Trace("Received Filter '{Filter}'", filterDefinition.TargetStream); _logger.Trace("Received Scope '{Scope}'", scopeId); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(externalCancellationToken); var cancellationToken = linkedTokenSource.Token; _logger.Debug("Connecting Filter '{Filter}'", filterDefinition.TargetStream); var tryRegisterFilter = TryRegisterStreamProcessor(scopeId, filterDefinition, getFilterProcessor, cancellationToken); if (!tryRegisterFilter.Success) { linkedTokenSource.Cancel(); if (tryRegisterFilter.HasException) { var exception = tryRegisterFilter.Exception; _logger.Warning(exception, "An error occurred while registering Filter '{Filter}'", filterDefinition.TargetStream); ExceptionDispatchInfo.Capture(exception).Throw(); } else { _logger.Warning("Failed to register Filter '{Filter}'. Filter already registered", filterDefinition.TargetStream); var failure = new Failure( FiltersFailures.FailedToRegisterFilter, $"Failed to register Filter '{filterDefinition.TargetStream}'. Filter already registered."); await WriteFailedRegistrationResponse(dispatcher, failure, externalCancellationToken).ConfigureAwait(false); return; } } using var filterStreamProcessor = tryRegisterFilter.Result; var tryStartFilter = await TryStartFilter( dispatcher, filterStreamProcessor, scopeId, filterDefinition, getFilterProcessor, cancellationToken).ConfigureAwait(false); if (!tryStartFilter.Success) { linkedTokenSource.Cancel(); if (tryStartFilter.HasException) { var exception = tryStartFilter.Exception; _logger.Warning(exception, "An error occurred while starting Filter '{Filter}' in Scope '{Scope}'", filterDefinition.TargetStream, scopeId); ExceptionDispatchInfo.Capture(exception).Throw(); } else { _logger.Warning("Could not start Filter '{Filter}' in Scope '{Scope}'", filterDefinition.TargetStream, scopeId); return; } } var tasks = tryStartFilter.Result; try { await Task.WhenAny(tasks).ConfigureAwait(false); if (TryGetException(tasks, out var ex)) { _logger.Warning(ex, "An error occurred while running Filter '{Filter}' in Scope '{Scope}'", filterDefinition.TargetStream, scopeId); await Task.WhenAll(tasks).ConfigureAwait(false); ExceptionDispatchInfo.Capture(ex).Throw(); } } finally { linkedTokenSource.Cancel(); await Task.WhenAll(tasks).ConfigureAwait(false); _logger.Debug("Filter '{Filter}' in Scope '{Scope}' stopped", filterDefinition.TargetStream, scopeId); } }
async Task RegisterFilter <TFilterDefinition, TClientMessage, TConnectRequest, TResponse>( IReverseCallDispatcher <TClientMessage, FilterRuntimeToClientMessage, TConnectRequest, FilterRegistrationResponse, FilterEventRequest, TResponse> dispatcher, ScopeId scopeId, TFilterDefinition filterDefinition, Func <TenantId, IFilterProcessor <TFilterDefinition> > getFilterProcessor, ExecutionContext executionContext, CancellationToken externalCancellationToken) where TFilterDefinition : IFilterDefinition where TClientMessage : IMessage, new() where TConnectRequest : class where TResponse : class { _logger.ReceivedFilter(filterDefinition.SourceStream, filterDefinition.TargetStream, scopeId); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(externalCancellationToken); var cancellationToken = linkedTokenSource.Token; _logger.ConnectingFilter(filterDefinition.TargetStream); var tryRegisterFilter = TryRegisterStreamProcessor(scopeId, filterDefinition, getFilterProcessor, executionContext, cancellationToken); if (!tryRegisterFilter.Success) { linkedTokenSource.Cancel(); if (tryRegisterFilter.Exception is StreamProcessorAlreadyRegistered) { _logger.FilterAlreadyRegistered(filterDefinition.TargetStream); var failure = new Failure( FiltersFailures.FailedToRegisterFilter, $"Failed to register Filter '{filterDefinition.TargetStream}'. Filter already registered."); await WriteFailedRegistrationResponse(dispatcher, failure, externalCancellationToken).ConfigureAwait(false); return; } else { var exception = tryRegisterFilter.Exception; _logger.ErrorWhileRegisteringFilter(exception, filterDefinition.TargetStream); ExceptionDispatchInfo.Capture(exception).Throw(); } } using var filterStreamProcessor = tryRegisterFilter.Result; var tryStartFilter = await TryStartFilter( dispatcher, filterStreamProcessor, scopeId, filterDefinition, getFilterProcessor, cancellationToken).ConfigureAwait(false); if (!tryStartFilter.Success) { linkedTokenSource.Cancel(); var exception = tryStartFilter.Exception; _logger.ErrorWhileStartingFilter(exception, filterDefinition.TargetStream, scopeId); ExceptionDispatchInfo.Capture(exception).Throw(); } var tasks = new TaskGroup(tryStartFilter.Result); tasks.OnFirstTaskFailure += (_, ex) => _logger.ErrorWhileRunningFilter(ex, filterDefinition.TargetStream, scopeId); tasks.OnAllTasksCompleted += () => _logger.FilterStopped(filterDefinition.TargetStream, scopeId); await tasks.WaitForAllCancellingOnFirst(linkedTokenSource).ConfigureAwait(false); }