Exemplo n.º 1
0
 public override Task <Empty> LeaderIsResigning(LeaderIsResigningRequest request, ServerCallContext context)
 {
     _bus.Publish(new ElectionMessage.LeaderIsResigning(
                      Uuid.FromDto(request.LeaderId).ToGuid(),
                      new IPEndPoint(IPAddress.Parse(request.LeaderInternalHttp.Address),
                                     (int)request.LeaderInternalHttp.Port)));
     return(EmptyResult);
 }
Exemplo n.º 2
0
 public override Task <Empty> Prepare(PrepareRequest request, ServerCallContext context)
 {
     _bus.Publish(new ElectionMessage.Prepare(
                      Uuid.FromDto(request.ServerId).ToGuid(),
                      new IPEndPoint(IPAddress.Parse(request.ServerInternalHttp.Address), (int)request.ServerInternalHttp.Port),
                      request.View));
     return(EmptyResult);
 }
Exemplo n.º 3
0
        public override async Task <Empty> Prepare(PrepareRequest request, ServerCallContext context)
        {
            var user = context.GetHttpContext().User;

            if (!await _authorizationProvider.CheckAccessAsync(user, PrepareOperation, context.CancellationToken).ConfigureAwait(false))
            {
                throw AccessDenied();
            }
            _bus.Publish(new ElectionMessage.Prepare(
                             Uuid.FromDto(request.ServerId).ToGuid(),
                             new IPEndPoint(IPAddress.Parse(request.ServerInternalHttp.Address), (int)request.ServerInternalHttp.Port),
                             request.View));
            return(EmptyResult);
        }
Exemplo n.º 4
0
        public override async Task <Empty> ViewChangeProof(ViewChangeProofRequest request, ServerCallContext context)
        {
            var user = context.GetHttpContext().User;

            if (!await _authorizationProvider.CheckAccessAsync(user, ViewChangeProofOperation, context.CancellationToken).ConfigureAwait(false))
            {
                throw AccessDenied();
            }
            _bus.Publish(new ElectionMessage.ViewChangeProof(
                             Uuid.FromDto(request.ServerId).ToGuid(),
                             new DnsEndPoint(request.ServerHttp.Address, (int)request.ServerHttp.Port),
                             request.InstalledView));
            return(EmptyResult);
        }
Exemplo n.º 5
0
        public override async Task <Empty> LeaderIsResigning(LeaderIsResigningRequest request, ServerCallContext context)
        {
            var user = context.GetHttpContext().User;

            if (!await _authorizationProvider.CheckAccessAsync(user, MasterIsResigningOperation, context.CancellationToken).ConfigureAwait(false))
            {
                throw AccessDenied();
            }
            _bus.Publish(new ElectionMessage.LeaderIsResigning(
                             Uuid.FromDto(request.LeaderId).ToGuid(),
                             new DnsEndPoint(request.LeaderHttp.Address,
                                             (int)request.LeaderHttp.Port)));
            return(EmptyResult);
        }
Exemplo n.º 6
0
 public override Task <Empty> PrepareOk(PrepareOkRequest request, ServerCallContext context)
 {
     _bus.Publish(new ElectionMessage.PrepareOk(
                      request.View,
                      Uuid.FromDto(request.ServerId).ToGuid(),
                      new IPEndPoint(IPAddress.Parse(request.ServerInternalHttp.Address), (int)request.ServerInternalHttp.Port),
                      request.EpochNumber,
                      request.EpochPosition,
                      Uuid.FromDto(request.EpochId).ToGuid(),
                      request.LastCommitPosition,
                      request.WriterCheckpoint,
                      request.ChaserCheckpoint,
                      request.NodePriority));
     return(EmptyResult);
 }
Exemplo n.º 7
0
        public override async Task <Empty> PrepareOk(PrepareOkRequest request, ServerCallContext context)
        {
            var user = context.GetHttpContext().User;

            if (!await _authorizationProvider.CheckAccessAsync(user, PrepareOkOperation, context.CancellationToken).ConfigureAwait(false))
            {
                throw AccessDenied();
            }
            _bus.Publish(new ElectionMessage.PrepareOk(
                             request.View,
                             Uuid.FromDto(request.ServerId).ToGuid(),
                             new DnsEndPoint(request.ServerHttp.Address, (int)request.ServerHttp.Port),
                             request.EpochNumber,
                             request.EpochPosition,
                             Uuid.FromDto(request.EpochId).ToGuid(),
                             request.LastCommitPosition,
                             request.WriterCheckpoint,
                             request.ChaserCheckpoint,
                             request.NodePriority));
            return(EmptyResult);
        }
Exemplo n.º 8
0
        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;
                }
            }
        }
    }
Exemplo n.º 9
0
        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;
                }
            }
        }
    }
        public override async Task Read(IAsyncStreamReader <ReadReq> requestStream,
                                        IServerStreamWriter <ReadResp> responseStream, ServerCallContext context)
        {
            if (!await requestStream.MoveNext().ConfigureAwait(false))
            {
                return;
            }

            if (requestStream.Current.ContentCase != ReadReq.ContentOneofCase.Options)
            {
                throw new InvalidOperationException();
            }

            var options = requestStream.Current.Options;
            var user    = context.GetHttpContext().User;

            if (!await _authorizationProvider.CheckAccessAsync(user,
                                                               ProcessMessagesOperation.WithParameter(Plugins.Authorization.Operations.Subscriptions.Parameters.StreamId(options.StreamName)), context.CancellationToken).ConfigureAwait(false))
            {
                throw AccessDenied();
            }
            var connectionName =
                context.RequestHeaders.FirstOrDefault(x => x.Key == Constants.Headers.ConnectionName)?.Value ??
                "<unknown>";
            var correlationId   = Guid.NewGuid();
            var uuidOptionsCase = options.UuidOption.ContentCase;

            await using var enumerator = new PersistentStreamSubscriptionEnumerator(correlationId, connectionName,
                                                                                    _publisher, options.StreamName, options.GroupName, options.BufferSize, user, context.CancellationToken);

            var subscriptionId = await enumerator.Started.ConfigureAwait(false);

            var read = requestStream.ForEachAsync(HandleAckNack);

            await responseStream.WriteAsync(new ReadResp {
                SubscriptionConfirmation = new ReadResp.Types.SubscriptionConfirmation {
                    SubscriptionId = subscriptionId
                }
            }).ConfigureAwait(false);

            while (await enumerator.MoveNextAsync().ConfigureAwait(false))
            {
                await responseStream.WriteAsync(new ReadResp {
                    Event = ConvertToReadEvent(enumerator.Current)
                }).ConfigureAwait(false);
            }

            await read.ConfigureAwait(false);

            ValueTask HandleAckNack(ReadReq request)
            {
                _publisher.Publish(request.ContentCase switch {
                    ReadReq.ContentOneofCase.Ack => new ClientMessage.PersistentSubscriptionAckEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Ack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    ReadReq.ContentOneofCase.Nack =>
                    new ClientMessage.PersistentSubscriptionNackEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Nack.Reason, request.Nack.Action switch {
                        ReadReq.Types.Nack.Types.Action.Unknown => NakAction.Unknown,
                        ReadReq.Types.Nack.Types.Action.Park => NakAction.Park,
                        ReadReq.Types.Nack.Types.Action.Retry => NakAction.Retry,
                        ReadReq.Types.Nack.Types.Action.Skip => NakAction.Skip,
                        ReadReq.Types.Nack.Types.Action.Stop => NakAction.Stop,
                        _ => throw new InvalidOperationException()
                    },
                        request.Nack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    _ => throw new InvalidOperationException()
                });
        public override async Task Read(IAsyncStreamReader <ReadReq> requestStream,
                                        IServerStreamWriter <ReadResp> responseStream, ServerCallContext context)
        {
            if (!await requestStream.MoveNext().ConfigureAwait(false))
            {
                return;
            }

            if (requestStream.Current.ContentCase != ReadReq.ContentOneofCase.Options)
            {
                throw new InvalidOperationException();
            }

            var options = requestStream.Current.Options;
            var user    = context.GetHttpContext().User;

            string streamId = null;

            switch (options.StreamOptionCase)
            {
            case ReadReq.Types.Options.StreamOptionOneofCase.StreamIdentifier:
                streamId = options.StreamIdentifier;
                break;

            case ReadReq.Types.Options.StreamOptionOneofCase.All:
                streamId = SystemStreams.AllStream;
                break;

            default:
                throw new InvalidOperationException();
            }

            if (!await _authorizationProvider.CheckAccessAsync(user,
                                                               ProcessMessagesOperation.WithParameter(Plugins.Authorization.Operations.Subscriptions.Parameters.StreamId(streamId)), context.CancellationToken).ConfigureAwait(false))
            {
                throw RpcExceptions.AccessDenied();
            }
            var connectionName =
                context.RequestHeaders.FirstOrDefault(x => x.Key == Constants.Headers.ConnectionName)?.Value ??
                "<unknown>";
            var correlationId   = Guid.NewGuid();
            var uuidOptionsCase = options.UuidOption.ContentCase;

            await using var enumerator = new PersistentStreamSubscriptionEnumerator(correlationId, connectionName,
                                                                                    _publisher, streamId, options.GroupName, options.BufferSize, user, context.CancellationToken);

            var subscriptionId = await enumerator.Started.ConfigureAwait(false);

            var read = ValueTask.CompletedTask;
            var cts  = new CancellationTokenSource();

            try {
                read = PumpRequestStream(cts.Token);

                await responseStream.WriteAsync(new ReadResp {
                    SubscriptionConfirmation = new ReadResp.Types.SubscriptionConfirmation {
                        SubscriptionId = subscriptionId
                    }
                }).ConfigureAwait(false);

                while (await enumerator.MoveNextAsync().ConfigureAwait(false))
                {
                    await responseStream.WriteAsync(new ReadResp {
                        Event = ConvertToReadEvent(enumerator.Current)
                    }).ConfigureAwait(false);
                }
            } catch (IOException) {
                Log.Information("Subscription {correlationId} to {subscriptionId} disposed. The request stream was closed.", correlationId, subscriptionId);
            } finally {
                // make sure we stop reading the request stream before leaving this method
                cts.Cancel();
                await read.ConfigureAwait(false);
            }

            async ValueTask PumpRequestStream(CancellationToken token)
            {
                try {
                    await requestStream.ForEachAsync(HandleAckNack, token).ConfigureAwait(false);
                } catch {
                }
            }

            ValueTask HandleAckNack(ReadReq request)
            {
                _publisher.Publish(request.ContentCase switch {
                    ReadReq.ContentOneofCase.Ack => new ClientMessage.PersistentSubscriptionAckEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Ack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    ReadReq.ContentOneofCase.Nack =>
                    new ClientMessage.PersistentSubscriptionNackEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Nack.Reason, request.Nack.Action switch {
                        ReadReq.Types.Nack.Types.Action.Unknown => NakAction.Unknown,
                        ReadReq.Types.Nack.Types.Action.Park => NakAction.Park,
                        ReadReq.Types.Nack.Types.Action.Retry => NakAction.Retry,
                        ReadReq.Types.Nack.Types.Action.Skip => NakAction.Skip,
                        ReadReq.Types.Nack.Types.Action.Stop => NakAction.Stop,
                        _ => throw RpcExceptions.InvalidArgument(request.Nack.Action)
                    },
                        request.Nack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    _ => throw RpcExceptions.InvalidArgument(request.ContentCase)
                });
Exemplo n.º 12
0
            private async Task Receive(ChannelWriter <BatchAppendResp> writer, ClaimsPrincipal user, bool requiresLeader,
                                       CancellationToken cancellationToken)
            {
                var pendingWrites = new ConcurrentDictionary <Guid, ClientWriteRequest>();

                try {
                    await foreach (var request in _requestStream.ReadAllAsync(cancellationToken))
                    {
                        try {
                            var correlationId = Uuid.FromDto(request.CorrelationId).ToGuid();

                            if (request.Options != null)
                            {
                                TimeSpan timeout = Min(GetRequestedTimeout(request.Options), _writeTimeout);

                                if (!await _authorizationProvider.CheckAccessAsync(user, WriteOperation.WithParameter(
                                                                                       Plugins.Authorization.Operations.Streams.Parameters.StreamId(
                                                                                           request.Options.StreamIdentifier)), cancellationToken).ConfigureAwait(false))
                                {
                                    await writer.WriteAsync(new BatchAppendResp {
                                        CorrelationId    = request.CorrelationId,
                                        StreamIdentifier = request.Options.StreamIdentifier,
                                        Error            = Status.AccessDenied
                                    }, cancellationToken).ConfigureAwait(false);

                                    continue;
                                }

                                if (request.Options.StreamIdentifier == null)
                                {
                                    await writer.WriteAsync(new BatchAppendResp {
                                        CorrelationId    = request.CorrelationId,
                                        StreamIdentifier = request.Options.StreamIdentifier,
                                        Error            = Status.BadRequest(
                                            $"Required field {nameof(request.Options.StreamIdentifier)} not set.")
                                    }, cancellationToken).ConfigureAwait(false);

                                    continue;
                                }

                                if (Max(timeout, TimeSpan.Zero) == TimeSpan.Zero)
                                {
                                    await writer.WriteAsync(new BatchAppendResp {
                                        CorrelationId    = request.CorrelationId,
                                        StreamIdentifier = request.Options.StreamIdentifier,
                                        Error            = Status.Timeout
                                    }, cancellationToken).ConfigureAwait(false);

                                    continue;
                                }

                                pendingWrites.AddOrUpdate(correlationId,
                                                          c => FromOptions(c, request.Options, timeout, cancellationToken),
                                                          (_, writeRequest) => writeRequest);
                            }

                            if (!pendingWrites.TryGetValue(correlationId, out var clientWriteRequest))
                            {
                                continue;
                            }

                            clientWriteRequest.AddEvents(request.ProposedMessages.Select(FromProposedMessage));

                            if (clientWriteRequest.Size > _maxAppendSize)
                            {
                                pendingWrites.TryRemove(correlationId, out _);
                                await writer.WriteAsync(new BatchAppendResp {
                                    CorrelationId    = request.CorrelationId,
                                    StreamIdentifier = clientWriteRequest.StreamId,
                                    Error            = Status.MaximumAppendSizeExceeded((uint)_maxAppendSize)
                                }, cancellationToken).ConfigureAwait(false);
                            }

                            if (!request.IsFinal)
                            {
                                continue;
                            }

                            if (!pendingWrites.TryRemove(correlationId, out _))
                            {
                                continue;
                            }

                            Interlocked.Increment(ref _pending);

                            _publisher.Publish(ToInternalMessage(clientWriteRequest, new CallbackEnvelope(message => {
                                try {
                                    writer.TryWrite(ConvertMessage(message));
                                } catch (Exception ex) {
                                    writer.TryComplete(ex);
                                }
                            }), requiresLeader, user, cancellationToken));

                            BatchAppendResp ConvertMessage(Message message)
                            {
                                var batchAppendResp = message switch {
                                    ClientMessage.NotHandled notHandled => new BatchAppendResp {
                                        Error = new Status {
                                            Details = Any.Pack(new Empty()),
                                            Message = (notHandled.Reason, notHandled.AdditionalInfo) switch {
                                                (NotHandledReason.NotReady, _) => "Server Is Not Ready",
                                                (NotHandledReason.TooBusy, _) => "Server Is Busy",
                                                (NotHandledReason.NotLeader or NotHandledReason.IsReadOnly,
                                                 LeaderInfo leaderInfo) =>
                                                throw RpcExceptions.LeaderInfo(leaderInfo.HttpAddress,
                                                                               leaderInfo.HttpPort),
                                                      (NotHandledReason.NotLeader or NotHandledReason.IsReadOnly, _) =>
                                                      "No leader info available in response",
                                                      _ => $"Unknown {nameof(NotHandledReason)} ({(int)notHandled.Reason})"
                                            }
                                        }
                                    },
                                    ClientMessage.WriteEventsCompleted completed => completed.Result switch {
                                        OperationResult.Success => new BatchAppendResp {
                                            Success = BatchAppendResp.Types.Success.Completed(completed.CommitPosition,
                                                                                              completed.PreparePosition, completed.LastEventNumber),
                                        },
                                        OperationResult.WrongExpectedVersion => new BatchAppendResp {
                                            Error = Status.WrongExpectedVersion(
                                                StreamRevision.FromInt64(completed.CurrentVersion),
                                                clientWriteRequest.ExpectedVersion)
                                        },
                                        OperationResult.AccessDenied => new BatchAppendResp
                                        {
                                            Error = Status.AccessDenied
                                        },
                                        OperationResult.StreamDeleted => new BatchAppendResp {
                                            Error = Status.StreamDeleted(clientWriteRequest.StreamId)
                                        },
                                        OperationResult.CommitTimeout or
                                        OperationResult.ForwardTimeout or
                                        OperationResult.PrepareTimeout => new BatchAppendResp
                                        {
                                            Error = Status.Timeout
                                        },
                                        _ => new BatchAppendResp {
                                            Error = Status.Unknown
                                        }
                                    },
                                    _ => new BatchAppendResp {
                                        Error = new Status {
                                            Details = Any.Pack(new Empty()),
                                            Message =
                                                $"Envelope callback expected either {nameof(ClientMessage.WriteEventsCompleted)} or {nameof(ClientMessage.NotHandled)}, received {message.GetType().Name} instead"
                                        }
                                    }
                                };

                                batchAppendResp.CorrelationId    = Uuid.FromGuid(correlationId).ToDto();
                                batchAppendResp.StreamIdentifier = new StreamIdentifier {
                                    StreamName = ByteString.CopyFromUtf8(clientWriteRequest.StreamId)
                                };
                                return(batchAppendResp);
                            }
                        } catch (Exception ex) {
                            await writer.WriteAsync(new BatchAppendResp {
                                CorrelationId    = request.CorrelationId,
                                StreamIdentifier = request.Options.StreamIdentifier,
                                Error            = Status.BadRequest(ex.Message)
                            }, cancellationToken).ConfigureAwait(false);
                        }
                    }
        public override async Task Read(IAsyncStreamReader <ReadReq> requestStream,
                                        IServerStreamWriter <ReadResp> responseStream, ServerCallContext context)
        {
            if (!await requestStream.MoveNext().ConfigureAwait(false))
            {
                return;
            }

            if (requestStream.Current.ContentCase != ReadReq.ContentOneofCase.Options)
            {
                throw new InvalidOperationException();
            }

            var options        = requestStream.Current.Options;
            var user           = context.GetHttpContext().User;
            var connectionName =
                context.RequestHeaders.FirstOrDefault(x => x.Key == Constants.Headers.ConnectionName)?.Value ??
                "<unknown>";
            var    correlationId   = Guid.NewGuid();
            var    uuidOptionsCase = options.UuidOption.ContentCase;
            var    source          = new TaskCompletionSource <bool>();
            string subscriptionId  = default;

            await using var _ = context.CancellationToken.Register(source.SetCanceled).ConfigureAwait(false);

#pragma warning disable 4014
            requestStream.ForEachAsync(HandleAckNack);
#pragma warning restore 4014

            await using var enumerator = new PersistentStreamSubscriptionEnumerator(correlationId, connectionName,
                                                                                    _queue, options.StreamName, options.GroupName, options.BufferSize, user, context.CancellationToken);

            subscriptionId = await enumerator.Started.ConfigureAwait(false);

            await responseStream.WriteAsync(new ReadResp {
                SubscriptionConfirmation = new ReadResp.Types.SubscriptionConfirmation {
                    SubscriptionId = subscriptionId
                }
            }).ConfigureAwait(false);

            while (await enumerator.MoveNextAsync().ConfigureAwait(false))
            {
                await responseStream.WriteAsync(new ReadResp {
                    Event = ConvertToReadEvent(enumerator.Current)
                }).ConfigureAwait(false);
            }

            Task HandleAckNack(ReadReq request)
            {
                _queue.Publish(request.ContentCase switch {
                    ReadReq.ContentOneofCase.Ack => (Message)
                    new ClientMessage.PersistentSubscriptionAckEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Ack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    ReadReq.ContentOneofCase.Nack =>
                    new ClientMessage.PersistentSubscriptionNackEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Nack.Reason, request.Nack.Action switch {
                        ReadReq.Types.Nack.Types.Action.Unknown => NakAction.Unknown,
                        ReadReq.Types.Nack.Types.Action.Park => NakAction.Park,
                        ReadReq.Types.Nack.Types.Action.Retry => NakAction.Retry,
                        ReadReq.Types.Nack.Types.Action.Skip => NakAction.Skip,
                        ReadReq.Types.Nack.Types.Action.Stop => NakAction.Stop,
                        _ => throw new InvalidOperationException()
                    },
                        request.Nack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    _ => throw new InvalidOperationException()
                });
        public override async Task Read(IAsyncStreamReader <ReadReq> requestStream,
                                        IServerStreamWriter <ReadResp> responseStream, ServerCallContext context)
        {
            if (!await requestStream.MoveNext())
            {
                return;
            }

            if (requestStream.Current.ContentCase != ReadReq.ContentOneofCase.Options)
            {
                throw new InvalidOperationException();
            }

            var options = requestStream.Current.Options;
            var user    = await GetUser(_authenticationProvider, context.RequestHeaders);

            var    correlationId  = Guid.NewGuid();
            var    source         = new TaskCompletionSource <bool>();
            string subscriptionId = default;

            context.CancellationToken.Register(source.SetCanceled);

#pragma warning disable 4014
            Task.Run(() => requestStream.ForEachAsync(HandleAckNack));
#pragma warning restore 4014

            await using var enumerator = new PersistentStreamSubscriptionEnumerator(correlationId, _queue,
                                                                                    options.StreamName, options.GroupName, options.BufferSize, user, context.CancellationToken);

            subscriptionId = await enumerator.Started;

            await responseStream.WriteAsync(new ReadResp {
                Empty = new ReadResp.Types.Empty()
            });

            while (await enumerator.MoveNextAsync())
            {
                await responseStream.WriteAsync(new ReadResp {
                    Event = ConvertToReadEvent(enumerator.Current)
                });
            }

            Task HandleAckNack(ReadReq request)
            {
                _queue.Publish(request.ContentCase switch {
                    ReadReq.ContentOneofCase.Ack => (Message)
                    new ClientMessage.PersistentSubscriptionAckEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Ack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    ReadReq.ContentOneofCase.Nack =>
                    new ClientMessage.PersistentSubscriptionNackEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Nack.Reason, request.Nack.Action switch {
                        ReadReq.Types.Nack.Types.Action.Unknown => NakAction.Unknown,
                        ReadReq.Types.Nack.Types.Action.Park => NakAction.Park,
                        ReadReq.Types.Nack.Types.Action.Retry => NakAction.Retry,
                        ReadReq.Types.Nack.Types.Action.Skip => NakAction.Skip,
                        ReadReq.Types.Nack.Types.Action.Stop => NakAction.Stop,
                        _ => throw new InvalidOperationException()
                    },
                        request.Nack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    _ => throw new InvalidOperationException()
                });