Giter Site home page Giter Site logo

eventstore / eventstore-client-dotnet Goto Github PK

View Code? Open in Web Editor NEW
134.0 34.0 38.0 2.52 MB

Dotnet Client SDK for the Event Store gRPC Client API written in C#

License: Other

C# 99.76% Shell 0.14% PowerShell 0.10%
cqrs event-sourcing eventsourcing eventstore eventstoredb client-libraries dotnet grpc

eventstore-client-dotnet's Introduction

EventStoreDB

EventStoreDB is the event-native database, where business events are immutably stored and streamed. Designed for event-sourced, event-driven, and microservices architectures

What is EventStoreDB

EventStoreDB is a new category of operational database that has evolved from the Event Sourcing community. Powered by the state-transition data model, events are stored with the context of why they have happened. Providing flexible, real-time data insights in the language your business understands.

Download the latest version. For more product information visit the website.

What is Event Store Cloud?

Event Store Cloud is a fully managed cloud offering that's designed to make it easy for developers to build and run highly available and secure applications that incorporate EventStoreDB without having to worry about managing the underlying infrastructure. You can provision EventStoreDB clusters in AWS, Azure, and GCP, and connect these services securely to your own cloud resources.

For more details visit the website.

Licensing

View Event Store Ltd's licensing information.

Docs

For guidance on installation, development, deployment, and administration, see the User Documentation.

Getting started with EventStoreDB

Follow the getting started guide.

Getting started with Event Store Cloud

Event Store can manage EventStoreDB for you, so you don't have to run your own clusters. See the online documentation: Getting started with Event Store Cloud.

Client libraries

This guide shows you how to get started with EventStoreDB by setting up an instance or cluster and configuring it. EventStoreDB supports two protocols: gRPC and TCP(legacy).

EventStoreDB supported gRPC clients

Community supported gRPC clients

Read more in the documentation.

Legacy TCP Clients (support ends with 23.10 LTS)

Deployment

Communities

Contributing

Development is done on the master branch. We attempt to do our best to ensure that the history remains clean and to do so, we generally ask contributors to squash their commits into a set or single logical commit.

If you want to switch to a particular release, you can check out the release branch for that particular release. For example:
git checkout release/oss-v22.10

Building EventStoreDB

EventStoreDB is written in a mixture of C# and JavaScript. It can run on Windows, Linux and macOS (using Docker) using the .NET Core runtime.

Prerequisites

Once you've installed the prerequisites for your system, you can launch a Release build of EventStore as follows:

dotnet build -c Release src

The build scripts: build.sh and build.ps1 are also available for Linux and Windows respectively to simplify the build process.

To start a single node, you can then run:

dotnet ./src/EventStore.ClusterNode/bin/x64/Release/net8.0/EventStore.ClusterNode.dll --dev --db ./tmp/data --index ./tmp/index --log ./tmp/log

Running the tests

You can launch the tests as follows:

dotnet test src/EventStore.sln

Build EventStoreDB Docker image

You can also build a Docker image by running the command:

docker build --tag myeventstore . \
--build-arg CONTAINER_RUNTIME={container-runtime}
--build-arg RUNTIME={runtime}

For instance:

docker build --tag myeventstore . \
--build-arg CONTAINER_RUNTIME=bookworm-slim \
--build-arg RUNTIME=linux-x64

Note: Because of the Docker issue, if you're building a Docker image on Windows, you may need to set the DOCKER_BUILDKIT=0 environment variable. For instance, running in PowerShell:

$env:DOCKER_BUILDKIT=0; docker build --tag myeventstore . `
--build-arg CONTAINER_RUNTIME=bookworm-slim `
--build-arg RUNTIME=linux-x64

Currently, we support the following configurations:

  1. Bookworm slim:
  • CONTAINER_RUNTIME=bookworm-slim
  • RUNTIME=linux-x64
  1. Jammy:
  • CONTAINER_RUNTIME=Jammy
  • RUNTIME=linux-x64
  1. Alpine:
  • CONTAINER_RUNTIME=alpine
  • RUNTIME=linux-musl-x64

You can verify the built image by running:

docker run --rm myeventstore --insecure --what-if

Need help?

eventstore-client-dotnet's People

Contributors

321zer0 avatar alexeyzimarev avatar artiomchi avatar arwinneil avatar avish0694 avatar brunozell avatar condron avatar danielmarbach avatar dependabot[bot] avatar dharmaturtle avatar hayley-jean avatar jageall avatar jen20 avatar josephcummings avatar martinothamar avatar mat-mcloughlin avatar nielspilgaard avatar nikodemmazur avatar oskardudycz avatar pgermishuys avatar pvanbuijtene avatar ragingkore avatar shaan1337 avatar tarmil avatar thefringeninja avatar timothycoleman avatar w1am avatar ylorph avatar yoeight avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

eventstore-client-dotnet's Issues

I get WrongExpectedVersionException when I send two requests with minimal delay in production cluster

Describe the bug
I get WrongExpectedVersionException when I send two requests with minimal delay (50ms - 500ms) in production cluster. I use optimistic concurrency.

To Reproduce
Steps to reproduce the behavior:

  1. A server get request and create a new stream.
  2. A server gets two requests with minimal delay and he try save the stream event from request to EventStore.
  3. Sometimes a server get errors from EventStore that WrongExpectedVersionException.

Expected behavior
Events save without errors.

Actual behavior
Sometimes I get error when send two requests with minimal delay.

Config/Logs/Screenshots
EventStore.ClientAPI.Exceptions.WrongExpectedVersionException: Append failed due to WrongExpectedVersion. Stream: xx.XXX-c3226e4300ab48c8b3395cc81ba69aa4, Expected version: 2, Current version: 3

EventStore details

  • EventStore server version: 4.1.2
  • EventStore cluster has 2 slave, 1 master
  • Every EventStore database takes up 30 GB
  • Operating system: Linux
  • EventStore client version (if applicable): Version 4.1.2

Additional context
I use ASP.NET Core 2.2 on Docker, Kubernates.

Suggested API Changes to Reads

Since IAsyncEnumerable streams results in, there are not many use cases for reading part of a stream. By default, a read should give you everything.

The client produces the credentials warning even though credentials aren't supplied

When using the following connection string for a local ESDB instance running in Docker:

esdb://localhost:2113?tls=false

the client still produces the warning:

The configured CallCredentials were not used because the call does not use TLS.

Expected behaviour:
No warning is shown as I don't supply any credentials when connecting to an insecure ESDB instance.

Update Grpc.Net.Client NuGet package to 2.28.0+

Describe the bug
Currently, there are error log spam for Call failed with gRPC error status. which is caused by old Grpc.Net.Client code.

You can see the code changed 5 months ago: https://github.com/grpc/grpc-dotnet/blame/master/src/Grpc.Net.Client/Internal/GrpcCallLog.cs

Expected behavior
Log Call failed with gRPC error status. as info

Actual behavior
Log Call failed with gRPC error status. as error

EventStore details

  • EventStore client version (if applicable): EventStore.Client.Grpc.Streams 20.6.0

Additional context
The issue is documented here: grpc/grpc-dotnet#767
The PR is here: grpc/grpc-dotnet#777
The fix released here: https://github.com/grpc/grpc-dotnet/releases/tag/v2.28.0-pre1

Align catch-up and persistent subscription Subscribe

Catch-up subscriptions have the ability to tweak the client operation options:

public Task<StreamSubscription> SubscribeToStreamAsync(
...
      Action<EventStoreClientOperationOptions>? configureOperationOptions = null,
...
    {
      EventStoreClientOperationOptions operationOptions = this.Settings.OperationOptions.Clone();
      if (configureOperationOptions != null)
        configureOperationOptions(operationOptions);
      return this.SubscribeToStreamAsync(streamName, eventAppeared, operationOptions, resolveLinkTos, subscriptionDropped, userCredentials, cancellationToken);
    }

However, for persistent subscriptions, there is no such possibility, it just clones the client settings, as in the snippet above, but doesn't provide any options to tweak the settings.

Is it by design, why can one tweak client operation options for catch-up subscriptions, but not for persistent subscriptions?

Exception 'The SSL connection could not be established' when trying to subscribe to a stream

Describe the bug
When disabling tls validation, the exception The SSL connection could not be established. is still thrown when trying to subscribe to a stream by invoking SubscribeToStreamAsync().

To Reproduce
Steps to reproduce the behavior:

  1. Implement CreateHttpMessageHandler to disable tls certificate validation as described here
  2. Invoke SubscribeToStreamAsync()

Expected behavior
No exception won't be thrown on SubscribeToStreamAsync and other methods,

Actual behavior
Exception: The SSL connection could not be established. is thrown on invoking SubscribeToStreamAsync().
Other requests like appending events on stream do honor the skipping of the certificate validation.

EventStore details

  • EventStore server version: 20.6

  • Operating system:
    MacOS / With running EventStore Service inside Docker Containers. I do have the generated certificates inside the Docker container added to my keychain, so even without skipping the TLS certificate validation other methods than SubscribeToStreamAsync() do work perfectly.

  • EventStore client version (if applicable):
    Latest stable version.

GRPC client - SubscribeToStreamAsync with position 0 skips event with original number 0

Describe the bug
When subscribing to a stream from position 0, the event with original number 0 will not be processed.

To Reproduce
Steps to reproduce the behavior:

  1. Run EVSDb 21.2.0 buster-slim docker image
  2. Append an event to stream "hello-world"
  3. Use the EventStore.Client.Grpc.Streams nuget package
  4. Run a subscriber
var sub = await _reader.SubscribeToStreamAsync(
	streamName: "hello-world",
	start: StreamPosition.FromInt64(0),
	eventAppeared: async (subscription, @event, c) =>
	{
		using (LogContext.PushProperty("stream-id", subscription.SubscriptionId))
		using (LogContext.PushProperty("event-id", @event.Event.EventId))
		{
			_logger.Debug("Processing event to stream {0}", stream);
			await _eventsProcessor.Process(@event, c);
		}
	},
	resolveLinkTos: false,
	subscriptionDropped: (subscription, reason, exception) =>
	{
		_logger.Warning(exception, "Subscription dropped {0}.", stream);
	},
	cancellationToken: cancellationToken);

Expected behavior
The event created in step .2 will to be processed

Actual behavior
The event never appears

Config/Logs/Screenshots
Here you can see there are 3 events in my stream but only 2 processed. Always the event with original number 0 is skipped (never shows up on the delegate)
image

EventStore details

  • EventStore server version:
    21.2.0-buster-slim on docker
  • Operating system:
    Windows 10
  • EventStore client version (if applicable):
    EventStore.Client.Grpc.Streams 21.2.0

Additional context
I have tried with StreamPosition.Start and same behavior.

How to list persisted subscriptions

Hello guys,

Our team migrates from v5.x to 20.x of EventStoreDB.
We got several issues during migration. One of them is to get the list of the persisted subscriptions.

Version 5.x contains EventStorePersistentSubscriptionsClient with method List to get the list of persisted subscriptions.
In version 20.x we could not find a similar method.

Could you help with this issue?

Thanks,
Serhii

Projection - filter out deleted streams

Hello
I have deleted some streams but I'm still getting those streams when my projection starts.

Is there a way to filter out any deleted streams from projections?

TIA

Projection - filter deleted streams

Hello
I have deleted some streams but I'm still getting those streams when my projection starts.

Is there a way to filter out any deleted streams from projections?

TIA

IAsyncEnumerable for subscriptions

First of all, great work on the new stuff!

I've noticed the new changes are using IAsyncEnumerable. StreamSubscription is also using it, but only internally as far as I can see.

I was hoping the new API would return IAsyncEnumerable for subscriptions as well, but it doesn't. I've used several API's that are now returning IAsyncEnumerable and I love it! Would it be possible to also add support for it in when using subscriptions?

Thanks!

DEV-88

netstandard2.1 instead of netcoreapp3.1 ?

Is your feature request related to a problem? Please describe.
We are updating Event Store from 5.0.8 to 20.6.0 and would like to start using gRPC instead since we get deprecation warning.

DEPRECATION WARNING: The Legacy TCP Client Interface has been deprecated as of version 20.02.

However, we are not able to installing EventStore.Client.Grpc to our netstandard2.1 project.

Is there any limitation or reason that this is not released as netstandard2.1 NuGet package?

Describe the solution you'd like
Refactor to netstandard2.1, I saw someone already did it, but seems no action within a month #19

Describe alternatives you've considered
Or update the existing EventStore.Client NuGet package to support gRPC?

Additional context

  GET http://nuget.executivecentre.net/flatcontainer/eventstore.client.grpc/index.json
  GET https://api.nuget.org/v3-flatcontainer/eventstore.client.grpc/index.json
  NotFound http://nuget.executivecentre.net/flatcontainer/eventstore.client.grpc/index.json 107ms
  OK https://api.nuget.org/v3-flatcontainer/eventstore.client.grpc/index.json 216ms
  GET https://api.nuget.org/v3-flatcontainer/eventstore.client.grpc/20.6.0/eventstore.client.grpc.20.6.0.nupkg
  OK https://api.nuget.org/v3-flatcontainer/eventstore.client.grpc/20.6.0/eventstore.client.grpc.20.6.0.nupkg 770ms
Installing EventStore.Client.Grpc 20.6.0.
NU1202: Package EventStore.Client.Grpc 20.6.0 is not compatible with netstandard2.1 (.NETStandard,Version=v2.1). Package EventStore.Client.Grpc 20.6.0 supports: netcoreapp3.1 (.NETCoreApp,Version=v3.1)
Package restore failed. Rolling back package changes for 'The.NetStandard2Dot1.Project'.
Time Elapsed: 00:00:02.4560139

Projections API, missing functionality

Describe the bug

The v20.6.0 projection management API does not expose the ability to:

  • Create a projection with emitEnabled
  • Retrieve the JS query of a projection

To Reproduce N/A

Expected behavior N/A

Actual behavior N/A

Config/Logs/Screenshots N/A

EventStore details

  • EventStore server version: v20.6.0
  • Operating system: Win 10 x64
  • EventStore client version (if applicable): v20.6.0

Additional context

A workaround for emitEnabled is to update immediately following creation:

await _client.CreateContinuousAsync(projectionName, sourceText);
await _client.UpdateAsync(projectionName, sourceText, emitEnabled: true);

AppendToStreamAsync fails with client 20.6.1 under netcoreapp3.1

Describe the bug
AppendToStreamAsync example (https://github.com/EventStore/EventStore.Samples.Dotnet/blob/master/WritingEvents/Program.cs) does not work when hosted within a netcoreapp3.1 application with EventStore.Client 20.6.1 (no events written to target stream). Console logs say that connection gets established and then closed.

Same sample does work under netcoreapp3.1 and against the same EventStore service, however with net40 EventStore.Client.3.4.0 assembly used instead (100 events written to the target stream; visible via UI).

Perhaps I don't have required feature enabled at the EventStore service (please examind step 1 below first; thank you)?

To Reproduce
Steps to reproduce the behavior:

  1. Launch EventStore service at Docker; I'm not sure which ES version tag "latest" really is
docker volume create eventstore-data
docker volume create eventstore-logs
docker volume create eventstore-index

docker run \
	--name "eventstore"\
	--volume "eventstore-data:/var/lib/eventstore" \
	--volume "eventstore-logs:/var/log/eventstore" \
	--volume "eventstore-index:/var/lib/eventstore/index" \
	--hostname "eventstore" \
	--network "bridge" \
	--publish "1113:1113" \
	--publish "2113:2113" \
	--env "EVENTSTORE_CLUSTER_SIZE=1" \
	--env "EVENTSTORE_SKIP_DB_VERIFY=true" \
	--env "EVENTSTORE_RUN_PROJECTIONS=All" \
	--env "EVENTSTORE_START_STANDARD_PROJECTIONS=true" \
	--env "EVENTSTORE_ENABLE_EXTERNAL_TCP=true" \
	--env "EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true" \
	--env "EVENTSTORE_EXT_TCP_PORT=1113" \
	--env "EVENTSTORE_EXT_HTTP_PORT=2113" \
	--restart "unless-stopped" \
	--detach \
	"eventstore/eventstore:latest" --insecure
  1. Use example https://github.com/EventStore/EventStore.Samples.Dotnet/blob/master/WritingEvents/Program.cs with EventStore.Client 20.6.1 under netcoreapp3.1.

Expected behavior
dotnet project file for working scenario (copy of sample project; AssemblyInfo.cs removed):

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.9" />
    <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.9" />
  </ItemGroup>

  <ItemGroup>
    <Reference Include="EventStore.ClientAPI">
      <HintPath>..\packages\EventStore.Client.3.4.0\lib\net40\EventStore.ClientAPI.dll</HintPath>
    </Reference>
  </ItemGroup>

</Project>

Actual behavior
dotnet project file for non-working scenario (copy of sample project; AssemblyInfo.cs removed):

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="EventStore.Client" Version="20.6.1" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
    <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
  </ItemGroup>

</Project>

For comparison, the sample project won't work (same symptoms) when targeting .NET Framework 4.5.2 and using EventStore.Client 20.6.1 in compatible build. So, with these 2 declarations in their relevant locations (csproj; packages.config):

<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<package id="EventStore.Client" version="20.6.1" targetFramework="net452" />

Config/Logs/Screenshots

[01,22:26:46.421,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': enqueueing message EventStore.ClientAPI.Internal.StartConnectionMessage..
[06,22:26:46.444,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': StartConnection.
[06,22:26:46.445,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': DiscoverEndPoint.
[06,22:26:46.447,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': enqueueing message EventStore.ClientAPI.Internal.EstablishTcpConnectionMessage..
[04,22:26:46.959,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': EstablishTcpConnection to [127.0.0.1:1113].
[01,22:27:12.347,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': enqueueing message EventStore.ClientAPI.Internal.StartOperationMessage..
[04,22:27:12.413,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': StartOperation enqueue AppendToStreamOperation, Stream: a_test_stream, ExpectedVersion: -2, 0, 00:00:07..
[04,22:27:12.413,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': EnqueueOperation WAITING for Operation AppendToStreamOperation (2c7401ba-0dd6-47d0-8b12-b2b541e3a46e): Stream: a_test_stream, ExpectedVersion: -2, retry count: 0, created: 22:27:12.413, last updated: 22:27:12.413..
[07,22:27:12.488,DEBUG] TcpPackageConnection: connected to [127.0.0.1:1113, L127.0.0.1:63479, {84415501-7e60-417e-adac-8d40a868da9f}].
[07,22:27:12.488,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': enqueueing message EventStore.ClientAPI.Internal.TcpConnectionEstablishedMessage..
[07,22:27:12.489,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': TCP connection to [127.0.0.1:1113, L127.0.0.1:63479, {84415501-7e60-417e-adac-8d40a868da9f}] established..
[07,22:27:12.490,DEBUG] IdentifyClient; Client Version: 1, ConnectionName: ES-7f5e3a5e-e70d-46bb-b018-250a88916e51,
[08,22:27:14.927,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63479, 84415501-7e60-417e-adac-8d40a868da9f] due to HEARTBEAT TIMEOUT at pkgNum 0.
[08,22:27:14.927,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection.
[08,22:27:14.928,INFO] ClientAPI TcpConnectionSsl closed [22:27:14.928: S127.0.0.1:1113, L127.0.0.1:63479, {84415501-7e60-417e-adac-8d40a868da9f}]:
[08,22:27:14.929,INFO] Received bytes: 0, Sent bytes: 0
[08,22:27:14.929,INFO] Send calls: 0, callbacks: 0
[08,22:27:14.929,INFO] Receive calls: 0, callbacks: 0
[08,22:27:14.930,INFO] Close reason: [Success] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63479, 84415501-7e60-417e-adac-8d40a868da9f] due to HEARTBEAT TIMEOUT at pkgNum 0.
[08,22:27:14.932,DEBUG] TcpPackageConnection: connection [127.0.0.1:1113, L127.0.0.1:63479, {84415501-7e60-417e-adac-8d40a868da9f}] was closed cleanly.
[08,22:27:14.932,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': enqueueing message EventStore.ClientAPI.Internal.TcpConnectionClosedMessage..
[08,22:27:14.933,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': TCP connection to [127.0.0.1:1113, L127.0.0.1:63479, {84415501-7e60-417e-adac-8d40a868da9f}] closed..
[08,22:27:14.935,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': TCP connection to [127.0.0.1:1113, L127.0.0.1:63479, {84415501-7e60-417e-adac-8d40a868da9f}] closed..
[08,22:27:15.126,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': TimerTick checking reconnection....
[08,22:27:15.145,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': ExecuteOperation package WriteEvents, 2c7401ba-0dd6-47d0-8b12-b2b541e3a46e, Operation AppendToStreamOperation (2c7401ba-0dd6-47d0-8b12-b2b541e3a46e): Stream: a_test_stream, ExpectedVersion: -2, retry count: 0, created: 22:27:12.413, last updated: 22:27:15.131..
[08,22:27:15.148,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': DiscoverEndPoint.
[08,22:27:15.148,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': enqueueing message EventStore.ClientAPI.Internal.EstablishTcpConnectionMessage..
[07,22:27:15.148,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': EstablishTcpConnection to [127.0.0.1:1113].
[04,22:27:15.150,DEBUG] TcpPackageConnection: connected to [127.0.0.1:1113, L127.0.0.1:63480, {16adab56-836c-4799-a6b6-1bd0f400ed52}].
[04,22:27:15.150,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': enqueueing message EventStore.ClientAPI.Internal.TcpConnectionEstablishedMessage..
[04,22:27:15.151,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': TCP connection to [127.0.0.1:1113, L127.0.0.1:63480, {16adab56-836c-4799-a6b6-1bd0f400ed52}] established..
[04,22:27:15.151,DEBUG] IdentifyClient; Client Version: 1, ConnectionName: ES-7f5e3a5e-e70d-46bb-b018-250a88916e51,
[11,22:27:17.527,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[11,22:27:17.527,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[11,22:27:17.730,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[11,22:27:17.730,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[07,22:27:17.934,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[07,22:27:17.934,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[07,22:27:18.139,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[07,22:27:18.139,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[04,22:27:18.337,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[04,22:27:18.338,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[11,22:27:18.527,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[11,22:27:18.527,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[11,22:27:18.729,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[11,22:27:18.729,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[04,22:27:18.935,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[04,22:27:18.935,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[04,22:27:19.138,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[04,22:27:19.138,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[08,22:27:19.342,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[08,22:27:19.342,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[07,22:27:19.527,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[07,22:27:19.527,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[08,22:27:19.733,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[08,22:27:19.733,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[08,22:27:19.937,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[08,22:27:19.937,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[08,22:27:20.136,INFO] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': closing TCP connection [127.0.0.1:1113, 127.0.0.1:63480, 16adab56-836c-4799-a6b6-1bd0f400ed52] due to HEARTBEAT TIMEOUT at pkgNum 0.
[08,22:27:20.136,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': CloseTcpConnection IGNORED because was closed.
[09,22:27:20.219,INFO] [S127.0.0.1:1113, L127.0.0.1:63480]: Exception on EndAuthenticateAsClient.
EXCEPTION(S) OCCURRED:
System.IO.IOException:  Received an unexpected EOF or 0 bytes from the transport stream.
   at System.Net.FixedSizeReader.ReadPacketAsync(Stream transport, AsyncProtocolRequest request)
   at System.Net.Security.SslStream.ThrowIfExceptional()
   at System.Net.Security.SslStream.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Net.Security.SslStream.EndProcessAuthentication(IAsyncResult result)
   at System.Net.Security.SslStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
   at EventStore.ClientAPI.Transport.Tcp.TcpConnectionSsl.OnEndAuthenticateAsClient(IAsyncResult ar)

[09,22:27:20.220,INFO] ClientAPI TcpConnectionSsl closed [22:27:20.220: S127.0.0.1:1113, L127.0.0.1:63480, {16adab56-836c-4799-a6b6-1bd0f400ed52}]:
[09,22:27:20.221,INFO] Received bytes: 0, Sent bytes: 0
[09,22:27:20.221,INFO] Send calls: 0, callbacks: 0
[09,22:27:20.221,INFO] Receive calls: 0, callbacks: 0
[09,22:27:20.224,INFO] Close reason: [SocketError]  Received an unexpected EOF or 0 bytes from the transport stream.
[09,22:27:20.226,DEBUG] TcpPackageConnection: connection [127.0.0.1:1113, L127.0.0.1:63480, {16adab56-836c-4799-a6b6-1bd0f400ed52}] was closed with error: SocketError.
[09,22:27:20.226,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': enqueueing message EventStore.ClientAPI.Internal.TcpConnectionClosedMessage..
[11,22:27:20.226,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': TCP connection to [127.0.0.1:1113, L127.0.0.1:63480, {16adab56-836c-4799-a6b6-1bd0f400ed52}] closed..
[11,22:27:20.342,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': TimerTick checking reconnection....
[11,22:27:20.343,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': RemoveOperation SUCCEEDED for Operation AppendToStreamOperation (2c7401ba-0dd6-47d0-8b12-b2b541e3a46e): Stream: a_test_stream, ExpectedVersion: -2, retry count: 0, created: 22:27:12.413, last updated: 22:27:15.131.
[11,22:27:20.347,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': ScheduleOperationRetry for Operation AppendToStreamOperation (2c7401ba-0dd6-47d0-8b12-b2b541e3a46e): Stream: a_test_stream, ExpectedVersion: -2, retry count: 0, created: 22:27:12.413, last updated: 22:27:15.131.
[11,22:27:20.348,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': DiscoverEndPoint.
[11,22:27:20.348,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': enqueueing message EventStore.ClientAPI.Internal.EstablishTcpConnectionMessage..
[11,22:27:20.349,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': EstablishTcpConnection to [127.0.0.1:1113].
[11,22:27:20.400,DEBUG] TcpPackageConnection: connected to [127.0.0.1:1113, L127.0.0.1:63482, {8b165f51-e9ba-435c-b323-97887de34d6e}].
[11,22:27:20.400,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': enqueueing message EventStore.ClientAPI.Internal.TcpConnectionEstablishedMessage..
[11,22:27:20.401,DEBUG] EventStoreConnection 'ES-7f5e3a5e-e70d-46bb-b018-250a88916e51': TCP connection to [127.0.0.1:1113, L127.0.0.1:63482, {8b165f51-e9ba-435c-b323-97887de34d6e}] established..
[11,22:27:20.403,DEBUG] IdentifyClient; Client Version: 1, ConnectionName: ES-7f5e3a5e-e70d-46bb-b018-250a88916e51,

EventStore details

"ES VERSION:"             "20.6.1.0" ("tags/oss-v20.6.1"/"9ea108855", "Unknown"),
[    1, 1,06:53:08.930,INF] "RUNTIME:"                ".NET 3.1.9" (64-bit),
[    1, 1,06:53:08.928,INF] "OS:"                     Linux ("Unix 4.15.0.123")

Checkpoint Reached not behaving as expected

I've noticed two problems with the checkpoint reached when using subscribe to all in the Grpc client

  • The prepare value on the position is always zero. Commit is populated however.
  • If no interval value is supplied then the the delegate is just called repeatedly
await client.SubscribeToAllAsync(Position.Start,
    (s, e, c) =>
    {
        Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}");
        return Task.CompletedTask;
    },
    filterOptions: new SubscriptionFilterOptions(
        EventTypeFilter.ExcludeSystemEvents(),
        checkpointReached: (s, p, c) =>
        {
            Console.WriteLine($"checkpoint taken at {p.PreparePosition}");
            return Task.CompletedTask;
        })
);

Retry feature in event store persistence subscription stops working after first retry

Describe the bug
I found that explicitly retry an event in persistence subscription only worked once and then the subscription just stopped processing it.

To Reproduce
Here's sample code to reproduce it

using EventStore.Client;
using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace EventStoreSubscriptionTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Task.WaitAll(SubscribeAsync(default));
            Console.WriteLine("End");
        }
        private static async Task SubscribeAsync(CancellationToken token)
        {
            const string streamName = "TestStream";
            const string groupName = "TestGroup";

            var operationOptions = EventStoreClientOperationOptions.Default;
            operationOptions.ThrowOnAppendFailure = true;
            operationOptions.TimeoutAfter = null;

            var client = new EventStorePersistentSubscriptionsClient(
                new EventStoreClientSettings
                {
                    ConnectionName = "",
                    ConnectivitySettings = { Address = new Uri("https://localhost:2113") },
                    DefaultCredentials = new UserCredentials("admin", "changeit"),
                    CreateHttpMessageHandler = () =>
                        new HttpClientHandler
                        {
                            ServerCertificateCustomValidationCallback = (message, certificate2, x509Chain, sslPolicyErrors) => true
                        },
                    OperationOptions = operationOptions,
                    LoggerFactory = LoggerFactory.Create(cfg => cfg.AddConsole())
                });

            try
            {
                await client.CreateAsync(
                    streamName,
                    groupName,
                    new PersistentSubscriptionSettings(),
                    cancellationToken: token);

                Console.WriteLine($"[{DateTime.Now}] Subscription created.");
            }
            catch (Exception)
            {
                Console.WriteLine($"[{DateTime.Now}] Subscription exists.");
            }

            await client.SubscribeAsync(
                streamName,
                groupName,
                OnEventAppeared,
                async (sub, reason, ex) => await OnSubscriptionDropped(client, streamName, groupName, sub, reason, ex),
                cancellationToken: token);

            Console.WriteLine($"[{DateTime.Now}] Subscription connected.");

            while (true)
            {
                await Task.Delay(TimeSpan.FromHours(1), token);
            }
        }

        private static async Task OnEventAppeared(
            PersistentSubscription subscription,
            ResolvedEvent evt,
            int? count,
            CancellationToken token)
        {
            Console.WriteLine($"[{DateTime.Now}] [{count}]: Type: {evt.Event.EventType}.");
            await Task.Delay(5000, token);
            await subscription.Nack(PersistentSubscriptionNakEventAction.Retry, "to be retried", evt);
        }

        private static async Task OnSubscriptionDropped(
            EventStorePersistentSubscriptionsClient client,
            string streamName,
            string groupName,
            PersistentSubscription subscription,
            SubscriptionDroppedReason reason,
            Exception? ex)
        {
            Console.WriteLine($"[{DateTime.Now}] Dropped for '{reason}'. Reconnecting ...");

            await client.SubscribeAsync(
                streamName,
                groupName,
                OnEventAppeared,
                async (sub, reason, ex) => await OnSubscriptionDropped(client, streamName, groupName, sub, reason, ex));

            Console.WriteLine($"[{DateTime.Now}] Subscription reconnected.");
        }
    }
}

Expected behavior
I expect the event to be continuously retried until it reaches the max retry count

Actual behavior
Retry stopped after first execution

Config/Logs/Screenshots
Here's the subscription configuration showing on event store admin panel
image

Here's the logs from event store console
image

Here's the logs from program execution
image

EventStore details

  • EventStore server version:
    20.6.1.0
  • Operating system:
    Windows 10 Pro 20H2
  • EventStore client version (if applicable):
    EventStore.Client.Grpc.PersistentSubscriptions 20.6.1

Rename CheckpointInterval to CheckpointIntervalMultiplier

This is a breaking change and needs to be done in the next major version:

https://github.com/EventStore/EventStore-Client-Dotnet/blob/master/src/EventStore.Client.Streams/SubscriptionFilterOptions.cs#L19

https://github.com/EventStore/EventStore-Client-Dotnet/blob/master/src/EventStore.Client.Streams/SubscriptionFilterOptions.cs#L37

The checkpoint interval is actually multiplied by the max search window to determine the number of events after which to checkpoint.

Separate "batch" API for appends using `RepeatedList<EventData>`

Is your feature request related to a problem? Please describe.
No problem, but interested to ask the question since I see significant throughput and latency drop from TCP -> gRPC (which is somewhat expected of course).

Describe the solution you'd like
A second set of functions for stream appends where the client doesn't append to a gRPC stream, but rather sends a single message with a repeated list of EventData objects.

Describe alternatives you've considered
Just using TCP if the perf drop is too serious, but seems like you want to deprecate and remove the TCP client at some point.

Additional context
The current API opens a gRPC stream, first writing the app-level headers to the stream, then each message one by one. One scenario that benefits from this is since you pass in IEnumerable<EventData> you can create your own enumerator which serializes your objects one by one reusing buffers between each MoveNext, which makes for efficient memory allocation, so I guess this approach fits better when you have lots of events/several big events to send. Though if you only have small events and/or few events, this is very inefficient, and you would rather serialize the batch of event(s) all at once and send it in a single unary gRPC request. If I understand gRPC/networking bits correctly that would result in a single WriteAsync + FlushAsync call to the socket instead of N + 1 (N events + headers).

I have no hard numbers on the traditional "append to aggregate stream", but it is slower then TCP of course, but would be nice if there was less of a difference at least. One scenario I do have some numbers on is this:

Continuous replication of $all into another instance of EventStore. 1 thread reading from $all, posting into an inmemory buffer with a buffer size of 8k, then another thread reading batches from the buffer, trying to send a batch per stream to the new EventStore instance. Tested both gRPC and TCP client against the target replication instance and there was a tenfold difference in throughput. 30/s gRPC -> 330/s TCP. Obviously this scenario is much better suited to raw TCP, but would be nice if there was less of a perf drop of course.

Have you discussed these things yourselves yet, do you have any input on this?

SetSystemSettingsAsync throw EventStore.Client.AccessDeniedException when connecting with ops but using admin credentials in request

Describe the bug
Creating a connection with Ops and then calling SetSystemSettingsAsync with Admin credentials throws AccessDeniedException when it should not.

To Reproduce
Steps to reproduce the behavior:

  1. Establish a connection with Ops
  2. Call client.SetSystemSettingsAsync(systemSettings, new UserCredentials("admin", "changeit"))
  3. AccessDeniedException

Expected behavior
Operation should complete successfully.

Actual behavior

Client Side verbose logs:

14:45:53 DBG] Append to stream - $settings@Any.
[14:45:53 DBG] Starting gRPC call. Method type: 'ClientStreaming', URI: 'https://188.166.177.208:2113/event_store.client.streams.Streams/Append'.
[14:45:53 VRB] Starting deadline timeout. Duration: 00:00:04.9831440.
[14:45:54 DBG] Sending message.
[14:45:54 VRB] Serialized 'EventStore.Client.Streams.AppendReq' to 17 byte message.
[14:45:54 VRB] Message sent.
[14:45:54 VRB] Response headers received.
[14:45:54 INF] Call failed with gRPC error status. Status code: 'PermissionDenied', Message: 'Access Denied'.
[14:45:54 DBG] Finished gRPC call.
[14:45:54 VRB] Appending event to stream - { "streamName": "JHNldHRpbmdz" }@69a6738a-b59d-4103-a048-d682cd32138a $settings.
[14:45:54 ERR] Error writing message.
System.InvalidOperationException: Can't write the message because the call is complete.
[14:45:54 DBG] Completing client stream.
Unhandled exception. System.AggregateException: One or more errors occurred. (Status(StatusCode="PermissionDenied", Detail="Access Denied"))
---> EventStore.Client.AccessDeniedException: Status(StatusCode="PermissionDenied", Detail="Access Denied")
---> Grpc.Core.RpcException: Status(StatusCode="PermissionDenied", Detail="Access Denied")
--- End of inner exception stack trace ---
at EventStore.Client.Interceptors.TypedExceptionInterceptor.b__5_0[TRequest,TResponse](Task1 t) at System.Threading.Tasks.ContinuationResultTaskFromResultTask2.InnerInvoke()
at System.Threading.Tasks.Task.<>c.<.cctor>b__277_0(Object obj)
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
at EventStore.Client.EventStoreClient.AppendToStreamInternal(AppendReq header, IEnumerable`1 eventData, EventStoreClientOperationOptions operationOptions, UserCredentials userCredentials, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.Wait()
at ticket109639.Program.Main(String[] args)

Server Side logs:
Failed authorization check for "ops" in 00:30:37.3085738 with "streams : write p: {streamId : $settings} Deny : Policy : Legacy 1 12/31/9999 23:59:59 +00:00 : default:denied by default:Deny, $"

EventStore details

  • EventStore server version: 20.10.2

  • Operating system: MacOS

  • EventStore client version (if applicable): 21.2

`ReadStreamAsync` produces an error log entry if the result stream is not enumerated

Originated by: https://discuss.eventstore.com/t/deadlineexceeded-errors/2737

Code to reproduce the issue:

public async Task<bool> Exists(string stream) {
    await using var result = _client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1);
    var state = await result.ReadState;
    return state != ReadState.StreamNotFound;
}

Since we're only interested in the read result and aren't enumerating the stream, gRPC client closes the stream after timeout exceeds.

Here is the log:

warn: Grpc.Net.Client.Internal.GrpcCall[7]
      gRPC call deadline exceeded.
fail: Grpc.Net.Client.Internal.GrpcCall[3]
      Call failed with gRPC error status. Status code: 'DeadlineExceeded', Message: ''.

The code still works but under load, such an app will produce lots of false warnings and errors in the logs.

The code snippet above disposes the enumerator but it doesn't seem to help.

SubscribeToStreamAsync will start with the next event after a given position

Describe the bug
SubscribeToStreamAsync with StreamPosition.Start will start from the second event in the stream

When subscribing from StreamPosition.Start, the first event delivered to eventAppeared will be the second event in the stream.

To Reproduce
Steps to reproduce the behavior:

  1. Append 10 events to the stream
  2. Subscribe to the same stream from Start
  3. Get nine events back

Expected behavior
Subscription delivers all events when using StreamPosition.Start.

Actual behavior
Subscription starts with the second event in the stream.

EventStore details

  • EventStore server version: 20.6.1

  • Operating system: macOS

  • EventStore client version (if applicable): 20.6.1

Additional context
https://github.com/alexeyzimarev/EsdbSubscriptionBug

Cannot Use CallCredentials When Sending over HTTP

As mentioned in grpc/grpc#18288, you cannot use ChannelCredentials.Insecure with CallCredentials as you may end up leaking sensitive information over an unencrypted channel. However this is problematic for a couple of reasons:

  1. It fails silently - you get back 401 from the server but no indication as to why
  2. setting up TLS certs for unit tests or dev is a hassle

Instead we should allow the client to do this, and print a big fat warning in the logs as to why it is a bad idea.

Filtered subscriptions still receive checkpoints after being disposed

Describe the bug
The checkpoint callback is still triggered for events that don't pass the filter after a filtered subscription has been disposed.
Once an event passing the filter is received, the subscription disposes itself correctly and no more checkpoints are received.

To Reproduce
Steps to reproduce the behavior:

  1. Create a filtered subscription with a checkpoint callback.
  2. Write a few events and wait for them to be received.
  3. Dispose the subscription.
  4. Write enough events that don't pass the filter to trigger a checkpoint.
  5. The checkpoint callback will be triggered.
Test Code
         private static async Task FilteredSubscriptionCheckpointTest(EventStoreClient client)
        {
            var passingStream = "test-stream";
            var failingStream = "other-stream";
            uint maxSearchWindow = 32;
            
            var filter = StreamFilter.Prefix(maxSearchWindow, "test-");
            var subscriptionDisposed = new TaskCompletionSource<bool>();
            var isSubscriptionDisposed = false;
            var disposedCheckpointCount = 0;
            
            // Write an event before subscribing
            await WriteEvents(client, passingStream, count: 1);
            
            var filterOptions = new SubscriptionFilterOptions(filter, 1, (sub, position, cancel) =>
            {
                if (!isSubscriptionDisposed) return Task.CompletedTask;
                Console.WriteLine($"Disposed Checkpoint written at {position}");
                disposedCheckpointCount++;

                return Task.CompletedTask;
            });
            var subscription = await client.SubscribeToAllAsync(EventAppeared, false, SubscriptionDisposed, filterOptions);
            Task EventAppeared(StreamSubscription sub, ResolvedEvent evnt, CancellationToken cancel)
            {
                Console.WriteLine($"Received event {evnt.OriginalEventNumber}@{evnt.OriginalStreamId}");
                return Task.CompletedTask;
            }

            void SubscriptionDisposed(StreamSubscription sub, SubscriptionDroppedReason reason, Exception ex)
            {
                isSubscriptionDisposed = true;
                Console.WriteLine("Subscription has been disposed.");
                subscriptionDisposed.TrySetResult(true);
            }
            
            // Give it some time to go live
            await Task.Delay(1000);
            
            // Dispose the subscription
            subscription.Dispose();
            await subscriptionDisposed.Task;
            
            // Write events to a stream that doesn't pass the filter
            await WriteEvents(client, failingStream, 50);
            
            // Give it some more time to catch up
            await Task.Delay(1000);

            if (disposedCheckpointCount > 1) throw new Exception($"{disposedCheckpointCount} checkpoints were written after disposing the subscription");
        }

Expected behavior
The checkpoint callback is not triggered after the subscription is disposed.

Actual behavior
A checkpoint callback is triggered.

EventStore details

  • EventStore server version: 20.10.0

  • EventStore client version (if applicable): dotnet gRPC 20.10.0

gRPC call deadline exceeded.

Describe the bug
I have a .NET 5.0 Application and I am moving from EventStore-Client (TCP) using EventStore.Client.Grpc.Streams 21.2.0. When I try to check if a stream exists, I get the error, I get the error gRPC call deadline exceeded.

Result in Console from my Web Application (Serilog Logger):

[10:40:46 DBG] Starting gRPC call. Method type: 'ServerStreaming', URI: 'https://localhost:1013/event_store.client.streams.Streams/Read'.
[10:40:56 WRN] gRPC call deadline exceeded.
[10:40:56 DBG] gRPC call canceled.

eventstore.conf:

# Network configuration
IntIp: 127.0.0.1
ExtIp: 127.0.0.1
HttpPort: 8800
ClusterGossipPort: 1011
ExtTcpPort: 1013
EnableExternalTcp: true
EnableAtomPubOverHTTP: true
Insecure: true
LogFailedAuthenticationAttempts: true

# DB Configuration
MemDb: false

# Projections configuration
RunProjections: All
StartStandardProjections: true

To Reproduce
Steps to reproduce the behavior: (code available in attached solution [TestReadStream.zip])(https://github.com/EventStore/EventStore-Client-Dotnet/files/6324171/TestReadStream.zip)

  1. Start EventStore (delete data folder before)
  2. Create TEST Event image
  3. Try to get event with TCP Client ---> it works
static async Task Main(string[] args)
{
    var esConnection = EventStoreConnection.Create(
            "ConnectTo=tcp://admin:changeit@localhost:1013;DefaultUserCredentials=admin:changeit",
            ConnectionSettings.Create().KeepReconnecting().UseConsoleLogger());
    await esConnection.ConnectAsync();
    var result = await esConnection.ReadEventAsync("TEST-STREAM", StreamPosition.End, false);
    esConnection.Close();
    bool exists = result.Status is EventReadStatus.Success; ;
    Console.WriteLine($"TCP Client: {exists}");
}
  1. Try to get event with gRPC Client ---> Error
static async Task Main()
{
    var settings = EventStoreClientSettings.Create("esdb://localhost:1013?tls=true");
    settings.DefaultCredentials = new UserCredentials("admin", "changeit");
    settings.ConnectionName = "myAppConnection";
    var esClient = new EventStoreClient(settings);
    var result = esClient.ReadStreamAsync(Direction.Forwards, "TEST-STREAM", new(0L), maxCount: 1);
    bool exists = await result.ReadState != ReadState.StreamNotFound;
    Console.WriteLine($"gRPC Client: {exists}");
}

Expected behavior
I expect to get an ReadStreamResult from the called method esClient.ReadStreamAsync()

Actual behavior

Grpc.Core.RpcException
  HResult=0x80131500
  Message=Status(StatusCode="DeadlineExceeded", Detail="")
  Source=EventStore.Client
  StackTrace:
   at EventStore.Client.Interceptors.TypedExceptionInterceptor.AsyncStreamReader`1.<MoveNext>d__4.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at Grpc.Core.AsyncStreamReaderExtensions.<ReadAllAsyncCore>d__1`1.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1.GetResult(Int16 token)
   at Grpc.Core.AsyncStreamReaderExtensions.<ReadAllAsyncCore>d__1`1.System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult(Int16 token)
   at System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable`1.ConfiguredValueTaskAwaiter.GetResult()
   at EventStore.Client.EventStoreClient.ReadStreamResult.<>c__DisplayClass4_0.<<-ctor>g__GetStateInternal|1>d.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at TestReadStream.Program.<Main>d__0.MoveNext() in C:\Users\Simon\source\repos\TestReadStream\Program.cs:line 16

Config/Logs/Screenshots
See above

EventStore details

  • EventStore server version:
    EventStoreDB version 21.2.0.0 (tags/oss-v21.2.0/bc30009b8, Wed, 24 Feb 2021 17:09:23 +0100)

  • Operating system:
    Windows 10

  • EventStore client version (if applicable):
    EventStore.Client.Grpc.Streams v21.2.0 (I tried all available versions from 20.6.0 to now)

Additional context
None

Persistent subscription cannot start from the beginning of a stream

We know the N+1 thing for subscriptions, so when creating a catch-up subscription, we should use the overload, which doesn't have the stream positions, to subscribe to the beginning of the stream.

However, persistent subscriptions express different behaviour, which is also inconsistent with other clients (NodeJS, Rust), so when I subscribe without giving it a position, it will use Stream.End. Although I understand the intention to make persistent subscriptions behaving in message broker style, it is confusing.

The biggest issue for me is that it seems literally impossible to create a persistent subscription for an existing stream and get the very first event. When given Stream.Start, it will subscribe from 0+1, the second event.

Persistent subscriptions are dropped after specific period of time by default

Describe the bug
After updating package EventStore.Client.Grpc.PersistentSubscriptions to v20.6.1, I noticed connection of event persistent subscriptions was dropped every 5 seconds by default, while v20.6.0 didn't have this behavior. Is it a new feature or a bug?

To Reproduce
Here's a .net core 3.1 console program to reproduce it

using EventStore.Client;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace EventStoreSubscriptionTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Task.WaitAll(SubscribeAsync(default));
            Console.WriteLine("End");
        }

        private static async Task SubscribeAsync(CancellationToken token)
        {
            var streamName = "TestStream";
            var groupName = "TestGroup";
            var client = new EventStorePersistentSubscriptionsClient(
                new EventStoreClientSettings
                {
                    ConnectionName = "",
                    ConnectivitySettings = { Address = new Uri("https://localhost:2113") },
                    DefaultCredentials = new UserCredentials("admin", "changeit"),
                    CreateHttpMessageHandler = () =>
                        new HttpClientHandler
                        {
                            ServerCertificateCustomValidationCallback = (message, certificate2, x509Chain, sslPolicyErrors) => true
                        }
                });

            try
            {
                await client.CreateAsync(
                    streamName,
                    groupName,
                    new PersistentSubscriptionSettings(),
                    cancellationToken: token);

                Console.WriteLine($"[{DateTime.Now}] Subscription created.");
            }
            catch (Exception)
            {
                Console.WriteLine($"[{DateTime.Now}] Subscription exists.");
            }

            await client.SubscribeAsync(
                streamName,
                groupName,
                OnEventAppeared,
                async (sub, reason, ex) => await OnSubscriptionDropped(client, streamName, groupName, sub, reason, ex));

            Console.WriteLine($"[{DateTime.Now}] Subscription connected.");

            while (true)
            {
                await Task.Delay(TimeSpan.FromHours(1));
            }
        }

        private static Task OnEventAppeared(
            PersistentSubscription subscription,
            ResolvedEvent evt,
            int? count,
            CancellationToken token)
        {
            Console.WriteLine($"[{DateTime.Now}] [{count}]: Type: {evt.Event.EventType}.");
            return Task.CompletedTask;
        }

        private static async Task OnSubscriptionDropped(
            EventStorePersistentSubscriptionsClient client,
            string streamName,
            string groupName,
            PersistentSubscription subscription,
            SubscriptionDroppedReason reason,
            Exception? ex)
        {
            Console.WriteLine($"[{DateTime.Now}] Dropped for '{reason}'. Reconnecting ...");

            await client.SubscribeAsync(
                streamName,
                groupName,
                OnEventAppeared,
                async (sub, reason, ex) => await OnSubscriptionDropped(client, streamName, groupName, sub, reason, ex));

            Console.WriteLine($"[{DateTime.Now}] Subscription reconnected.");
        }
    }
}

Expected behavior
Connection of persistent subscriptions should keep alive.

Actual behavior
Connection of persistent subscriptions are dropped every 5 seconds.

EventStore details

  • EventStore server version: 20.6.1

  • Operating system: Windows 10 Pro 2004

  • EventStore client version (if applicable): 20.6.1

Allow infinite operation timeouts + review default timeout of operations returning streams

Operations that return a stream like ReadAllAsync and ReadStreamAsync have a default maxCount of long.MaxValue which represents an infinite stream which should never time out.

SubscribeAsync dropping subscription with DeadlineExceeded consistently

Describe the bug
After subscribing to a persistent subscription via SubscribeAsync method and acking every single event leads to dropping the subscription with DeadlineExceeded exception.

To Reproduce
Steps to reproduce the behavior:

  1. Append around 1000 events to a stream.
  2. Create a persistent subscription and connect.
  3. Attach EventAppearedCallback with SubscribeAsync method.
  4. See the subscription drops after a manner of seconds.

Expected behavior
Subscription should not be dropped.

Actual behavior
Subscription drops.

Config/Logs/Screenshots

EventStore details

  • EventStore server version: 20.6.1 (docker image buster slim to be precise)

  • Operating system: Windows

  • EventStore client version (if applicable): 20.6.1

Additional context
Code to produce this behavior can be seen from here: https://github.com/kivancguckiran/EventStoreSubscriptionDrop/blob/master/EventStoreSubscriptionDrop/Program.cs

Only raise exceptions when necessary

Currently the gRPC client will throw exceptions which makes for terrible ergonomics from handling them there are some cases from which we don't need to throw exceptions.

The 2 exceptions that have been identified that should not need to be thrown would be StreamNotFoundException and WrongExpectedVersionException.

Prepare position is incorrect when reading from $all after writing in a batch

Test writes

await client.AppendToStreamAsync("foo", StreamState.Any,
Enumerable.Range(0, 4096).Select(x => new EventData(Uuid.NewUuid(), x.ToString(), 
ReadOnlyMemory<byte>.Empty)));

Tcp Client results from Reading

streamId: foo, #13, Position: 2746/2746
streamId: foo, #14, Position: 2842/2842
streamId: foo, #15, Position: 2938/2938
streamId: foo, #16, Position: 3034/3034
streamId: foo, #17, Position: 3130/3130
streamId: foo, #18, Position: 3226/3226
streamId: foo, #19, Position: 3322/3322
streamId: foo, #20, Position: 3418/3418
streamId: foo, #21, Position: 3512/3512
streamId: foo, #22, Position: 3606/3606
streamId: foo, #23, Position: 3700/3700
streamId: foo, #24, Position: 3794/3794

gRPC Client results from Reading

streamId: foo, #11, Position: C:2554/P:2554
streamId: foo, #12, Position: C:2650/P:2650
streamId: foo, #13, Position: C:2746/P:2746
streamId: foo, #14, Position: C:2842/P:2842
streamId: foo, #15, Position: C:2938/P:2938
streamId: foo, #16, Position: C:3034/P:3034
streamId: foo, #17, Position: C:3130/P:3130
streamId: foo, #18, Position: C:3226/P:3226
streamId: foo, #19, Position: C:3322/P:3322
streamId: foo, #20, Position: C:3418/P:3418
streamId: foo, #21, Position: C:3512/P:3418
streamId: foo, #22, Position: C:3606/P:3418
streamId: foo, #23, Position: C:3700/P:3418
streamId: foo, #24, Position: C:3794/P:3418

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.