// Not thread safe. public async Task ManageAsync(CancellationToken cancellationToken) { try { while (!cancellationToken.IsCancellationRequested) { ResolutionStreamEvent resolutionStreamEvent; while (!cancellationToken.IsCancellationRequested && _resolutionQueue.TryDequeue(out resolutionStreamEvent)) { var streamEvent = resolutionStreamEvent.StreamEvent; IBusinessEvent businessEvent = null; try { // If unable to resolve event type then subscriber event will still be created but it // will be unresolved. if (_resolver.CanResolve(streamEvent.EventType)) { businessEvent = _resolver.Resolve(streamEvent.EventType, streamEvent.Data); } } catch (Exception ex) { _logger.LogError(ex, "Exception while resolving business event. Subscription event will be created with null business event."); } // Stream events may (and will for subscription streams) have links to original events. // We want the original event as the core event and the subscription event info as secondary. // However, if the event is not a link then we take the event info as both subscription info and original event. var streamId = streamEvent.StreamId; var position = streamEvent.Position; var subscriptionStreamId = streamEvent.StreamId; var subscriptionPosition = streamEvent.Position; if (streamEvent.IsLink) { streamId = streamEvent.Link.StreamId; position = streamEvent.Link.Position; } var subscriberEvent = new SubscriberEvent(resolutionStreamEvent.RegionId, streamId, position, subscriptionStreamId, subscriptionPosition, streamEvent.EventType, businessEvent); // Send to the sorting manager. await _sortingManager.ReceiveSubscriberEventAsync(subscriberEvent, cancellationToken); } await Task.WhenAny(new Task[] { _resolutionQueue.AwaitEnqueueSignalAsync(), cancellationToken.WaitHandle.AsTask() }); } } catch (Exception ex) { _logger.LogError(ex, "Exception while managing resolution."); throw; } }
public IBusinessEvent Resolve(string type, byte[] data) { var strongType = _typeNamesToStrongTypes[type]; // Allow to throw unhandled exception. try { var json = Encoding.Unicode.GetString(data); return((IBusinessEvent)JsonConvert.DeserializeObject(json, strongType)); } catch (Exception ex) { _logger.LogError(ex, "Exception while resolving business event."); return(null); // Intentionally resolve to null when serialization or encoding exception. } }
// Not thread safe. public async Task ManageAsync(CancellationToken cancellationToken) { try { while (!cancellationToken.IsCancellationRequested) { SubscriberEvent subscriberEvent; while (!cancellationToken.IsCancellationRequested && _sortingQueue.TryDequeue(out subscriberEvent)) { // Expecting a case INsensitive key used to group executions. var parallelKey = _sorter.SortSubscriberEventToParallelKey(subscriberEvent); if (string.IsNullOrEmpty(parallelKey)) { throw new ArgumentException("Parallel key can't be null or empty."); } // Send to the handling manager. await _handlingManager.ReceiveSubscriberEventAsync(parallelKey, subscriberEvent, cancellationToken); } await Task.WhenAny(new Task[] { _sortingQueue.AwaitEnqueueSignalAsync(), cancellationToken.WaitHandle.AsTask() }); } } catch (Exception ex) { _logger.LogError(ex, "Exception while managing sorting."); throw; } }
public async Task ResetStreamStatesAsync() { if (_isSubscribing) { throw new InvalidOperationException("Can't reset stream states while subscribing."); } try { await _streamStateRepo.ResetStreamStatesAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error while resetting stream states."); throw; } }
public async Task LoadStreamEventsAsync(string streamId, long fromPosition, Func <StreamEvent, Task> receiverAsync, CancellationToken cancellationToken) { if (fromPosition < FirstPositionInStream) { throw new ArgumentException("Invalid position."); } try { StreamEventsSlice currentSlice; long nextSliceStart = StreamPosition.Start; do { // Read stream with to-links resolution. currentSlice = await _connection.ReadStreamEventsForwardAsync(streamId, nextSliceStart, _options.StreamReadBatchSize, true); if (currentSlice.Status != SliceReadStatus.Success) { break; } nextSliceStart = currentSlice.NextEventNumber; foreach (var resolvedEvent in currentSlice.Events) { await ReceiveResolvedEventAsync(receiverAsync, resolvedEvent, cancellationToken); } } while (!currentSlice.IsEndOfStream && !cancellationToken.IsCancellationRequested); } catch (Exception ex) { _logger.LogError(ex, "Exception while loading stream."); throw; } }
public virtual async Task <ICommandResult> HandleGenericCommandAsync(ICommand command, CancellationToken cancellationToken) { try { // Semantic validation to check format of fields, etc. - data that isn't dependent on state. var semanticValidationResult = command.ValidateSemantics(); if (!semanticValidationResult.IsValid) { return(CommandResult.FromErrors(semanticValidationResult.Errors.ToList())); } var regionId = command.GetRegionId(); var aggregateRootId = command.GetAggregateRootId(); var streamId = _streamIdBuilder.Build(regionId, _context, _aggregateRootName, aggregateRootId); // Load the latest aggregate root instance. var state = await _stateRepo.LoadAsync(regionId, streamId, cancellationToken); // Check for duplicate command id. if (await state.IsCausalIdInHistoryAsync(command.GetCommandId())) { return(CommandResult.FromError("Duplicate command id.")); } var handlerResult = await InvokeTypedCommandHandlerAsync(state, command, cancellationToken); if (!handlerResult.IsSuccess) { return(handlerResult); } // Commit events to the agg root stream. if (!(await CommitEventsAsync(handlerResult.Events, regionId, streamId, state.StreamPositionCheckpoint))) { return(CommandResult.FromError("Concurrency conflict while committing events.")); } return(handlerResult); } catch (Exception ex) { _logger.LogError(ex, "Exception while handling generic command."); throw; } }
private IEventStoreConnection CreateConnection(string regionId) { var uri = _eventStoreUris[regionId]; var builder = ConnectionSettings.Create().KeepReconnecting(); // Very important. Attempt reconnect infinitely. var connection = EventStoreConnection.Create(uri, builder, $"Connection-{regionId}"); connection.Connected += new EventHandler <ClientConnectionEventArgs>(delegate(Object o, ClientConnectionEventArgs a) { // _logger.LogDebug($"Event Store client connected. ({a.Connection.ConnectionName})"); }); connection.ErrorOccurred += new EventHandler <ClientErrorEventArgs>(delegate(Object o, ClientErrorEventArgs a) { _logger.LogError(a.Exception, $"Event store connection error. ({a.Connection.ConnectionName})"); }); _connections.Add(regionId, connection); return(connection); }
// Thread safe, can be called in parallel by one caller to load events from multiple subscription sources. public async Task ListenAsync(string regionId, string subscriptionStreamId, CancellationToken cancellationToken) { try { using (var streamClient = _streamClientFactory.Create(regionId)) { var listenerTask = streamClient.SubscribeToStreamAsync( subscriptionStreamId, streamClient.FirstPositionInStream, (se) => _resolutionManager.ReceiveStreamEventAsync(regionId, se, streamClient.FirstPositionInStream, cancellationToken), cancellationToken ); await Task.WhenAny(new[] { listenerTask, cancellationToken.WaitHandle.AsTask() }); } } catch (Exception ex) { _logger.LogError(ex, $"Exception while listening on regional subscription ({regionId}) stream {subscriptionStreamId}."); throw; } }
public async Task TryRunHandlerAsync(SubscriberEvent subscriberEvent, CancellationToken cancellationToken) { try { try { await _handler.HandleSubscriberEventAsync(subscriberEvent, cancellationToken); await _streamStateRepo.SaveStreamStateAsync(subscriberEvent.StreamId, subscriberEvent.Position, false); } catch (Exception ex) { _logger.LogError(ex, "Exception while handling event. Stream will be halted until stream state errors are cleared."); // Save errored stream state. await _streamStateRepo.SaveStreamStateAsync(subscriberEvent.StreamId, subscriberEvent.Position, true); } } finally { _awaiter.ReleaseThrottle(); // Make room for other handlers. _awaiter.SetHandlerCompletionSignal(); } }
// Not thread safe. public async Task ManageAsync(CancellationToken cancellationToken) { try { while (!cancellationToken.IsCancellationRequested) { while (_handlingQueue.IsEventsAvailable) { _awaiter.ResetHandlerCompletionSignal(); // Clean up finished tasks. _handlerTasks.PurgeFinishedTasks(); // Get the next event from the queue that isn't in a parallel group already running. var item = _handlingQueue.TryDequeue(_handlerTasks.Keys); if (item != null) { // When a stream has an error there may already be subscriber events in the // handling queue that have made it past the first check for errored stream state // prior to deserialization. If that's the case we just ignore the event. var state = await _streamStateRepo.LoadStreamStateAsync(item.SubscriberEvent.StreamId); if (state == null || !state.HasError) { await Task.WhenAny(new[] { _awaiter.AwaitThrottleAsync(), cancellationToken.WaitHandle.AsTask() }); if (!cancellationToken.IsCancellationRequested) { _handlerTasks.Add(item.ParallelKey, _handlerRunner.TryRunHandlerAsync(item.SubscriberEvent, cancellationToken)); } } } else { // Events are available but none in a parallel group that is not already executing. // Wait for a new event to arrive or for an event handler to complete. await Task.WhenAny(new Task[] { _awaiter.AwaitHandlerCompletionSignalAsync(), _handlingQueue.AwaitEnqueueSignalAsync(), cancellationToken.WaitHandle.AsTask() }); } if (cancellationToken.IsCancellationRequested) { break; } } if (cancellationToken.IsCancellationRequested) { break; } await Task.WhenAny(new Task[] { _handlingQueue.AwaitEnqueueSignalAsync(), cancellationToken.WaitHandle.AsTask() }); } } catch (Exception ex) { _logger.LogError(ex, "Exception while managing handler execution."); throw; } }