public CatchupAllSubscription(Guid subscriptionId, IPublisher bus, Position position, bool resolveLinks, ClaimsPrincipal user, bool requiresLeader, IReadIndex readIndex, CancellationToken cancellationToken) { if (bus == null) { throw new ArgumentNullException(nameof(bus)); } if (readIndex == null) { throw new ArgumentNullException(nameof(readIndex)); } _subscriptionId = subscriptionId; _bus = bus; _nextPosition = position == Position.End ? Position.FromInt64(readIndex.LastIndexedPosition, readIndex.LastIndexedPosition) : position; _startPosition = position == Position.End ? Position.Start : position; _resolveLinks = resolveLinks; _user = user; _requiresLeader = requiresLeader; _disposedTokenSource = new CancellationTokenSource(); _buffer = new ConcurrentQueue <ResolvedEvent>(); _tokenRegistration = cancellationToken.Register(_disposedTokenSource.Dispose); _currentPosition = _startPosition; Log.Information("Catch-up subscription {subscriptionId} to $all running...", _subscriptionId); }
public async ValueTask <bool> MoveNextAsync() { ReadLoop: if (_disposedTokenSource.IsCancellationRequested) { return(false); } if (_buffer.TryDequeue(out var current)) { _current = current; _currentPosition = Position.FromInt64(current.OriginalPosition.Value.CommitPosition, current.OriginalPosition.Value.PreparePosition); return(true); } var correlationId = Guid.NewGuid(); var readNextSource = new TaskCompletionSource <bool>(); var(commitPosition, preparePosition) = _nextPosition.ToInt64(); Log.Verbose( "Catch-up subscription {subscriptionId} to $all reading next page starting from {nextPosition}.", _subscriptionId, _nextPosition); _bus.Publish(new ClientMessage.ReadAllEventsForward( correlationId, correlationId, new CallbackEnvelope(OnMessage), commitPosition, preparePosition, ReadBatchSize, _resolveLinks, _requiresLeader, default, _user));
public CatchupAllSubscription(Guid subscriptionId, IPublisher bus, Position position, bool resolveLinks, IEventFilter eventFilter, ClaimsPrincipal user, bool requiresLeader, IReadIndex readIndex, uint?maxWindowSize, uint checkpointIntervalMultiplier, Func <Position, Task> checkpointReached, CancellationToken cancellationToken) { if (bus == null) { throw new ArgumentNullException(nameof(bus)); } if (eventFilter == null) { throw new ArgumentNullException(nameof(eventFilter)); } if (readIndex == null) { throw new ArgumentNullException(nameof(readIndex)); } if (checkpointReached == null) { throw new ArgumentNullException(nameof(checkpointReached)); } if (checkpointIntervalMultiplier == 0) { throw new ArgumentOutOfRangeException(nameof(checkpointIntervalMultiplier)); } _subscriptionId = subscriptionId; _bus = bus; _nextPosition = position == Position.End ? Position.FromInt64(readIndex.LastIndexedPosition, readIndex.LastIndexedPosition) : position; _startPosition = position == Position.End ? Position.Start : position; _resolveLinks = resolveLinks; _eventFilter = eventFilter; _user = user; _requiresLeader = requiresLeader; _checkpointReached = checkpointReached; _maxWindowSize = maxWindowSize ?? ReadBatchSize; _checkpointInterval = checkpointIntervalMultiplier * _maxWindowSize; _disposedTokenSource = new CancellationTokenSource(); _buffer = new ConcurrentQueueWrapper <(ResolvedEvent, Position?)>(); _tokenRegistration = cancellationToken.Register(_disposedTokenSource.Dispose); _currentPosition = _startPosition; _checkpointIntervalCounter = 0; Log.Information("Catch-up subscription {subscriptionId} to $all:{eventFilter} running...", _subscriptionId, _eventFilter); }
private static ReadResp.Types.ReadEvent.Types.RecordedEvent ConvertToRecordedEvent( ReadReq.Types.Options.Types.UUIDOption uuidOption, EventRecord e, long?commitPosition, long?preparePosition) { if (e == null) { return(null); } var position = Position.FromInt64(commitPosition ?? -1, preparePosition ?? -1); return(new ReadResp.Types.ReadEvent.Types.RecordedEvent { Id = uuidOption.ContentCase switch { ReadReq.Types.Options.Types.UUIDOption.ContentOneofCase.String => new UUID { String = e.EventId.ToString() }, _ => Uuid.FromGuid(e.EventId).ToDto() },
private void Subscribe(Position?startPosition) { if (startPosition == Position.End) { GoLive(Position.End); } else if (startPosition == null) { CatchUp(Position.Start); } else { var(commitPosition, preparePosition) = startPosition.Value.ToInt64(); var indexResult = _readIndex.ReadAllEventsForward(new TFPos(commitPosition, preparePosition), 1); CatchUp(Position.FromInt64(indexResult.NextPos.CommitPosition, indexResult.NextPos.PreparePosition)); } }
public AllSubscription(IPublisher bus, Position?startPosition, bool resolveLinks, ClaimsPrincipal user, bool requiresLeader, IReadIndex readIndex, CancellationToken cancellationToken) { if (bus == null) { throw new ArgumentNullException(nameof(bus)); } if (readIndex == null) { throw new ArgumentNullException(nameof(readIndex)); } _subscriptionId = Guid.NewGuid(); _bus = bus; _resolveLinks = resolveLinks; _user = user; _requiresLeader = requiresLeader; _cancellationToken = cancellationToken; _subscriptionStarted = new TaskCompletionSource <bool>(); _channel = Channel.CreateBounded <ResolvedEvent>(BoundedChannelOptions); _semaphore = new SemaphoreSlim(1, 1); SubscriptionId = _subscriptionId.ToString(); var startPositionExclusive = startPosition == Position.End ? Position.FromInt64(readIndex.LastIndexedPosition, readIndex.LastIndexedPosition) : startPosition ?? Position.Start; _startPositionExclusive = new TFPos((long)startPositionExclusive.CommitPosition, (long)startPositionExclusive.PreparePosition); Subscribe(startPositionExclusive, startPosition != Position.End); }
public AllSubscription(IPublisher bus, Position?startPosition, bool resolveLinks, ClaimsPrincipal user, bool requiresLeader, IReadIndex readIndex, CancellationToken cancellationToken) { if (bus == null) { throw new ArgumentNullException(nameof(bus)); } if (readIndex == null) { throw new ArgumentNullException(nameof(readIndex)); } _subscriptionId = Guid.NewGuid(); _bus = bus; _resolveLinks = resolveLinks; _user = user; _requiresLeader = requiresLeader; _readIndex = readIndex; _cancellationToken = cancellationToken; _subscriptionStarted = new TaskCompletionSource <bool>(); _subscriptionStarted.SetResult(true); _inner = startPosition == Position.End ? (IStreamSubscription) new LiveStreamSubscription(_subscriptionId, _bus, Position.FromInt64(_readIndex.LastIndexedPosition, _readIndex.LastIndexedPosition), _resolveLinks, _user, _requiresLeader, _cancellationToken) : new CatchupAllSubscription(_subscriptionId, bus, startPosition ?? Position.Start, resolveLinks, user, _requiresLeader, readIndex, cancellationToken); }
public override async Task <AppendResp> Append( IAsyncStreamReader <AppendReq> requestStream, ServerCallContext context) { if (!await requestStream.MoveNext().ConfigureAwait(false)) { throw new InvalidOperationException(); } if (requestStream.Current.ContentCase != AppendReq.ContentOneofCase.Options) { throw new InvalidOperationException(); } var options = requestStream.Current.Options; var streamName = options.StreamName; var expectedVersion = options.ExpectedStreamRevisionCase switch { AppendReq.Types.Options.ExpectedStreamRevisionOneofCase.Revision => new StreamRevision( options.Revision).ToInt64(), AppendReq.Types.Options.ExpectedStreamRevisionOneofCase.Any => AnyStreamRevision.Any.ToInt64(), AppendReq.Types.Options.ExpectedStreamRevisionOneofCase.StreamExists => AnyStreamRevision.StreamExists.ToInt64(), AppendReq.Types.Options.ExpectedStreamRevisionOneofCase.NoStream => AnyStreamRevision.NoStream.ToInt64(), _ => throw new InvalidOperationException() }; var user = await GetUser(_authenticationProvider, context.RequestHeaders).ConfigureAwait(false); var correlationId = Guid.NewGuid(); // TODO: JPB use request id? var events = new List <Event>(); var size = 0; while (await requestStream.MoveNext().ConfigureAwait(false)) { if (requestStream.Current.ContentCase != AppendReq.ContentOneofCase.ProposedMessage) { throw new InvalidOperationException(); } var proposedMessage = requestStream.Current.ProposedMessage; var data = proposedMessage.Data.ToByteArray(); size += data.Length; if (size > _maxAppendSize) { throw RpcExceptions.MaxAppendSizeExceeded(_maxAppendSize); } if (!proposedMessage.Metadata.TryGetValue(Constants.Metadata.Type, out var eventType)) { throw RpcExceptions.RequiredMetadataPropertyMissing(Constants.Metadata.Type); } if (!proposedMessage.Metadata.TryGetValue(Constants.Metadata.IsJson, out var isJson)) { throw RpcExceptions.RequiredMetadataPropertyMissing(Constants.Metadata.IsJson); } events.Add(new Event( Uuid.FromDto(proposedMessage.Id).ToGuid(), eventType, bool.Parse(isJson), data, proposedMessage.CustomMetadata.ToByteArray())); } var appendResponseSource = new TaskCompletionSource <AppendResp>(); var envelope = new CallbackEnvelope(HandleWriteEventsCompleted); _queue.Publish(new ClientMessage.WriteEvents( correlationId, correlationId, envelope, true, streamName, expectedVersion, events.ToArray(), user)); return(await appendResponseSource.Task.ConfigureAwait(false)); void HandleWriteEventsCompleted(Message message) { if (message is ClientMessage.NotHandled notHandled && RpcExceptions.TryHandleNotHandled(notHandled, out var ex)) { appendResponseSource.TrySetException(ex); return; } if (!(message is ClientMessage.WriteEventsCompleted completed)) { appendResponseSource.TrySetException( RpcExceptions.UnknownMessage <ClientMessage.WriteEventsCompleted>(message)); return; } switch (completed.Result) { case OperationResult.Success: var response = new AppendResp(); if (completed.LastEventNumber == -1) { response.NoStream = new AppendResp.Types.Empty(); } else { response.CurrentRevision = StreamRevision.FromInt64(completed.LastEventNumber); } if (completed.CommitPosition == -1) { response.Empty = new AppendResp.Types.Empty(); } else { var position = Position.FromInt64(completed.CommitPosition, completed.PreparePosition); response.Position = new AppendResp.Types.Position { CommitPosition = position.CommitPosition, PreparePosition = position.PreparePosition }; } appendResponseSource.TrySetResult(response); return; case OperationResult.PrepareTimeout: case OperationResult.CommitTimeout: case OperationResult.ForwardTimeout: appendResponseSource.TrySetException(RpcExceptions.Timeout()); return; case OperationResult.WrongExpectedVersion: appendResponseSource.TrySetException(RpcExceptions.WrongExpectedVersion( streamName, expectedVersion, completed.CurrentVersion)); return; case OperationResult.StreamDeleted: appendResponseSource.TrySetException(RpcExceptions.StreamDeleted(streamName)); return; case OperationResult.InvalidTransaction: appendResponseSource.TrySetException(RpcExceptions.InvalidTransaction()); return; case OperationResult.AccessDenied: appendResponseSource.TrySetException(RpcExceptions.AccessDenied()); return; default: appendResponseSource.TrySetException(RpcExceptions.UnknownError(completed.Result)); return; } } } }
public AllSubscriptionFiltered(IPublisher bus, Position?startPosition, bool resolveLinks, IEventFilter eventFilter, ClaimsPrincipal user, bool requiresLeader, IReadIndex readIndex, uint?maxSearchWindow, uint checkpointIntervalMultiplier, Func <Position, Task> checkpointReached, CancellationToken cancellationToken) { if (bus == null) { throw new ArgumentNullException(nameof(bus)); } if (eventFilter == null) { throw new ArgumentNullException(nameof(eventFilter)); } if (readIndex == null) { throw new ArgumentNullException(nameof(readIndex)); } if (checkpointReached == null) { throw new ArgumentNullException(nameof(checkpointReached)); } if (checkpointIntervalMultiplier == 0) { throw new ArgumentOutOfRangeException(nameof(checkpointIntervalMultiplier)); } _subscriptionId = Guid.NewGuid(); _bus = bus; _resolveLinks = resolveLinks; _eventFilter = eventFilter; _user = user; _requiresLeader = requiresLeader; _readIndex = readIndex; _maxSearchWindow = maxSearchWindow; _checkpointIntervalMultiplier = checkpointIntervalMultiplier; _checkpointReached = checkpointReached; _cancellationToken = cancellationToken; _subscriptionStarted = new TaskCompletionSource <bool>(); _subscriptionStarted.SetResult(true); _inner = startPosition == Position.End ? (IStreamSubscription) new LiveStreamSubscription(_subscriptionId, _bus, Position.FromInt64(_readIndex.LastIndexedPosition, _readIndex.LastIndexedPosition), _resolveLinks, _eventFilter, _user, _requiresLeader, _maxSearchWindow, _checkpointIntervalMultiplier, checkpointReached, _cancellationToken) : new CatchupAllSubscription(_subscriptionId, bus, startPosition ?? Position.Start, resolveLinks, _eventFilter, user, _requiresLeader, readIndex, _maxSearchWindow, _checkpointIntervalMultiplier, checkpointReached, cancellationToken); }
private async Task <Position?> DeleteInternal(string streamName, long expectedVersion, ClaimsPrincipal user, bool hardDelete, bool requiresLeader) { var correlationId = Guid.NewGuid(); // TODO: JPB use request id? var deleteResponseSource = new TaskCompletionSource <Position?>(); var envelope = new CallbackEnvelope(HandleStreamDeletedCompleted); _queue.Publish(new ClientMessage.DeleteStream( correlationId, correlationId, envelope, requiresLeader, streamName, expectedVersion, hardDelete, user)); return(await deleteResponseSource.Task.ConfigureAwait(false)); void HandleStreamDeletedCompleted(Message message) { if (message is ClientMessage.NotHandled notHandled && RpcExceptions.TryHandleNotHandled(notHandled, out var ex)) { deleteResponseSource.TrySetException(ex); return; } if (!(message is ClientMessage.DeleteStreamCompleted completed)) { deleteResponseSource.TrySetException( RpcExceptions.UnknownMessage <ClientMessage.DeleteStreamCompleted>(message)); return; } switch (completed.Result) { case OperationResult.Success: deleteResponseSource.TrySetResult(completed.CommitPosition == -1 ? default : Position.FromInt64(completed.CommitPosition, completed.PreparePosition)); return; case OperationResult.PrepareTimeout: case OperationResult.CommitTimeout: case OperationResult.ForwardTimeout: deleteResponseSource.TrySetException(RpcExceptions.Timeout()); return; case OperationResult.WrongExpectedVersion: deleteResponseSource.TrySetException(RpcExceptions.WrongExpectedVersion(streamName, expectedVersion)); return; case OperationResult.StreamDeleted: deleteResponseSource.TrySetException(RpcExceptions.StreamDeleted(streamName)); return; case OperationResult.InvalidTransaction: deleteResponseSource.TrySetException(RpcExceptions.InvalidTransaction()); return; case OperationResult.AccessDenied: deleteResponseSource.TrySetException(RpcExceptions.AccessDenied()); return; default: deleteResponseSource.TrySetException(RpcExceptions.UnknownError(completed.Result)); return; } } }
public override async Task <AppendResp> Append( IAsyncStreamReader <AppendReq> requestStream, ServerCallContext context) { if (!await requestStream.MoveNext().ConfigureAwait(false)) { throw new InvalidOperationException(); } if (requestStream.Current.ContentCase != AppendReq.ContentOneofCase.Options) { throw new InvalidOperationException(); } var options = requestStream.Current.Options; var streamName = options.StreamIdentifier; var expectedVersion = options.ExpectedStreamRevisionCase switch { AppendReq.Types.Options.ExpectedStreamRevisionOneofCase.Revision => new StreamRevision( options.Revision).ToInt64(), AppendReq.Types.Options.ExpectedStreamRevisionOneofCase.Any => AnyStreamRevision.Any.ToInt64(), AppendReq.Types.Options.ExpectedStreamRevisionOneofCase.StreamExists => AnyStreamRevision.StreamExists.ToInt64(), AppendReq.Types.Options.ExpectedStreamRevisionOneofCase.NoStream => AnyStreamRevision.NoStream.ToInt64(), _ => throw new InvalidOperationException() }; var requiresLeader = GetRequiresLeader(context.RequestHeaders); var user = context.GetHttpContext().User; var op = WriteOperation.WithParameter(Plugins.Authorization.Operations.Streams.Parameters.StreamId(streamName)); if (!await _provider.CheckAccessAsync(user, op, context.CancellationToken).ConfigureAwait(false)) { throw AccessDenied(); } var correlationId = Guid.NewGuid(); // TODO: JPB use request id? var events = new List <Event>(); var size = 0; while (await requestStream.MoveNext().ConfigureAwait(false)) { if (requestStream.Current.ContentCase != AppendReq.ContentOneofCase.ProposedMessage) { throw new InvalidOperationException(); } var proposedMessage = requestStream.Current.ProposedMessage; var data = proposedMessage.Data.ToByteArray(); var metadata = proposedMessage.CustomMetadata.ToByteArray(); if (!proposedMessage.Metadata.TryGetValue(Constants.Metadata.Type, out var eventType)) { throw RpcExceptions.RequiredMetadataPropertyMissing(Constants.Metadata.Type); } if (!proposedMessage.Metadata.TryGetValue(Constants.Metadata.ContentType, out var contentType)) { throw RpcExceptions.RequiredMetadataPropertyMissing(Constants.Metadata.ContentType); } size += Event.SizeOnDisk(eventType, data, metadata); if (size > _maxAppendSize) { throw RpcExceptions.MaxAppendSizeExceeded(_maxAppendSize); } events.Add(new Event( Uuid.FromDto(proposedMessage.Id).ToGuid(), eventType, contentType == Constants.Metadata.ContentTypes.ApplicationJson, data, metadata)); } var appendResponseSource = new TaskCompletionSource <AppendResp>(); var envelope = new CallbackEnvelope(HandleWriteEventsCompleted); _publisher.Publish(new ClientMessage.WriteEvents( correlationId, correlationId, envelope, requiresLeader, streamName, expectedVersion, events.ToArray(), user, cancellationToken: context.CancellationToken)); return(await appendResponseSource.Task.ConfigureAwait(false)); void HandleWriteEventsCompleted(Message message) { if (message is ClientMessage.NotHandled notHandled && RpcExceptions.TryHandleNotHandled(notHandled, out var ex)) { appendResponseSource.TrySetException(ex); return; } if (!(message is ClientMessage.WriteEventsCompleted completed)) { appendResponseSource.TrySetException( RpcExceptions.UnknownMessage <ClientMessage.WriteEventsCompleted>(message)); return; } var response = new AppendResp(); switch (completed.Result) { case OperationResult.Success: response.Success = new AppendResp.Types.Success(); if (completed.LastEventNumber == -1) { response.Success.NoStream = new Empty(); } else { response.Success.CurrentRevision = StreamRevision.FromInt64(completed.LastEventNumber); } if (completed.CommitPosition == -1) { response.Success.NoPosition = new Empty(); } else { var position = Position.FromInt64(completed.CommitPosition, completed.PreparePosition); response.Success.Position = new AppendResp.Types.Position { CommitPosition = position.CommitPosition, PreparePosition = position.PreparePosition }; } appendResponseSource.TrySetResult(response); return; case OperationResult.PrepareTimeout: case OperationResult.CommitTimeout: case OperationResult.ForwardTimeout: appendResponseSource.TrySetException(RpcExceptions.Timeout()); return; case OperationResult.WrongExpectedVersion: response.WrongExpectedVersion = new AppendResp.Types.WrongExpectedVersion(); switch (options.ExpectedStreamRevisionCase) { case AppendReq.Types.Options.ExpectedStreamRevisionOneofCase.Any: response.WrongExpectedVersion.ExpectedAny = new Empty(); response.WrongExpectedVersion.Any2060 = new Empty(); break; case AppendReq.Types.Options.ExpectedStreamRevisionOneofCase.StreamExists: response.WrongExpectedVersion.ExpectedStreamExists = new Empty(); response.WrongExpectedVersion.StreamExists2060 = new Empty(); break; case AppendReq.Types.Options.ExpectedStreamRevisionOneofCase.NoStream: response.WrongExpectedVersion.ExpectedNoStream = new Empty(); response.WrongExpectedVersion.ExpectedRevision2060 = ulong.MaxValue; break; case AppendReq.Types.Options.ExpectedStreamRevisionOneofCase.Revision: response.WrongExpectedVersion.ExpectedRevision = StreamRevision.FromInt64(expectedVersion); response.WrongExpectedVersion.ExpectedRevision2060 = StreamRevision.FromInt64(expectedVersion); break; } if (completed.CurrentVersion == -1) { response.WrongExpectedVersion.CurrentNoStream = new Empty(); response.WrongExpectedVersion.NoStream2060 = new Empty(); } else { response.WrongExpectedVersion.CurrentRevision = StreamRevision.FromInt64(completed.CurrentVersion); response.WrongExpectedVersion.CurrentRevision2060 = StreamRevision.FromInt64(completed.CurrentVersion); } appendResponseSource.TrySetResult(response); return; case OperationResult.StreamDeleted: appendResponseSource.TrySetException(RpcExceptions.StreamDeleted(streamName)); return; case OperationResult.InvalidTransaction: appendResponseSource.TrySetException(RpcExceptions.InvalidTransaction()); return; case OperationResult.AccessDenied: appendResponseSource.TrySetException(RpcExceptions.AccessDenied()); return; default: appendResponseSource.TrySetException(RpcExceptions.UnknownError(completed.Result)); return; } } } }
private void ReadPage(Position startPosition, ulong readCount = 0) { var correlationId = Guid.NewGuid(); var(commitPosition, preparePosition) = startPosition.ToInt64(); _bus.Publish(new ClientMessage.FilteredReadAllEventsForward( correlationId, correlationId, new ContinuationEnvelope(OnMessage, _semaphore, _cancellationToken), commitPosition, preparePosition, (int)Math.Min(ReadBatchSize, _maxCount), _resolveLinks, _requiresLeader, (int)_maxSearchWindow, null, _eventFilter, _user, expires: _deadline)); async Task OnMessage(Message message, CancellationToken ct) { if (message is ClientMessage.NotHandled notHandled && RpcExceptions.TryHandleNotHandled(notHandled, out var ex)) { _channel.Writer.TryComplete(ex); return; } if (!(message is ClientMessage.FilteredReadAllEventsForwardCompleted completed)) { _channel.Writer.TryComplete( RpcExceptions.UnknownMessage <ClientMessage.FilteredReadAllEventsForwardCompleted>(message)); return; } switch (completed.Result) { case FilteredReadAllResult.Success: foreach (var @event in completed.Events) { if (readCount >= _maxCount) { _channel.Writer.TryComplete(); return; } await _channel.Writer.WriteAsync(new ReadResp { Event = ConvertToReadEvent(_uuidOption, @event) }, _cancellationToken).ConfigureAwait(false); readCount++; } if (completed.IsEndOfStream) { _channel.Writer.TryComplete(); return; } ReadPage(Position.FromInt64( completed.NextPos.CommitPosition, completed.NextPos.PreparePosition), readCount); return; case FilteredReadAllResult.AccessDenied: _channel.Writer.TryComplete(RpcExceptions.AccessDenied()); return; default: _channel.Writer.TryComplete(RpcExceptions.UnknownError(completed.Result)); return; } } }
private void GoLive(Position startPosition) { var liveEvents = Channel.CreateBounded <ResolvedEvent>(BoundedChannelOptions); var caughtUpSource = new TaskCompletionSource <Position>(); var liveMessagesCancelled = 0; Log.Information( "Live subscription {subscriptionId} to $all running from {position}...", _subscriptionId, startPosition); _bus.Publish(new ClientMessage.SubscribeToStream(Guid.NewGuid(), _subscriptionId, new ContinuationEnvelope(OnSubscriptionMessage, _semaphore, _cancellationToken), _subscriptionId, string.Empty, _resolveLinks, _user)); Task.Factory.StartNew(PumpLiveMessages, _cancellationToken); async Task PumpLiveMessages() { var position = await caughtUpSource.Task.ConfigureAwait(false); await _channel.Writer.WriteAsync(new ReadResp { Checkpoint = new ReadResp.Types.Checkpoint { CommitPosition = position.CommitPosition, PreparePosition = position.PreparePosition } }, _cancellationToken).ConfigureAwait(false); await foreach (var @event in liveEvents.Reader.ReadAllAsync(_cancellationToken) .ConfigureAwait(false)) { await _channel.Writer.WriteAsync(new ReadResp { Event = ConvertToReadEvent(_uuidOption, @event) }, _cancellationToken).ConfigureAwait(false); } } async Task OnSubscriptionMessage(Message message, CancellationToken cancellationToken) { if (message is ClientMessage.NotHandled notHandled && RpcExceptions.TryHandleNotHandled(notHandled, out var ex)) { Fail(ex); return; } switch (message) { case ClientMessage.SubscriptionConfirmation confirmed: await ConfirmSubscription().ConfigureAwait(false); var caughtUp = new TFPos(confirmed.LastIndexedPosition, confirmed.LastIndexedPosition); Log.Verbose( "Live subscription {subscriptionId} to $all confirmed at {position}.", _subscriptionId, caughtUp); if (startPosition != Position.End) { ReadHistoricalEvents(startPosition); } else { NotifyCaughtUp(startPosition); } void NotifyCaughtUp(Position position) { Log.Verbose( "Live subscription {subscriptionId} to $all caught up at {position} because the end of stream was reached.", _subscriptionId, position); caughtUpSource.TrySetResult(position); } async Task OnHistoricalEventsMessage(Message message, CancellationToken ct) { if (message is ClientMessage.NotHandled notHandled && RpcExceptions.TryHandleNotHandled(notHandled, out var ex)) { Fail(ex); return; } if (!(message is ClientMessage.ReadAllEventsForwardCompleted completed)) { Fail( RpcExceptions.UnknownMessage <ClientMessage.ReadAllEventsForwardCompleted>( message)); return; } switch (completed.Result) { case ReadAllResult.Success: if (completed.Events.Length == 0 && completed.IsEndOfStream) { NotifyCaughtUp(Position.FromInt64(completed.CurrentPos.CommitPosition, completed.CurrentPos.PreparePosition)); return; } foreach (var @event in completed.Events) { var position = @event.OriginalPosition.Value; if (position > caughtUp) { NotifyCaughtUp(Position.FromInt64(position.CommitPosition, position.PreparePosition)); return; } await _channel.Writer.WriteAsync(new ReadResp { Event = ConvertToReadEvent(_uuidOption, @event) }, _cancellationToken) .ConfigureAwait(false); } ReadHistoricalEvents(Position.FromInt64( completed.NextPos.CommitPosition, completed.NextPos.PreparePosition)); return; case ReadAllResult.AccessDenied: Fail(RpcExceptions.AccessDenied()); return; default: Fail(RpcExceptions.UnknownError(completed.Result)); return; } } void ReadHistoricalEvents(Position fromPosition) { if (fromPosition == Position.End) { throw new ArgumentOutOfRangeException(nameof(fromPosition)); } Log.Verbose( "Live subscription {subscriptionId} to $all loading any missed events starting from {position}.", _subscriptionId, fromPosition); ReadPage(fromPosition, OnHistoricalEventsMessage); } return; case ClientMessage.SubscriptionDropped dropped: switch (dropped.Reason) { case SubscriptionDropReason.AccessDenied: Fail(RpcExceptions.AccessDenied()); return; case SubscriptionDropReason.Unsubscribed: return; default: Fail(RpcExceptions.UnknownError(dropped.Reason)); return; } case ClientMessage.StreamEventAppeared appeared: { if (liveMessagesCancelled == 1) { return; } using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)); try { Log.Verbose( "Live subscription {subscriptionId} to $all enqueuing live message {position}.", _subscriptionId, appeared.Event.OriginalPosition); await liveEvents.Writer.WriteAsync(appeared.Event, cts.Token) .ConfigureAwait(false); } catch (Exception e) { if (Interlocked.Exchange(ref liveMessagesCancelled, 1) != 0) { return; } Log.Verbose( e, "Live subscription {subscriptionId} to $all timed out at {position}; unsubscribing...", _subscriptionId, appeared.Event.OriginalPosition.GetValueOrDefault()); Unsubscribe(); liveEvents.Writer.Complete(); CatchUp(Position.FromInt64(appeared.Event.OriginalPosition.Value.CommitPosition, appeared.Event.OriginalPosition.Value.PreparePosition)); } return; } default: Fail( RpcExceptions.UnknownMessage <ClientMessage.SubscriptionConfirmation>(message)); return; } } void Fail(Exception exception) { this.Fail(exception); caughtUpSource.TrySetException(exception); } }
private void CatchUp(Position startPosition) { Log.Information( "Catch-up subscription {subscriptionId} to $all@{position} running...", _subscriptionId, startPosition); ReadPage(startPosition, OnMessage); async Task OnMessage(Message message, CancellationToken ct) { if (message is ClientMessage.NotHandled notHandled && RpcExceptions.TryHandleNotHandled(notHandled, out var ex)) { Fail(ex); return; } if (!(message is ClientMessage.ReadAllEventsForwardCompleted completed)) { Fail( RpcExceptions.UnknownMessage <ClientMessage.ReadAllEventsForwardCompleted>(message)); return; } switch (completed.Result) { case ReadAllResult.Success: await ConfirmSubscription().ConfigureAwait(false); var position = Position.FromInt64(completed.CurrentPos.CommitPosition, completed.CurrentPos.PreparePosition); foreach (var @event in completed.Events) { position = Position.FromInt64( @event.OriginalPosition.Value.CommitPosition, @event.OriginalPosition.Value.PreparePosition); Log.Verbose( "Catch-up subscription {subscriptionId} to $all received event {position}.", _subscriptionId, position); await _channel.Writer.WriteAsync(new ReadResp { Event = ConvertToReadEvent(_uuidOption, @event) }, ct).ConfigureAwait(false); } var nextPosition = Position.FromInt64(completed.NextPos.CommitPosition, completed.NextPos.PreparePosition); if (completed.IsEndOfStream) { GoLive(nextPosition); return; } ReadPage(nextPosition, OnMessage); return; case ReadAllResult.AccessDenied: Fail(RpcExceptions.AccessDenied()); return; default: Fail(RpcExceptions.UnknownError(completed.Result)); return; } } }
public AllSubscriptionFiltered(IPublisher bus, Position?startPosition, bool resolveLinks, IEventFilter eventFilter, ClaimsPrincipal user, bool requiresLeader, IReadIndex readIndex, uint?maxSearchWindow, uint checkpointIntervalMultiplier, Func <Position, Task> checkpointReached, CancellationToken cancellationToken) { if (bus == null) { throw new ArgumentNullException(nameof(bus)); } if (eventFilter == null) { throw new ArgumentNullException(nameof(eventFilter)); } if (readIndex == null) { throw new ArgumentNullException(nameof(readIndex)); } if (checkpointReached == null) { throw new ArgumentNullException(nameof(checkpointReached)); } if (checkpointIntervalMultiplier == 0) { throw new ArgumentOutOfRangeException(nameof(checkpointIntervalMultiplier)); } _subscriptionId = Guid.NewGuid(); _bus = bus; _resolveLinks = resolveLinks; _eventFilter = eventFilter; _user = user; _requiresLeader = requiresLeader; _maxSearchWindow = maxSearchWindow ?? ReadBatchSize; _checkpointReached = checkpointReached; _cancellationToken = cancellationToken; _subscriptionStarted = new TaskCompletionSource <bool>(); _channel = Channel.CreateBounded <(ResolvedEvent?, Position?)>(BoundedChannelOptions); _checkpointInterval = checkpointIntervalMultiplier * _maxSearchWindow; _semaphore = new SemaphoreSlim(1, 1); _lastCheckpoint = Position.Start; SubscriptionId = _subscriptionId.ToString(); var lastIndexedPosition = readIndex.LastIndexedPosition; var startPositionExclusive = startPosition == Position.End ? Position.FromInt64(lastIndexedPosition, lastIndexedPosition) : startPosition ?? Position.Start; _startPositionExclusive = new TFPos((long)startPositionExclusive.CommitPosition, (long)startPositionExclusive.PreparePosition); Subscribe(startPositionExclusive, startPosition != Position.End); }
private void CatchUp(Position startPosition) { Log.Information( "Catch-up subscription {subscriptionId} to $all:{eventFilter}@{position} running...", _subscriptionId, _eventFilter, startPosition); ReadPage(startPosition, OnMessage); async Task OnMessage(Message message, CancellationToken ct) { if (message is ClientMessage.NotHandled notHandled && RpcExceptions.TryHandleNotHandled(notHandled, out var ex)) { Fail(ex); return; } if (!(message is ClientMessage.FilteredReadAllEventsForwardCompleted completed)) { Fail(RpcExceptions.UnknownMessage <ClientMessage.FilteredReadAllEventsForwardCompleted>( message)); return; } switch (completed.Result) { case FilteredReadAllResult.Success: ConfirmSubscription(); var position = Position.FromInt64(completed.CurrentPos.CommitPosition, completed.CurrentPos.PreparePosition); foreach (var @event in completed.Events) { position = Position.FromInt64( @event.OriginalPosition.Value.CommitPosition, @event.OriginalPosition.Value.PreparePosition); Log.Verbose( "Catch-up subscription {subscriptionId} to $all:{eventFilter} received event {position}.", _subscriptionId, _eventFilter, position); await _channel.Writer.WriteAsync((@event, new Position?()), ct).ConfigureAwait(false); } _checkpointIntervalCounter += completed.ConsideredEventsCount; Log.Verbose( "Catch-up subscription {subscriptionId} to $all:{eventFilter} considered {consideredEventsCount}, interval: {checkpointInterval}, counter: {checkpointIntervalCounter}.", _subscriptionId, _eventFilter, completed.ConsideredEventsCount, _checkpointInterval, _checkpointIntervalCounter); var nextPosition = Position.FromInt64(completed.NextPos.CommitPosition, completed.NextPos.PreparePosition); if (completed.IsEndOfStream) { GoLive(nextPosition); return; } if (_checkpointIntervalCounter >= _checkpointInterval) { _checkpointIntervalCounter %= _checkpointInterval; Log.Verbose( "Catch-up subscription {subscriptionId} to $all:{eventFilter} reached checkpoint at {position}, interval: {checkpointInterval}, counter: {checkpointIntervalCounter}.", _subscriptionId, _eventFilter, nextPosition, _checkpointInterval, _checkpointIntervalCounter); await _channel.Writer.WriteAsync((new ResolvedEvent?(), position), ct) .ConfigureAwait(false); } ReadPage(nextPosition, OnMessage); return; case FilteredReadAllResult.AccessDenied: Fail(RpcExceptions.AccessDenied()); return; default: Fail(RpcExceptions.UnknownError(completed.Result)); return; } } }