Skip to content

Commit ae93a37

Browse files
Handle projection catchup between each query (#39)
1 parent ba6a21a commit ae93a37

16 files changed

+219
-215
lines changed

Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<Copyright>Copyright © 2013-2024 Akka.NET Project</Copyright>
44
<NoWarn>$(NoWarn);CS1591;NU1701;CA1707;</NoWarn>
5-
<VersionPrefix>1.5.18</VersionPrefix>
5+
<VersionPrefix>1.5.20</VersionPrefix>
66
<Authors>Akka.NET Team</Authors>
77
<PackageProjectUrl>https://github.com/akkadotnet/Akka.Persistence.EventStore</PackageProjectUrl>
88
<PackageReleaseNotes>Serialization changes to match 1.4 API

Directory.Packages.props

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<Project>
22
<PropertyGroup>
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
4-
<AkkaVersion>1.5.19</AkkaVersion>
4+
<AkkaVersion>1.5.20</AkkaVersion>
55
<EventStoreVersion>23.2.1</EventStoreVersion>
6-
<XunitVersion>2.7.1</XunitVersion>
6+
<XunitVersion>2.8.0</XunitVersion>
77
<TestSdkVersion>17.9.0</TestSdkVersion>
88
</PropertyGroup>
99
<!-- Akka.NET Package Versions -->
@@ -26,7 +26,7 @@
2626
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
2727
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
2828
<PackageVersion Include="xunit" Version="$(XunitVersion)" />
29-
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.8">
29+
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.0">
3030
<PrivateAssets>all</PrivateAssets>
3131
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
3232
</PackageVersion>

README.md

-2
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,11 @@ This will run the journal with its default settings. The default settings can be
181181
#### HOCON Configuration
182182

183183
- `write-plugin` - Absolute path to the write journal plugin configuration entry that this query journal will connect to. If undefined (or "") it will connect to the default journal as specified by the `akka.persistence.journal.plugin` property.
184-
- `refresh-interval` - The amount of time the plugin will wait between queries when it didn't find any events.
185184

186185
#### HOCON Configuration Example
187186
```
188187
akka.persistence.query.journal.eventstore {
189188
write-plugin = ""
190-
refresh-interval = 5s
191189
}
192190
```
193191

RELEASE_NOTES.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#### 1.5.19 April 17 2024 ####
1+
#### 1.5.20 May 9 2024 ####
22
This is a patch release with some minor refactoring and updates to dependencies.
33

44
#### 1.5.18 April 8 2024 ####

src/Akka.Persistence.EventStore.Hosting.Tests/JournalSettingsSpec.cs

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Akka.Configuration;
22
using Akka.Persistence.EventStore.Configuration;
33
using FluentAssertions;
4-
using FluentAssertions.Extensions;
54
using Xunit;
65

76
namespace Akka.Persistence.EventStore.Hosting.Tests;
@@ -47,7 +46,6 @@ public void ModifiedOptionsTest()
4746
{
4847
AutoInitialize = false,
4948
ConnectionString = "a",
50-
QueryRefreshInterval = 5.Seconds(),
5149
Serializer = "hyperion",
5250
Adapter = "custom",
5351
StreamPrefix = "prefix",
@@ -64,9 +62,7 @@ public void ModifiedOptionsTest()
6462
.WithFallback(EventStorePersistence.DefaultJournalConfiguration);
6563

6664
var config = new EventStoreJournalSettings(journalConfig);
67-
68-
fullConfig.GetTimeSpan("akka.persistence.query.journal.custom.refresh-interval").Should().Be(5.Seconds());
69-
65+
7066
config.ConnectionString.Should().Be("a");
7167
config.Adapter.Should().Be("custom");
7268
config.StreamPrefix.Should().Be("prefix");

src/Akka.Persistence.EventStore.Hosting/EventStoreJournalOptions.cs

+3-7
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ public EventStoreJournalOptions() : this(true)
2222
public string? TaggedStreamNamePattern { get; init; }
2323
public string? PersistenceIdsStreamName { get; init; }
2424
public string? PersistedEventsStreamName { get; init; }
25-
public TimeSpan? QueryRefreshInterval { get; init; }
26-
public TimeSpan? QueryProjectionCatchupTimeout { get; init; }
25+
public TimeSpan? QueryNoStreamTimeout { get; init; }
2726
public string? Tenant { get; init; }
2827
public string? MaterializerDispatcher { get; init; }
2928
public override string Identifier { get; set; } = identifier;
@@ -66,11 +65,8 @@ protected override StringBuilder Build(StringBuilder sb)
6665

6766
sb.AppendLine($"write-plugin = {PluginId.ToHocon()}");
6867

69-
if (QueryRefreshInterval != null)
70-
sb.AppendLine($"refresh-interval = {QueryRefreshInterval.ToHocon()}");
71-
72-
if (QueryProjectionCatchupTimeout != null)
73-
sb.AppendLine($"projection-catchup-timeout = {QueryProjectionCatchupTimeout.ToHocon()}");
68+
if (QueryNoStreamTimeout != null)
69+
sb.AppendLine($"no-stream-timeout = {QueryNoStreamTimeout.ToHocon()}");
7470

7571
sb.AppendLine("}");
7672

src/Akka.Persistence.EventStore.Hosting/HostingExtensions.cs

+2-4
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ public static AkkaConfigurationBuilder WithEventStorePersistence(
2424
string? persistedEventsStreamName = null,
2525
string? tenantStreamNamePattern = null,
2626
string? materializerDispatcher = null,
27-
TimeSpan? queryRefreshInterval = null,
28-
TimeSpan? queryProjectionCatchupTimeout = null)
27+
TimeSpan? queryNoStreamTimeout = null)
2928
{
3029
if (mode == PersistenceMode.SnapshotStore && journalBuilder is not null)
3130
throw new Exception($"{nameof(journalBuilder)} can only be set when {nameof(mode)} is set to either {PersistenceMode.Both} or {PersistenceMode.Journal}");
@@ -44,8 +43,7 @@ public static AkkaConfigurationBuilder WithEventStorePersistence(
4443
PersistenceIdsStreamName = persistenceIdsStreamName,
4544
Tenant = tenant,
4645
MaterializerDispatcher = materializerDispatcher,
47-
QueryRefreshInterval = queryRefreshInterval,
48-
QueryProjectionCatchupTimeout = queryProjectionCatchupTimeout
46+
QueryNoStreamTimeout = queryNoStreamTimeout
4947
};
5048

5149
var adapters = new AkkaPersistenceJournalBuilder(journalOptions.Identifier, builder);

src/Akka.Persistence.EventStore.Tests/EventStoreConfiguration.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ class = ""Akka.Persistence.EventStore.Snapshot.EventStoreSnapshotStore, Akka.Per
3333
akka.persistence.query.journal.eventstore {{
3434
class = ""Akka.Persistence.EventStore.Query.EventStoreReadJournalProvider, Akka.Persistence.EventStore""
3535
write-plugin = ""akka.persistence.journal.eventstore""
36-
refresh-interval = 1s
37-
projection-catchup-timeout = 1s
36+
no-stream-timeout = 200ms
3837
}}
3938
akka.test.single-expect-default = 10s");
4039

src/Akka.Persistence.EventStore.Tests/PersistentSubscriptionSpec.cs

+18-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Collections.Immutable;
2-
using Akka.Actor;
32
using Akka.Persistence.EventStore.Streams;
43
using Akka.Streams;
54
using Akka.Streams.Dsl;
@@ -30,7 +29,7 @@ public async Task ReadJournal_PersistentSubscription_should_see_existing_events(
3029
{
3130
const string streamName = "a";
3231

33-
var (cancelable, probe) = await Setup(streamName, 2);
32+
var probe = await Setup(streamName, 2);
3433

3534
probe.Request(5);
3635

@@ -44,15 +43,15 @@ public async Task ReadJournal_PersistentSubscription_should_see_existing_events(
4443

4544
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
4645

47-
cancelable.Cancel();
46+
probe.Cancel();
4847
}
4948

5049
[Fact]
5150
public async Task ReadJournal_PersistentSubscription_should_see_new_events()
5251
{
5352
const string streamName = "b";
5453

55-
var (cancelable, probe) = await Setup(streamName, 1);
54+
var probe = await Setup(streamName, 1);
5655

5756
probe.Request(5);
5857

@@ -77,15 +76,15 @@ await _eventStoreClient.AppendToStreamAsync(
7776

7877
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
7978

80-
cancelable.Cancel();
79+
probe.Cancel();
8180
}
8281

8382
[Fact]
8483
public async Task ReadJournal_PersistentSubscription_should_see_all_150_events()
8584
{
8685
const string streamName = "c";
8786

88-
var (cancelable, probe) = await Setup(streamName, 150);
87+
var probe = await Setup(streamName, 150);
8988

9089
probe.Request(150);
9190

@@ -100,19 +99,18 @@ public async Task ReadJournal_PersistentSubscription_should_see_all_150_events()
10099

101100
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
102101

103-
cancelable.Cancel();
102+
probe.Cancel();
104103
}
105104

106105
[Fact]
107106
public async Task ReadJournal_PersistentSubscription_should_survive_dropped_connection_when_given_retry_settings()
108107
{
109108
const string streamName = "d";
110109

111-
var (cancelable, probe) = await Setup(
110+
var probe = await Setup(
112111
streamName,
113112
1,
114-
RestartSettings
115-
.Create(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), 0.2));
113+
keepReconnecting: true);
116114

117115
probe.Request(5);
118116

@@ -141,15 +139,15 @@ await _eventStoreClient.AppendToStreamAsync(
141139

142140
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
143141

144-
cancelable.Cancel();
142+
probe.Cancel();
145143
}
146144

147145
[Fact]
148146
public async Task ReadJournal_PersistentSubscription_should_fail_on_dropped_connection_when_not_given_any_retry_settings()
149147
{
150148
const string streamName = "e";
151149

152-
var (_, probe) = await Setup(streamName, 1);
150+
var probe = await Setup(streamName, 1);
153151

154152
probe.Request(5);
155153

@@ -169,35 +167,33 @@ public async Task ReadJournal_PersistentSubscription_subscription_should_be_drop
169167
{
170168
const string streamName = "f";
171169

172-
var (cancelable, probe) = await Setup(streamName, 1);
170+
var probe = await Setup(streamName, 1);
173171

174172
probe.Request(5);
175173

176174
var firstMessage = await probe.ExpectNextAsync<PersistentSubscriptionEvent>(x => x.Event.Event.EventType == $"{streamName}-1");
177175

178176
await firstMessage.Ack();
179177

180-
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
178+
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
181179

182180
var subscriptionBeforeCancel = await _subscriptionClient.GetInfoToStreamAsync(streamName, streamName);
183181

184182
subscriptionBeforeCancel.Connections.Should().HaveCount(1);
185183

186-
cancelable.Cancel();
184+
probe.Cancel();
187185

188-
await probe.ExpectCompleteAsync();
189-
190-
await Task.Delay(TimeSpan.FromMilliseconds(500));
186+
await Task.Delay(TimeSpan.FromMilliseconds(300));
191187

192188
var subscriptionAfterCancel = await _subscriptionClient.GetInfoToStreamAsync(streamName, streamName);
193189

194190
subscriptionAfterCancel.Connections.Should().HaveCount(0);
195191
}
196192

197-
private async Task<(ICancelable, TestSubscriber.Probe<PersistentSubscriptionEvent>)> Setup(
193+
private async Task<TestSubscriber.Probe<PersistentSubscriptionEvent>> Setup(
198194
string streamName,
199195
int numberOfEvents,
200-
RestartSettings? restartWith = null)
196+
bool keepReconnecting = false)
201197
{
202198
await _subscriptionClient.CreateToStreamAsync(
203199
streamName,
@@ -221,8 +217,8 @@ await _eventStoreClient.AppendToStreamAsync(
221217
_subscriptionClient,
222218
streamName,
223219
streamName,
224-
restartWith: restartWith);
220+
keepReconnecting: keepReconnecting);
225221

226-
return stream.ToMaterialized(this.SinkProbe<PersistentSubscriptionEvent>(), Keep.Both).Run(Sys.Materializer());
222+
return stream.ToMaterialized(this.SinkProbe<PersistentSubscriptionEvent>(), Keep.Right).Run(Sys.Materializer());
227223
}
228224
}

src/Akka.Persistence.EventStore.Tests/Query/EventStoreCurrentEventsByTagSpec.cs

+51
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
using Akka.Persistence.TCK.Query;
55
using Akka.Streams;
66
using Akka.Streams.Dsl;
7+
using Akka.Streams.TestKit;
78
using FluentAssertions;
89
using Xunit;
910
using Xunit.Abstractions;
11+
using Xunit.Sdk;
1012

1113
namespace Akka.Persistence.EventStore.Tests.Query;
1214

@@ -19,6 +21,34 @@ public EventStoreCurrentEventsByTagSpec(DatabaseFixture databaseFixture, ITestOu
1921
ReadJournal = Sys.ReadJournalFor<EventStoreReadJournal>(EventStorePersistence.QueryConfigPath);
2022
}
2123

24+
[Fact]
25+
public override void ReadJournal_query_CurrentEventsByTag_should_see_all_150_events()
26+
{
27+
if (ReadJournal is not ICurrentEventsByTagQuery queries)
28+
throw IsTypeException.ForMismatchedType(nameof(ICurrentEventsByTagQuery), ReadJournal?.GetType().Name ?? "null");
29+
30+
var a = Sys.ActorOf(Query.TestActor.Props("a"));
31+
32+
foreach (var _ in Enumerable.Range(1, 150))
33+
{
34+
a.Tell("a green apple");
35+
ExpectMsg("a green apple-done");
36+
}
37+
38+
Thread.Sleep(TimeSpan.FromMilliseconds(300));
39+
40+
var greenSrc = queries.CurrentEventsByTag("green", offset: Offset.NoOffset());
41+
var probe = greenSrc.RunWith(this.SinkProbe<EventEnvelope>(), Materializer);
42+
probe.Request(150);
43+
foreach (var i in Enumerable.Range(1, 150))
44+
{
45+
ExpectEnvelope(probe, "a", i, "a green apple", "green");
46+
}
47+
48+
probe.ExpectComplete();
49+
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
50+
}
51+
2252
[Fact]
2353
public async Task ReadJournal_query_offset_exclusivity_should_be_correct()
2454
{
@@ -44,10 +74,31 @@ public async Task ReadJournal_query_offset_exclusivity_should_be_correct()
4474

4575
actor.Tell("a green banana");
4676
ExpectMsg("a green banana-done");
77+
78+
await Task.Delay(TimeSpan.FromMilliseconds(300));
4779

4880
var round3 = await journal.CurrentEventsByTag(tag, item1Offset)
4981
.RunWith(Sink.Seq<EventEnvelope>(), Sys.Materializer());
5082

5183
round3.Should().HaveCount(1);
5284
}
85+
86+
private void ExpectEnvelope(
87+
TestSubscriber.Probe<EventEnvelope> probe,
88+
string persistenceId,
89+
long sequenceNr,
90+
string @event,
91+
string tag)
92+
{
93+
var envelope = probe.ExpectNext<EventEnvelope>(_ => true);
94+
envelope.PersistenceId.Should().Be(persistenceId);
95+
envelope.SequenceNr.Should().Be(sequenceNr);
96+
envelope.Event.Should().Be(@event);
97+
98+
if (SupportsTagsInEventEnvelope)
99+
{
100+
envelope.Tags.Should().NotBeNull();
101+
envelope.Tags.Should().Contain(tag);
102+
}
103+
}
53104
}

src/Akka.Persistence.EventStore/Configuration/EventStoreReadJournalSettings.cs

+2-4
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ public EventStoreReadJournalSettings(Config config)
1313
config = config.WithFallback(EventStorePersistence.DefaultQueryConfiguration);
1414

1515
WritePlugin = config.GetString("write-plugin");
16-
QueryRefreshInterval = config.GetTimeSpan("refresh-interval", TimeSpan.FromSeconds(5));
17-
ProjectionCatchupTimeout = config.GetTimeSpan("projection-catchup-timeout", TimeSpan.FromMilliseconds(500));
16+
NoStreamTimeout = config.GetTimeSpan("no-stream-timeout", TimeSpan.FromMilliseconds(500));
1817
}
1918

2019
public string WritePlugin { get; }
21-
public TimeSpan QueryRefreshInterval { get; }
22-
public TimeSpan ProjectionCatchupTimeout { get; }
20+
public TimeSpan NoStreamTimeout { get; }
2321
}

0 commit comments

Comments
 (0)