diff --git a/Egil.Orleans.Messaging/README.md b/Egil.Orleans.Messaging/README.md index 6efa65a2..3699bebe 100644 --- a/Egil.Orleans.Messaging/README.md +++ b/Egil.Orleans.Messaging/README.md @@ -1825,8 +1825,9 @@ this.RegisterStreamManager() async (message, cursor) => await UpdateProjectionAsync(message)); ``` -The Event Hubs extensions support Orleans **10.3.1 and 10.4.0** with the same -package binary. On 10.4, enriched tokens share the built-in Event Hubs comparison +All OM packages require Orleans **10.4.0 or later**. Upgrade the Orleans packages +in your host together when upgrading OM. Enriched tokens share the built-in Event +Hubs comparison domain: equal sequence numbers and event indexes compare equally regardless of enrichment. Checkpoints still belong to their provider and complete `StreamId`; this does not make positions from different streams interchangeable. The JSON diff --git a/Egil.Orleans.Messaging/docs/eventhubs-token-compatibility.md b/Egil.Orleans.Messaging/docs/eventhubs-token-compatibility.md index 29f0516e..d7e94950 100644 --- a/Egil.Orleans.Messaging/docs/eventhubs-token-compatibility.md +++ b/Egil.Orleans.Messaging/docs/eventhubs-token-compatibility.md @@ -1,36 +1,34 @@ -# Event Hubs token compatibility +# Event Hubs token compatibility on Orleans 10.4 -The stable core and Event Hubs extension retain their Orleans 10.3.1 minimum. -Only Event Hubs compilation uses the two 10.4 streaming/provider assemblies, -acquired through `PackageDownload`; restore, runtime and pack graphs retain 10.3.1. +All OM packages require Orleans 10.4.0 or later. The preview Journaling integration +uses Microsoft.Orleans.Journaling 10.4.0-alpha.1. Consumers must upgrade their Orleans +packages together; no mixed 10.3/10.4 runtime or compile-reference strategy is supported. +Shared dependency pins and coordinated package verification are owned by #285. -The enriched token overrides 10.4's protected compatibility-domain getter with +The enriched token overrides the protected compatibility-domain getter with `typeof(EventHubSequenceToken)`, matching standard provider tokens in both comparison -directions. On 10.3.1, the CLR introduces an unused virtual slot because the base -getter does not exist. No explicit override mapping or missing base call is emitted. -This is established by actual packed-binary execution, not nuspec metadata alone. -Aliases and JSON discriminator values are unchanged. Provider plus full `StreamId` -remain the checkpoint identity. Generic-token public equality remains separate from -the provider's historical exact V1 checkpoint normalization at recovery boundaries. +directions. Aliases, field IDs and JSON discriminator values are unchanged. Provider +plus full `StreamId` remain the checkpoint identity. Generic-token public equality +remains separate from the provider's historical exact V1 checkpoint normalization at +recovery boundaries; upgrading the runtime does not discard existing checkpoint data. + +Factory and cache recovery tests now run in the normal Event Hubs test project: ```powershell -pwsh -File Egil.Orleans.Messaging/scripts/test-eventhubs-compatibility.ps1 +dotnet test Egil.Orleans.Messaging/Egil.Orleans.Messaging.slnx -c Release ``` -The script packs once, checks the dependency minimum, uses a fresh package cache and -runs existing Event Hubs tests as external consumers on 10.3.1 and 10.4.0. Additional -10.4 cases cover the inherited per-event factory, real pooled provider cache recovery -for standard/enriched/historical exact V1 tokens, genuine eviction misses and generic -token rejection. Existing cases cover persisted JSON fixtures, Orleans serialization, -custom containers, null per-event tokens, metadata and outbox request context. SHA-256 -verification checks both the unchanged package and each consumer's copied assembly against the packed DLL. Each run gets a unique feed and cache even when the output parent is reused. +Coverage includes symmetric equality/order/hash, sequence-before-index ordering, +persisted standard/enriched provider/full-stream checkpoint lookup, Orleans and JSON +serialization, default/custom/null-token containers, per-event metadata, inherited +factory cloning, historical exact V1 recovery through the real pooled provider cache, +and genuine eviction misses. The cache fixtures create separate broker events through +the enriched adapter. -Before the override, both symmetric equality cases failed on the packed 10.4 -consumer; after the override, both version consumers passed. Broker connectivity and -a live pulling-agent subscription handshake remain coordinated validation work for -#285; this in-process suite does not claim either. No claim covers future versions or -NativeAOT. +Before the override, standard/enriched symmetric equality failed on Orleans 10.4; +the override restores both comparison directions. Actual StreamManager resume and +the live pulling-agent subscription handshake remain coordinated validation work for +#285. In-process cache tests do not claim broker connectivity or live handshake proof. Issue: https://github.com/egil/framework/issues/282 Upstream: https://github.com/dotnet/orleans/pull/11155 - diff --git a/Egil.Orleans.Messaging/scripts/test-eventhubs-compatibility.ps1 b/Egil.Orleans.Messaging/scripts/test-eventhubs-compatibility.ps1 deleted file mode 100644 index 05943c36..00000000 --- a/Egil.Orleans.Messaging/scripts/test-eventhubs-compatibility.ps1 +++ /dev/null @@ -1,65 +0,0 @@ -param([string] $OutputDirectory) -$ErrorActionPreference = 'Stop' -$projectRoot = Split-Path $PSScriptRoot -if (!$OutputDirectory) { $OutputDirectory = [IO.Path]::GetTempPath() } -$OutputDirectory = Join-Path ([IO.Path]::GetFullPath($OutputDirectory)) ('om-eventhubs-' + [guid]::NewGuid().ToString('N')) -$feed = Join-Path $OutputDirectory 'feed' -New-Item -ItemType Directory -Force $feed | Out-Null -function Invoke-Dotnet { - & dotnet @args - if ($LASTEXITCODE -ne 0) { throw "dotnet failed with exit code $LASTEXITCODE" } -} -Invoke-Dotnet pack (Join-Path $projectRoot 'src/Egil.Orleans.Messaging/Egil.Orleans.Messaging.csproj') -c Release -o $feed -Invoke-Dotnet pack (Join-Path $projectRoot 'src/Egil.Orleans.Messaging.Streams.EventHubs/Egil.Orleans.Messaging.Streams.EventHubs.csproj') -c Release -o $feed -Add-Type -AssemblyName System.IO.Compression.FileSystem -$package = Get-ChildItem $feed -Filter 'Egil.Orleans.Messaging.Streams.EventHubs.*.nupkg' | Select-Object -First 1 -$archive = [IO.Compression.ZipFile]::OpenRead($package.FullName) -try { - $entry = $archive.Entries | Where-Object FullName -Like '*.nuspec' | Select-Object -First 1 - $reader = [IO.StreamReader]::new($entry.Open()) - try { [xml] $nuspec = $reader.ReadToEnd() } finally { $reader.Dispose() } - $dllEntry = $archive.GetEntry('lib/net10.0/Egil.Orleans.Messaging.Streams.EventHubs.dll') - $dllStream = $dllEntry.Open() - try { $expectedDllHash = (Get-FileHash -InputStream $dllStream -Algorithm SHA256).Hash } finally { $dllStream.Dispose() } - $version = $nuspec.package.metadata.version - $providerDependency = $nuspec.package.metadata.dependencies.group.dependency | Where-Object id -EQ 'Microsoft.Orleans.Streaming.EventHubs' - if ($providerDependency.version -ne '10.3.1') { throw "Unexpected provider minimum: $($providerDependency.version)" } -} finally { $archive.Dispose() } -$hashBefore = (Get-FileHash $package.FullName -Algorithm SHA256).Hash -$sources = (Join-Path $projectRoot 'test/Egil.Orleans.Messaging.Streams.EventHubs.Tests').Replace('\', '/') -$recoverySources = (Join-Path $projectRoot 'test/EventHubs.Compatibility/*.cs').Replace('\', '/') -foreach ($orleansVersion in '10.3.1', '10.4.0') { - $consumer = Join-Path $OutputDirectory $orleansVersion - New-Item -ItemType Directory -Force $consumer | Out-Null - $recovery = if ($orleansVersion -eq '10.4.0') { '' } else { '' } - @" - - - net10.0enableenable - falsetrueExe - true - - - $recovery - - - - - - - - - - -"@ | Set-Content (Join-Path $consumer 'Tests.csproj') - @" - -"@ | Set-Content (Join-Path $consumer 'NuGet.Config') - Invoke-Dotnet restore (Join-Path $consumer 'Tests.csproj') --configfile (Join-Path $consumer 'NuGet.Config') --packages (Join-Path $OutputDirectory 'packages') - Invoke-Dotnet test (Join-Path $consumer 'Tests.csproj') -c Release --no-restore - $loadedDll = Join-Path $consumer 'bin/Release/net10.0/Egil.Orleans.Messaging.Streams.EventHubs.dll' - if ((Get-FileHash $loadedDll -Algorithm SHA256).Hash -ne $expectedDllHash) { throw 'Consumer did not load the packed assembly.' } -} -if ((Get-FileHash $package.FullName -Algorithm SHA256).Hash -ne $hashBefore) { throw 'Package changed between consumer runs.' } -Write-Host "Same artifact verified on both Orleans versions: $hashBefore" -Write-Host "Evidence directory: $OutputDirectory" diff --git a/Egil.Orleans.Messaging/test/EventHubs.Compatibility/EventHubs104RecoveryTests.cs b/Egil.Orleans.Messaging/test/Egil.Orleans.Messaging.Streams.EventHubs.Tests/EventHubs/EventHubsRecoveryTests.cs similarity index 97% rename from Egil.Orleans.Messaging/test/EventHubs.Compatibility/EventHubs104RecoveryTests.cs rename to Egil.Orleans.Messaging/test/Egil.Orleans.Messaging.Streams.EventHubs.Tests/EventHubs/EventHubsRecoveryTests.cs index 590f902f..2cf8b512 100644 --- a/Egil.Orleans.Messaging/test/EventHubs.Compatibility/EventHubs104RecoveryTests.cs +++ b/Egil.Orleans.Messaging/test/Egil.Orleans.Messaging.Streams.EventHubs.Tests/EventHubs/EventHubsRecoveryTests.cs @@ -7,7 +7,7 @@ namespace Egil.Orleans.Messaging.Tests.Streams.EventHubs; -public sealed class EventHubs104RecoveryTests +public sealed class EventHubsRecoveryTests { [Fact] public void Inherited_event_factory_retains_enrichment() @@ -78,9 +78,15 @@ public void Generic_tokens_remain_distinct_in_public_equality() Assert.Throws(() => token.CompareTo(genericV2)); } - public enum CheckpointKind { Standard, Enriched, HistoricalV1 } + public enum CheckpointKind + { + Standard, + Enriched, + HistoricalV1 + } private sealed class UnrelatedProviderToken(long sequenceNumber, int eventIndex) : EventSequenceToken(sequenceNumber, eventIndex); + private static (PooledQueueCache Cache, StreamId Stream) CreateCache() { var services = new ServiceCollection();