diff --git a/src/EventTests/Projections/EventPageTests.cs b/src/EventTests/Projections/EventPageTests.cs new file mode 100644 index 0000000..a031107 --- /dev/null +++ b/src/EventTests/Projections/EventPageTests.cs @@ -0,0 +1,48 @@ +using JasperFx.Events; +using JasperFx.Events.Projections; +using Shouldly; + +namespace EventTests.Projections; + +public class EventPageTests +{ + [Fact] + public void calculate_ceiling_when_page_is_full_uses_last_sequence() + { + var page = new EventPage(0) + { + new Event(new AEvent()) { Sequence = 4 }, + new Event(new AEvent()) { Sequence = 5 } + }; + + page.CalculateCeiling(2, 1000); + + page.Ceiling.ShouldBe(5); + } + + [Fact] + public void calculate_ceiling_when_page_is_not_full_uses_high_water_mark() + { + var page = new EventPage(0) + { + new Event(new AEvent()) { Sequence = 4 } + }; + + page.CalculateCeiling(10, 1000); + + page.Ceiling.ShouldBe(1000); + } + + [Fact] + public void calculate_ceiling_when_full_batch_was_entirely_skipped_does_not_throw() + { + // Reproduces https://github.com/JasperFx/marten/issues/4663 -- every event in a + // full batch was skipped, so the page is empty. CalculateCeiling must not call + // Last() on the empty page; it should fall back to the high water mark. + var page = new EventPage(0); + + Should.NotThrow(() => page.CalculateCeiling(10, 1000, skippedEvents: 10)); + + page.Ceiling.ShouldBe(1000); + } +} diff --git a/src/JasperFx.Events/Projections/EventPage.cs b/src/JasperFx.Events/Projections/EventPage.cs index a5d2629..0d46104 100644 --- a/src/JasperFx.Events/Projections/EventPage.cs +++ b/src/JasperFx.Events/Projections/EventPage.cs @@ -14,7 +14,12 @@ public EventPage(long floor) public void CalculateCeiling(int batchSize, long highWaterMark, int skippedEvents = 0) { - Ceiling = (Count + skippedEvents) == batchSize + // Count == 0 happens when every event in a full batch was skipped (e.g. error + // handling is configured to skip serialization/application/unknown event errors). + // There is no last event to read a sequence from, so fall back to the high water + // mark and let the daemon make progress rather than throwing on an empty page. See + // https://github.com/JasperFx/marten/issues/4663 + Ceiling = (Count + skippedEvents) == batchSize && Count > 0 ? this.Last().Sequence : highWaterMark; }