Skip to content

Release v1.5.57-beta2#7958

Closed
Aaronontheweb wants to merge 33 commits into
devfrom
v1.5
Closed

Release v1.5.57-beta2#7958
Aaronontheweb wants to merge 33 commits into
devfrom
v1.5

Conversation

@Aaronontheweb
Copy link
Copy Markdown
Member

Summary

Release notes and version bump for Akka.NET v1.5.57-beta2.

This beta release adds significant new APIs for Akka.Persistence:

  • Completion callback support for PersistAll and PersistAllAsync
  • Async handler support (Func<TEvent, Task>) for all persist methods
  • Maintains strict ordering guarantees via Defer/DeferAsync

Changes

  • Updated RELEASE_NOTES.md with detailed feature description and code examples
  • Bumped version to 1.5.57-beta2 in Directory.Build.props

Testing

This release includes the changes from PR #7957 which was already merged and tested.

Arkatufus and others added 30 commits August 21, 2025 16:02
* Force synchronous start for `TestActor`

fix #7770

* separate creation of implicit, default `TestActor` from additional ones

* force `TestActor` to start via CTD tweak instead

* don't wait for `TestActor` to start

* Revert "don't wait for `TestActor` to start"

This reverts commit bdd77f9.

* run default `TestActor` without `CallingThreadDispatcher`

* fix TestKit deadlock during parallel test execution

This commit resolves a deadlock that occurs when running tests in parallel, where the initial TestActor creation gets stuck during async initialization with CallingThreadDispatcher.

The root cause was that SystemActorOf hardcodes async=true initialization, creating a RepointableActorRef that requires processing a Supervise system message. With CallingThreadDispatcher, this creates a circular dependency:
- TestKit constructor blocks waiting for TestActor initialization
- CallingThreadDispatcher only runs on the calling thread
- The calling thread is blocked, so Supervise message never gets processed

The solution bypasses SystemActorOf and directly calls AttachChild with async=false, enabling true synchronous initialization while preserving full system integration including supervision tree and mailbox configuration.

This maintains compatibility with CallingThreadDispatcher for deterministic testing while eliminating startup deadlocks in parallel test scenarios.

Resolves issue where TestProbe child actor creation and implicit sender functionality would fail due to incomplete TestActor initialization.

* Fix TestKit serialization issue

- Use AttachChild with isSystemService=true to exempt TestActor from serialization verification
- Resolves 700+ test failures caused by UnboundedChannelWriter serialization errors

* still working on synchronous `TestActor` startup

* Fix TestKit deadlock during parallel test execution

Resolves deadlock that occurs when TestKit instances are created in parallel
and actors try to interact with TestActor during initialization. The issue
was caused by CallingThreadDispatcher creating RepointableActorRef which
requires async initialization, leading to deadlocks.

Changes:
- Add AttachChildWithAsync internal method to ActorCell to control sync/async actor creation
- Modify TestKitBase to create TestActor synchronously (LocalActorRef) instead of async (RepointableActorRef)
- Update Xunit/Xunit2 TestKits to create logger actors synchronously
- Replace Ask with Tell for logger initialization to avoid synchronous wait deadlocks
- Add InternalsVisibleTo for Xunit TestKits to access internal Akka methods
- Maintain LoggerInitialized response for protocol compatibility (has IDeadLetterSuppression)

Fixes #7770

* added API approvals

* remove `EnsureTestActorReady` method

* API approvals

* ensure  calls can't get contaminated with  references

* fix API approvals

* Fix race condition in ParallelTestActorDeadlockSpec

The test had a race condition where the PingerActor sends 'ping' to TestActor
during PreStart, but the test was expecting 'test-message' first. This could
cause ExpectMsgAsync to receive the wrong message and fail.

Fixed by properly expecting the 'ping' message first before sending and
expecting the 'test-message'.
…7791)

* Parameterize Incrementalist base branch for Azure DevOps pipelines

- Add dynamic incrementalist.baseBranch variable to pipeline templates
- Use System.PullRequest.TargetBranch for PR builds
- Default to 'dev' for non-PR builds
- Update all Incrementalist commands to use --branch parameter

This allows PRs targeting version branches (e.g., v1.5) to correctly
compare against their target branch instead of always using dev,
avoiding unnecessary full builds.

* Fix Incrementalist base branch detection for Azure DevOps PRs

- Use PowerShell script to extract branch name from System.PullRequest.TargetBranch
- Strip 'refs/heads/' prefix from target branch reference
- Set IncrementalistBaseBranch variable dynamically at runtime
- Update all Incrementalist commands to use the new variable

This fixes the issue where PR builds always used 'dev' as the base branch
even when targeting version branches like v1.5.
* Fix IIS/Windows Service console race condition (#7691)

- Detect when running in IIS/Windows Service environments where Console.Out
  and Console.Error are redirected to the same StreamWriter.Null singleton
- Skip console output entirely in these environments to prevent race conditions
  that cause IndexOutOfRangeException and cascade failures
- Improve DefaultLogger error handling to prevent feedback loops
- Add unit tests for non-console scenarios

The race condition occurs because:
1. IIS/Services redirect both Console.Out and Console.Error to StreamWriter.Null
2. StreamWriter.Null is a singleton, not thread-safe for concurrent access
3. Multiple threads writing to both streams cause IndexOutOfRangeException
4. Console output goes nowhere in these environments anyway

Fixes #7691

* Refine console detection and simplify error handling

- Make console detection more precise: only skip output when both Console.Out
  AND Console.Error point to StreamWriter.Null (the exact race condition scenario)
- Remove unnecessary try-catch in DefaultLogger.Print() since Tell() is unlikely to throw
- Keep improved error message for debugging when logger is not initialized
* Fix TLS handshake error handling

* Simplify PR

* Simplify PR, remove new DisassociateInfo

* Clean whitespace noise

* cleanup, remove TlsHandshakeErrorAssociation
* Akka.Persistence: add health check support to `AsyncWriteJournal`

#7840

* added messaging protocol to support plugin health check

* Added tests for basic Akka.Persistence health checks

this is mostly a sanity test. I don't want to get sucked into testing the `CircuitBreaker` necessarily either

* added structured output to health check results

* fix compilation errors

* added failure specs

* implemented `SnapshotStore` health checks

* renamed test class

* SnapshotStoreHealthCheckSpecs

* API approvals
* Update RELEASE_NOTES.md for 1.5.51 release

* Fix typo
…7847) (#7848)

* Fix: Validate SSL certificate private key access at server startup

**Problem**: Akka.Remote server starts successfully even when the application
lacks permissions to access the SSL certificate's private key. The server appears
healthy but fails when clients attempt to connect, making issues hard to diagnose.

**Root Cause**: Certificate loading in DotNettyTransportSettings only validates
that the certificate EXISTS in the Windows certificate store, not whether the
application can ACCESS the private key. Private key access is checked separately
by Windows ACL, which can fail even when Certificate.HasPrivateKey returns true.

**Solution**:
1. Add ValidateCertificate() method to SslSettings class that:
   - Checks Certificate.HasPrivateKey
   - Actually tests private key access with GetRSAPrivateKey() (not just presence)
   - Throws ConfigurationException with clear error message on failure

2. Call validation in Listen() method before server socket binds:
   - Ensures fail-fast behavior at startup
   - Prevents server from running in broken state
   - Provides clear error message for administrators

3. Add comprehensive tests:
   - Server should fail at startup with inaccessible private key
   - Server should start successfully with valid certificate
   - Server should start successfully without SSL

**Impact**:
- Existing misconfigured deployments will now fail at startup (correct behavior)
- Clear error messages guide administrators to fix permissions
- No breaking changes for correctly configured systems
- Related to Freshdesk #538 (BNSF Railway)

Fixes #538

* Update DotNettyTlsHandshakeFailureSpec to validate fail-fast behavior

**Changes**:
1. Renamed first test to `Server_should_fail_at_startup_with_certificate_without_private_key`
   - Now validates that server FAILS AT STARTUP with bad certificate
   - Tests fail-fast behavior instead of runtime TLS handshake failure

2. Removed redundant `Server_side_tls_handshake_failure_should_shutdown_server` test
   - This test validated the OLD (incorrect) behavior where server starts successfully
   - Now impossible with fail-fast validation in place
   - Scenario already covered by the updated first test

3. Kept `Client_side_tls_handshake_failure_should_shutdown_client` unchanged
   - Still valid - tests client-side validation failure
   - Not affected by server startup validation

**Result**: Tests now validate correct fail-fast behavior at server startup

* Add ECDSA private key validation and improve disposal pattern

Addresses review feedback from @Arkatufus:

**Changes**:
1. Check both RSA and ECDSA private keys
   - SslStream supports both RSA and ECDSA certificates
   - GetRSAPrivateKey() returns null for ECDSA certs (and vice versa)
   - Validation now checks both key types to match TLS handler behavior

2. Use `using` statements for proper disposal
   - Prevents resource leaks if exception is thrown
   - Both rsaKey and ecdsaKey are properly disposed
   - Exception-safe resource management

**TLS Handler Relationship**:
The TLS handler uses `TlsHandler.Server(Settings.Ssl.Certificate)` which
internally extracts either RSA or ECDSA private keys via SslStream. Our
validation now matches this behavior by checking both key types.

**Behavior**:
- RSA certificate: GetRSAPrivateKey() succeeds, GetECDsaPrivateKey() returns null ✅
- ECDSA certificate: GetECDsaPrivateKey() succeeds, GetRSAPrivateKey() returns null ✅
- Neither accessible: Both return null, validation fails with clear error ✅
- Permission denied: CryptographicException caught, clear error message ✅
)

* Add mutual TLS authentication support for DotNetty transport

Implements mutual TLS (mTLS) authentication as a defense-in-depth security
measure for Akka.Remote TLS connections. When enabled, both client and server
must present valid certificates with accessible private keys during the TLS
handshake, ensuring symmetric authentication.

Key Changes:
- Add require-mutual-authentication config option (default: true)
- Update SslSettings to include RequireMutualAuthentication property
- Modify client TLS handler to provide certificate only when mutual TLS enabled
- Modify server TLS handler to require and validate client certificates when enabled
- Add comprehensive test suite for mutual TLS scenarios

Security Benefits:
- Prevents nodes with inaccessible private keys from connecting as clients
- Ensures complete bidirectional authentication (not just server-side)
- Works in conjunction with startup certificate validation for fail-fast behavior
- Provides defense-in-depth security for production deployments

Configuration:
  akka.remote.dot-netty.tcp.ssl {
    require-mutual-authentication = true  # Default: secure by default
  }

Set to false only if your environment cannot support client certificate authentication.

Related: Freshdesk #538 - TLS certificate private key validation

* Update Akka.Remote security documentation

Major overhaul of the security documentation to reflect new TLS features
and provide comprehensive security guidance for production deployments.

Changes:
- Document new startup certificate validation feature (v1.5.52+)
- Document new mutual TLS authentication support (v1.5.52+)
- Add detailed suppress-validation guidance with security implications
- Provide Windows Certificate Store configuration examples
- Include PowerShell scripts for certificate management
- Add troubleshooting section for common TLS issues
- Update configuration examples from insecure to secure defaults
- Fix deprecated external links (Microsoft Learn, IETF, OWASP)
- Add security analysis for different configuration levels
- Include migration guide for upgrading to mutual TLS
- Add best practices summary with 10 key recommendations
- Document common pitfalls and their solutions

Security improvements:
- Changed example configs to use suppress-validation = false by default
- Added warnings about using suppress-validation = true in production
- Emphasized defense-in-depth with VPNs + TLS + mutual TLS
- Documented proper self-signed certificate usage for development

The documentation now provides clear guidance on:
- What TLS protects against (and what it doesn't)
- When to use mutual TLS vs standard TLS
- How to properly configure certificates in production
- How to troubleshoot common certificate permission issues

Related: Freshdesk #538 - TLS certificate validation improvements

* Add proper code samples for TLS configuration documentation

- Create TlsConfigurationSample.cs with proper HOCON configuration examples
- Update security.md to reference code samples using DocFX syntax
- Add context explaining when TLS is needed vs optional
- Remove poorly designed region tags from test file

The documentation now follows Akka.NET documentation guidelines with
proper code references instead of inline configuration blocks.

* feat(remote): implement mutual TLS authentication support

Added configurable mutual TLS (mTLS) authentication for Akka.Remote to provide bidirectional certificate validation between client and server nodes. This feature enhances security by ensuring both sides of a connection authenticate with valid certificates.

Changes:
- Added `require-mutual-authentication` config option (defaults to true for security-by-default)
- Updated DotNettyTransport to enforce mutual TLS in both client and server pipelines
- Added comprehensive test suite for mutual TLS scenarios
- Updated security documentation with detailed TLS/mTLS configuration guidance
- Added code samples for various TLS configurations (standard, mutual, Windows cert store)
- Included Mermaid sequence diagrams for TLS vs mTLS flows (pending Mermaid support on site)

The implementation ensures backward compatibility while encouraging secure defaults. When mutual TLS is disabled, the system falls back to standard server-only authentication.

Related to Freshdesk ticket #538

* Fix markdown linting issues in security documentation

* Fix remaining markdown linting issues - convert bold text to proper headings

* Fix title case issues in security.md documentation

- Fixed all heading title case to comply with markdownlint-rule-titlecase
- Changed 'suppress-validation' to 'Suppress-Validation' in headings
- Fixed error message headings to use proper title case
- All CI/CD checks should now pass

* Add Tailscale and ZeroTier to cSpell dictionary

Added VPN provider names to the accepted words list to fix CI spellcheck failures

* Address PR review feedback on mutual TLS implementation

- Fixed binary compatibility by adding overloaded SslSettings constructor
- Added config test to verify RequireMutualAuthentication defaults to true
- Added test for mutual TLS failure when client has no certificate
- Added test to verify mutual TLS can be disabled for backward compatibility
- Enhanced DotNettyMutualTlsSpec with more comprehensive test scenarios

* Add test for mutual TLS failure with different certificates

- Generated new self-signed certificate (akka-client-cert.pfx) for testing
- Modified CreateConfig to accept custom certificate paths
- Added test to verify mutual TLS fails when client and server have different valid certificates
- This ensures proper certificate validation in mutual TLS mode

* Add client certificate for mutual TLS testing

- Generated new self-signed certificate (akka-client-cert.pfx) to test scenarios where client and server have different valid certificates
- Added certificate to project file as build output
- Force-added certificate to git (normally ignored by .gitignore)

* fixed `RemoteConfigSpec`

* Fix RemoteConfigSpec and add SSL defaults test

- Added new test SSL_should_have_secure_defaults_when_enabled to verify secure defaults when SSL is enabled
- Removed SSL checks from non-SSL test Remoting_should_contain_correct_heliosTCP_values_in_ReferenceConf
- Fixed certificate path resolution using full path
- Tests now properly verify that require-mutual-authentication defaults to true and suppress-validation defaults to false

* remove redundant tests

* Restore DotNettySslSetupSpec and add mutual TLS Setup API test

- Restored DotNettySslSetupSpec which tests SSL configuration via Setup API
- This is distinct from HOCON-based configuration tested in DotNettySslSupportSpec
- Added test for configuring mutual TLS via DotNettySslSetup
- Enhanced TestActorSystemSetup to support mutual authentication parameter

* Fix DotNettySslSetupSpec compilation errors

- Removed unsupported requireMutualAuth parameter from DotNettySslSetup constructor
- Updated test to combine Setup API certificate with HOCON mutual TLS configuration
- Fixed shutdown method call to use correct API

* Revert "Fix DotNettySslSetupSpec compilation errors"

This reverts commit 6503391.

* Revert "Restore DotNettySslSetupSpec and add mutual TLS Setup API test"

This reverts commit b23c3cd.

* Revert "remove redundant tests"

This reverts commit 7a7f3ca.
avoids hot-spots by making sure that not all `DaemonMessageRouter`s start routing messages to the same first entity at the start of the list.
…7891)

* Improve TLS/SSL certificate error messages during handshake failures (#7890)

Added TlsErrorMessageBuilder helper class to provide human-readable error messages for TLS certificate validation failures. Enhanced error messages now include:

- Detailed SSL policy error interpretations
- X509 chain status diagnostics with actionable suggestions
- Certificate details (subject, issuer, thumbprint, validity dates)
- Role-specific troubleshooting guidance (client vs server)

Updated certificate validation callback in mutual TLS to use enhanced error messages.
Added TLS exception handling in TcpHandlers to detect and report AuthenticationException and CryptographicException with detailed diagnostics.

All existing TLS tests continue to pass.

* Enhance TLS error logging across all handshake scenarios

Upgraded mutual TLS validation errors from Warning to Error level for better visibility. Enhanced error messages now cover all TLS failure scenarios:

Server-side mutual TLS validation:
- No client certificate provided: detailed error with troubleshooting steps
- Client certificate validation failures: comprehensive chain validation diagnostics

Client-side and general handshake failures:
- Added enhanced error diagnostics to UserEventTriggered for TlsHandshakeCompletionEvent
- Improved client-side troubleshooting guidance including certificate trust chain requirements
- Both client and server TLS exceptions now include role-specific troubleshooting

All error messages provide actionable suggestions and certificate details to aid in diagnosis.
* Fix TLS hostname validation bug and add configurable validation (#7893)

## Summary

This commit addresses GitHub issue #7893 by fixing a critical TLS hostname validation bug and introducing a configurable, type-safe validation system.

## Changes

### Bug Fix
- **Fixed DotNettyTransport.cs:355** - TLS client was incorrectly validating against the client's own certificate DNS name instead of the remote server address
  - Changed from `certificate.GetNameInfo(X509NameType.DnsName, false)` to `remoteAddress.Host`

### New Configuration
- Added `validate-certificate-hostname` config option to Remote.conf
  - Default: `false` (disabled for backward compatibility and mutual TLS flexibility)
  - When enabled: Traditional TLS hostname validation (CN/SAN must match target hostname)
  - When disabled: Only validates certificate chain, ignores hostname mismatches
  - Useful for: Mutual TLS with per-node certificates, IP-based connections, dynamic service discovery

### Type-Safe Validation System
- Introduced enums to prevent primitive confusion in security-critical code:
  - `ChainValidationMode` enum (ValidateChain, IgnoreChainErrors)
  - `HostnameValidationMode` enum (ValidateHostname, IgnoreHostnameMismatch)
- Created `TlsValidationCallbacks` static factory class with:
  - Main `Create()` method accepting enum parameters and logging adapter
  - Convenience methods: `ValidateFull()`, `ValidateChainOnly()`, `ValidateHostnameOnly()`, `AcceptAll()`
  - Detailed error logging with filtered SslPolicyErrors
- Makes validation flags independent and composable
- Replaces ~35 lines of inline callback code with 3 lines of self-documenting factory calls

### Updated SslSettings
- Added `ValidateCertificateHostname` property
- Updated all constructors to accept the new property
- Updated `Create()` method to read from HOCON config

### Test Coverage
- Extended `DotNettyMutualTlsSpec` with 4 new test cases:
  - `Hostname_validation_disabled_should_allow_different_certificates()` - Different certs work with validation disabled
  - `Hostname_validation_enabled_should_reject_different_certificates()` - Different certs fail with validation enabled
  - `Same_certificate_should_connect_in_mutual_tls()` - Typical mutual TLS scenario
  - `Hostname_validation_default_should_be_disabled()` - Verifies backward compatibility

## Technical Details

Chain validation and hostname validation are now fully independent:
- `suppressValidation=true` disables chain/CA validation (for self-signed certs)
- `validateCertificateHostname=true/false` controls hostname matching (for per-node certs, IPs)

This allows testing hostname validation with self-signed certificates by using `suppressValidation=true, validateCertificateHostname=true`.

Fixes #7893

* Extend DotNettySslSetup to expose all SSL/TLS configuration options

## Summary

Extended `DotNettySslSetup` programmatic API to expose the full SSL/TLS configuration, including the newly added hostname validation setting and the existing RequireMutualAuthentication setting that was previously only available via HOCON.

## Changes

### DotNettySslSetup API
- Added `RequireMutualAuthentication` property (was missing from programmatic API)
- Added `ValidateCertificateHostname` property (new setting from #7893)
- Added comprehensive XML documentation for all properties and constructors
- Added backward-compatible constructors:
  - 2-parameter: Defaults to RequireMutualAuthentication=true, ValidateCertificateHostname=false
  - 3-parameter: Defaults to ValidateCertificateHostname=false
  - 4-parameter: Full control over all settings
- Updated `Settings` property to pass all 4 parameters to `SslSettings` constructor

### Integration Tests
- Added 3 integration tests in `DotNettySslSetupSpec`:
  - Verify 2-parameter setup configures effective DotNettyTransportSettings with expected defaults
  - Verify 3-parameter setup configures effective settings with specified RequireMutualAuth
  - Verify 4-parameter setup configures effective settings with all specified values
- Tests verify the actual consumption path: ActorSystem → DotNettyTransportSettings.Create() → setup.Value.Settings
- Tests validate that setup values correctly override HOCON defaults

## Backward Compatibility

All existing code using the 2-parameter constructor continues to work with the same defaults:
- RequireMutualAuthentication: true (matches previous HOCON-only behavior)
- ValidateCertificateHostname: false (matches new HOCON default)

The setup is properly consumed in `DotNettyTransportSettings.Create(ActorSystem)` which retrieves the setup via `system.Settings.Setup.Get<DotNettySslSetup>()` and calls `setup.Value.Settings` to get the fully configured `SslSettings` object.

* Update security documentation for hostname validation feature

## Changes

- Explained the new independent validation system (chain vs hostname)
- Added details about default certificate stores used (Windows, Linux, macOS)
- Documented the `validate-certificate-hostname` setting with use cases
- Added validation mode combination table
- Included configuration examples for both P2P and client-server scenarios
- Added comprehensive troubleshooting for hostname validation errors
- Documented enhanced TLS error messages from v1.5.52
- Reduced emoji usage for more professional tone
- Added links to Microsoft documentation and RFC specifications

## Key Documentation Updates

### New Sections
- Certificate Validation: Independent Control
- Hostname Validation setting explanation
- Validation Mode Combinations table
- Configuration with Hostname Validation Enabled
- Enhanced error message examples

### Troubleshooting Additions
- RemoteCertificateNameMismatch errors (hostname validation failures)
- UntrustedRoot errors (chain validation failures)
- Understanding TLS Error Messages section with real examples
- Multiple fix options for each error scenario

### Technical Details
- Explained which OS certificate stores are used by default
- Referenced RFC 5280 and RFC 6125 for validation standards
- Clarified that suppress-validation only controls chain validation
- Clarified that hostname validation is separate and optional

* Fix markdown linting and spellcheck issues in security docs

- Add blank lines around all fenced code blocks
- Add language specifiers to code blocks (text, hocon, bash, powershell)
- Change dash lists to asterisk lists for consistency
- Add 'hostnames' to spellcheck dictionary
- Emphasize that hostname validation defaults to false (disabled)

* Improve documentation heading structure and title case

- Remove technical setting names from headings
- Use descriptive section titles instead
- Change subheadings to 'Enabled/Disabled' pattern
- Move technical details into content body
- Fix title case linting issues

This makes the documentation more scannable and separates conceptual
sections from implementation details.

* added api approvals
Updated version to 1.5.53 and added release notes documenting security fixes for TLS hostname validation and improved TLS/SSL error messaging.
…bug (#7907)

* Add reproduction tests for SourceRef.Source non-idempotent property bug (#7895)

Added two tests to demonstrate the bug where ISourceRef<T>.Source property
creates a new SourceRefStageImpl instance on every access instead of being
idempotent:

1. SourceRef_Source_property_should_be_idempotent_issue_7895
   - Verifies that multiple .Source property accesses should return the same
     instance (currently fails - demonstrates the bug exists)
   - Tested 25 times with 100% failure rate, proving consistent reproduction

2. SourceRef_multiple_materializations_cause_timeout_issue_7895
   - Demonstrates the race condition when multiple SourceRefStageImpl instances
     try to connect to the same SinkRef
   - Shows intermittent timeouts and failures due to handshake conflicts

These tests will pass once the Source property is made idempotent by caching
the created Source instance.

Issue: #7895

* Fix SourceRef.Source and SinkRef.Sink non-idempotent property bug (#7895)

Implemented Lazy<T> to make both ISourceRef<T>.Source and ISinkRef<T>.Sink
properties idempotent. Previously, these properties created new stage
instances on every access, causing race conditions where multiple instances
would compete for the same handshake, leading to intermittent subscription
timeouts.

Changes:
- SourceRefImpl<T>: Use Lazy<Source<T, NotUsed>> for thread-safe caching
- SinkRefImpl<T>: Use Lazy<Sink<T, NotUsed>> for thread-safe caching
- Lazy<T> uses default ExecutionAndPublication mode for thread safety

Impact:
- Eliminates race conditions from accidental property accesses (debugger,
  logging, serialization, framework inspection)
- Prevents subscription timeouts caused by multiple stage instances
- Fixes intermittent ~30% failure rate in production workloads
- Double materialization (user error) still fails gracefully at actor
  protocol level via ObserveAndValidateSender

Test Results:
- Before fix: Tests failed 25/25 times (100% failure rate)
- After fix: Tests passed 10/10 times (100% success rate)

Fixes #7895
…s null (#7910) (#7911) (#7912)

## Summary
Fixed a bug where LWWDictionary.Delta would throw ArgumentNullException when the underlying ORDictionary.Delta is null, which is a legitimate state after initialization or calling ResetDelta().

## Changes
- Modified LWWDictionary.Delta property to return null when Underlying.Delta is null
- Added test Bugfix_7910_LWWDictionary_Delta_should_handle_null_underlying_delta to verify the fix

## Root Cause
The LWWDictionary.Delta property was unconditionally wrapping Underlying.Delta in a LWWDictionaryDelta constructor, which throws ArgumentNullException when passed null. However, ORDictionary.Delta can legitimately return null after construction or ResetDelta().

The Replicator expects deltas to be nullable (uses ?? operator at lines 665 and 685), so this fix aligns LWWDictionary with the expected interface contract.

Fixes #7910
…7918) (#7919)

* Add failing test to expose DotNettySslSetup override bug (#7917)

Added test case that demonstrates DotNettySslSetup settings being ignored
when HOCON has valid certificate configuration. The test configures:

- HOCON with valid certificate path and settings
- DotNettySslSetup with different certificate and settings

Expected: DotNettySslSetup should take precedence (programmatic over config)
Actual: HOCON certificate is used, DotNettySslSetup is completely ignored

The bug occurs because CreateOrDefault() tries HOCON first and only uses
the programmatic setup as an exception fallback. This test fails and
will pass once the fix is applied to make programmatic setup take precedence.

Existing tests didn't catch this because they only test the exception-based
fallback path (HOCON with enable-ssl=true but no certificate path).

* Fix DotNettySslSetup being ignored when HOCON has valid SSL config (#7917)

Changed SSL settings initialization to prioritize programmatic DotNettySslSetup
over HOCON configuration, fixing the precedence order bug.

Changes:
- Modified DotNettyTransportSettings.Create() to check sslSettings (from
  DotNettySslSetup) first before parsing HOCON configuration
- Changed SslSettings.Create() from private to internal to enable direct usage
- Previous behavior: HOCON always tried first, programmatic setup only used
  as exception fallback
- New behavior: Programmatic setup takes precedence, HOCON used if not provided

This ensures programmatic configuration properly overrides HOCON defaults,
which is the expected behavior for Setup-based configuration in Akka.NET.

The bug existed since DotNettySslSetup was introduced in July 2023
(commit 588d5d6). Existing tests passed only because they triggered
the exception-based fallback path (HOCON with enable-ssl=true but no
certificate path).
…h - fixes mTLS asymmetry bug (#7915) (#7921)

* feat(remote): add CertificateValidationCallback delegate and CertificateValidation helper factory

- Define public CertificateValidationCallback delegate for custom certificate validation
- Add CertificateValidation factory class with 7 helper methods:
  * ValidateChain() - CA chain validation
  * ValidateHostname() - CN/SAN matching
  * PinnedCertificate() - Certificate pinning by thumbprint
  * ValidateSubject() - Subject DN matching (with wildcard support)
  * ValidateIssuer() - Issuer DN matching
  * Combine() - Compose multiple validators
  * ChainPlusThen() - Chain validation + custom logic
- Add CustomValidator property to DotNettySslSetup with overloaded constructors
- Maintain full backward compatibility with existing config-based validation

Relates to #7914

* feat(remote): implement single execution path for certificate validation with hostname validation asymmetry fix

- Integrate custom certificate validators into DotNettyTransport pipelines (client and server)
- Implement single execution path: compose validator from config when custom not provided
- Add ComposeValidatorFromSettings() to build validators from SuppressValidation and ValidateCertificateHostname settings
- Add CustomValidator property to SslSettings with updated constructors for seamless integration
- Fix asymmetry bug: server-side now applies hostname validation like client-side
- Replace dual-path logic (custom vs config-based) with unified composition pattern
- Add hostname matching helper with reflection-based SAN support for multi-framework compatibility
- Eliminates need for TlsValidationCallbacks on each pipeline setup call

* fix: use TlsValidationCallbacks for config-based validation in single execution path

- Revert to using proven TlsValidationCallbacks logic for configuration-based validation
- This maintains compatibility with existing validation behavior while enabling single execution path
- CertificateValidation helpers remain available for custom user validators
- Reduces test failures from 9 to 2 by using well-tested validation logic

* fix: reject missing client certificates in server-side mutual TLS validation

When the server requires mutual TLS authentication (RequireMutualAuthentication=true),
it must reject TLS handshakes where the client fails to provide a certificate.

Previously, the validation callback would pass a null certificate to the composed
validator without pre-checking it. This allowed connections from clients without
certificates to succeed when they should fail.

Now we explicitly check if the certificate is null when mutual auth is required
and immediately reject the connection with a warning log message.

This fixes the failing test: Mutual_TLS_should_fail_when_client_has_no_certificate

Fixes: All 329 tests now pass (324 passed, 5 skipped, 0 failed)

* docs: add programmatic certificate validation examples and consolidate security documentation

Adds comprehensive programmatic certificate validation examples to TlsConfigurationSample:
- ProgrammaticMutualTlsSetup: Basic mutual TLS with custom validators
- CertificatePinningExample: Certificate pinning by thumbprint
- CustomValidationLogicExample: Chain validation + custom business logic
- HostnameValidationExample: Programmatic hostname validation setup
- SubjectValidationExample: Subject DN validation

Consolidates security.md documentation:
- Merged "Hostname Validation" and "Mutual TLS Authentication" into unified
  "Validation Strategies: HOCON vs Programmatic" section with decision matrix
- Added examples for both P2P clusters and client-server architectures
- Cross-referenced sections to reduce duplication
- Clarified when to use programmatic vs HOCON configuration

Follows documentation guidelines (security.md:70):
- Uses !code references with #region tags for live code examples
- Organizes content for discoverability
- Provides decision matrix for choosing validation strategy

* fix: correct compilation errors in TlsConfigurationSample documentation examples

- Add Akka.Remote reference to Akka.Docs.Tests project for DotNettySslSetup types
- Wrap new programmatic examples with #if NET6_0_OR_GREATER for framework compatibility
- Convert example methods to void to simplify documentation-only code
- Fix API usage: use params instead of arrays, correct delegate signatures
- Remove BootstrapSetup complexity from examples to focus on core TLS setup patterns

* fix: wire CustomValidator through SslSettings and add comprehensive integration tests

CRITICAL FIXES:
- Fixed DotNettySslSetup.Settings to pass CustomValidator to SslSettings constructor
  (Line 114 was creating SslSettings without the CustomValidator parameter)
- Removed unused ValidateCertificateHostnameMatch method (489-542)
  (Hostname validation is already handled by TlsValidationCallbacks.Create)

NEW TESTS - CustomValidator Functionality:
- CustomValidator_that_accepts_should_allow_connection
  * Verifies CustomValidator callback is invoked during TLS handshake
  * Verifies acceptance allows successful connection
- CustomValidator_that_rejects_should_prevent_connection
  * Verifies CustomValidator rejection prevents connection
  * Verifies callback was invoked even when rejecting
- DotNettySslSetup_should_pass_CustomValidator_to_SslSettings
  * Unit test verifying CustomValidator is wired through to SslSettings

Addresses PR review comments #7915:
- CustomValidator now properly wired to SslSettings
- Removed dead code (ValidateCertificateHostnameMatch)
- Added real integration tests that validate CustomValidator actually works

* Add warning when both DotNettySslSetup and HOCON SSL certificate config are present

- Logs warning when DotNettySslSetup is used alongside explicit HOCON certificate configuration
- Only warns when HOCON has actual certificate.path or certificate.thumbprint configured
- Avoids false positives from default/empty config sections
- Adds test verifying DotNettySslSetup precedence behavior
- Addresses PR feedback: implement Option 1 from review comment

* Remove unnecessary NET6_0_OR_GREATER conditional compilation directives

- All types used (X509Certificate2, DotNettySslSetup, CertificateValidation) are available in .NET Standard 2.0
- Conditional directives were added during troubleshooting but are not needed
- Verified compilation on both net8.0 and net48 targets

* Consolidate TlsValidationCallbacks into public CertificateValidation API

Removes internal TlsValidationCallbacks class and related enums (~145 lines)
and refactors ComposeValidatorFromSettings() to use the public
CertificateValidation helpers instead.

Changes:
- Remove ChainValidationMode and HostnameValidationMode enums
- Remove TlsValidationCallbacks internal class
- Refactor ComposeValidatorFromSettings() to handle all 4 combinations
  of SuppressValidation and ValidateCertificateHostname flags:
  * suppressChain=true, validateHostname=false → Accept all
  * suppressChain=true, validateHostname=true → Validate hostname only
  * suppressChain=false, validateHostname=true → Chain + hostname
  * suppressChain=false, validateHostname=false → Chain only (default)

Benefits:
- Eliminates code duplication between internal and public APIs
- Simplifies maintenance by having a single validation implementation
- Makes the public CertificateValidation API the canonical approach
- All 43 DotNetty tests pass including edge case validations

* cleaned up `CertificateValidation` composition code for default settings

* Add comprehensive test coverage for CertificateValidation helpers

Added 11 new tests to achieve 100% coverage of previously untested
CertificateValidation helper methods:

PinnedCertificate tests:
- Accept connections with matching thumbprint
- Reject connections with non-matching thumbprint

ValidateSubject tests:
- Accept certificates with matching subject
- Reject certificates with non-matching subject
- Support wildcard pattern matching (CN=Akka-Node-*)

ValidateIssuer tests:
- Accept certificates with matching issuer

Combine/ChainPlusThen tests:
- Verify composability of validators

CustomValidator precedence tests:
- Verify CustomValidator overrides validateCertificateHostname setting

Also removed obsolete Mono checks from all new tests per maintainer
guidance (Mono is no longer supported).

Test results: 18/18 passing (7 existing + 11 new)

* Remove unnecessary Combine() wrapper for single validators in tests

Simplified two test cases that were unnecessarily wrapping single
CertificateValidationCallback delegates in Combine():

- CustomValidator_that_accepts_should_allow_connection
- CustomValidator_that_rejects_should_prevent_connection

Changed from:
  var validator = CertificateValidation.Combine((cert, ...) => true);

To cleaner direct delegate assignment:
  CertificateValidationCallback validator = (cert, ...) => true;

Combine() is only needed when composing multiple validators. These
tests verify single custom validators, so direct assignment is clearer.

All tests still pass (4/4 CustomValidator tests verified).

* added `nullability` annotations to DotNettyTransport

* Remove obsolete Mono and NET471 workarounds from SSL tests

- Removed #if !NET471 conditional compilation directives (10 instances)
  Project now targets net48, making NET471 conditionals meaningless
- Removed if (IsMono) runtime checks (7 instances)
  Modern .NET uses CoreCLR cross-platform, not Mono
- All SSL tests now run unconditionally on supported platforms
- Tests verified passing: 27/27 on net8.0, 26/26 on net48

* Fix null certificate handling in SSL validation methods

- Added explicit null checks to all CertificateValidation helper methods
  - PinnedCertificate: Check for null cert and filter empty thumbprints
  - ValidateSubject/ValidateIssuer: Check for null cert and empty values
  - ValidateHostname: Check for null cert before accessing properties
  - ValidateChain: Check for null cert before chain validation
- Improved error messages to distinguish null cert from other failures
- Added comprehensive unit test coverage for edge cases
- Prevents potential NullReferenceException in TLS handshake scenarios

* Add documentation explaining why case-insensitive thumbprint comparison is safe

Added detailed comment explaining:
- Thumbprints are hexadecimal SHA hash representations
- Hex values are inherently case-insensitive (2A8B == 2a8b)
- Different tools display differently (Windows vs OpenSSL)
- Case-insensitive comparison improves usability without compromising security

* Improve certificate validation tests with EventFilter

- Replaced ExpectMsg with EventFilter for proper log assertion pattern
- EventFilter is the idiomatic way to assert log messages in Akka.NET tests
- Added test for rejecting non-matching thumbprint with EventFilter
- Updated Combine test to clearly document short-circuit behavior
- All tests now properly verify both result AND expected log messages

* Use EventFilter to assert SSL validation errors in multi-actor system tests

Updated SSL integration tests to use EventFilter for asserting specific validation
errors instead of just checking connection failure. This provides better test
precision by verifying the exact reason for connection failure.

With mTLS enabled, validation errors occur on the server side (_sys2) when it
validates the client certificate, since the client (Sys) has suppressValidation
enabled. The EventFilter assertions are correctly targeted to the system where
the validation errors occur.

Changes:
- Added EventFilter assertions to PinnedCertificate rejection test
- Added EventFilter assertions to CustomValidator rejection test
- Added EventFilter assertions to ValidateSubject rejection test
- Modified custom validator to log error for EventFilter detection
- Added comments explaining the mTLS validation flow

* Revert "Use EventFilter to assert SSL validation errors in multi-actor system tests"

This reverts commit 2022d63.

* remove unnecessary project reference

* added API approvals

* Fix incorrect bitwise AND check with SslPolicyErrors.None

The condition `(errors & SslPolicyErrors.None) != SslPolicyErrors.None`
was always false because SslPolicyErrors.None equals 0, and any value
bitwise AND with 0 always results in 0.

Changed to simple equality check `errors != SslPolicyErrors.None` to
correctly detect when SSL policy errors are present.

This bug prevented the TlsErrorMessageBuilder from ever building detailed
error messages when SSL validation failed, making debugging harder.

---------

Co-authored-by: Gregorius Soedharmo <arkatufus@yahoo.com>
…letion (#7941) (#7951)

* fix(streams): prevent race condition in ChannelSource on channel completion (#7940)

Fixed a race condition in ChannelSourceLogic that caused intermittent
NullReferenceException when completing a ChannelWriter while the stream
was waiting for data.

The issue occurred because two async callbacks could fire simultaneously
when the channel writer completed:
1. The _reader.Completion continuation → OnReaderComplete → CompleteStage
2. The WaitToReadAsync continuation → OnValueRead(false) → CompleteStage

Both paths could pass the IsStageCompleted check before either completed
the stage, leading to concurrent access of stage internals.

The fix adds an atomic flag (_completing) using Interlocked.Exchange to
ensure only one completion path ever executes. This is applied to:
- OnReaderComplete - channel completion callback
- OnValueRead - when data is not available
- OnValueReadFailure - when read fails
- OnPull - synchronous completion path

* refactor: use CompareExchange instead of Exchange for atomic flag

CompareExchange is more semantically correct - it only sets the value
if it's currently 0, rather than unconditionally setting it.
…7952)

When TLS is enabled, invalid traffic (like HTTP requests) hitting the
Akka.Remote port would cause the entire ActorSystem to shut down with
exit code 79. This was due to overly aggressive TLS handshake failure
handling introduced in #7839.

Changes:
- Modified TcpTransport to only trigger CoordinatedShutdown for client-side
  TLS handshake failures (outbound connections we initiate)
- Server-side TLS failures (incoming invalid connections) now just log a
  warning and reject the connection without shutting down
- Added test to verify servers remain running when invalid traffic hits
  the TLS port

This makes Akka.Remote resilient to port scanners, misconfigured clients,
or malicious traffic while maintaining strict security for legitimate
connections.

Fixes #7938
…7933) (#7955)

* feat: Add native semantic logging support to Akka.NET core

Implements semantic/structured logging with support for both positional ({0})
and named ({PropertyName}) message templates, enabling structured property
extraction for external logging frameworks.

Key Features:
- MessageTemplateParser with ThreadStatic LRU cache for template parsing
- LogMessage enhanced with PropertyNames and GetProperties() APIs
- SemanticLogMessageFormatter for Serilog-style template formatting
- LogEventExtensions helper methods for easy property extraction
- StandardOutLogger updated to display semantic properties
- Zero new dependencies - pure BCL implementation
- Full backward compatibility maintained

Performance Optimizations:
- ThreadStatic caching avoids lock contention
- Lazy property evaluation (zero cost if not used)
- FrozenDictionary on .NET 8+ for optimal read performance
- LRU eviction prevents unbounded cache growth

Testing:
- 25 new unit tests covering template parsing, property extraction, and formatting
- All 79 existing logger tests pass (full backward compatibility)
- Tests validate positional templates, named templates, edge cases, and caching

This enables external logger plugins (Serilog, NLog, MEL) to easily extract
structured properties using logEvent.TryGetProperties() for integration with
their native structured logging capabilities.

Addresses #7932

* perf: optimize semantic logging memory allocations (75% reduction)

Implemented Priority 1 performance optimizations to reduce GC pressure
in semantic logging operations.

Changes:
- LogMessage.GetProperties(): Avoid ToArray() when Parameters() returns
  IReadOnlyList<object> (LogValues<T> structs), saving ~200-300 bytes
- SemanticLogMessageFormatter.Format(): Check args type before conversion,
  use IReadOnlyList directly for named templates, only convert to array
  when required by string.Format(), saving ~500-800 bytes
- SemanticLoggingBenchmarks: Add comprehensive benchmark suite (34 benchmarks)
  and fix GlobalSetup to include GetProperties benchmarks

Performance Results:
- Full E2E pipeline: 1592B → 400B (75% reduction) 🎯
- Format 3 params: 1248B → 680B (45% reduction)
- GetProperties access: 526ns → 1.7ns (99.7% faster)
- Template cache hits: 70ns → 47ns (33% faster)
- E2E semantic logging: 1.34μs → 284ns (79% faster)

All 79 unit tests passing. Benchmarks confirm optimizations maintain
correctness while achieving target allocation reductions.

Addresses #7932

* Enable SemanticLogMessageFormatter as default logger formatter

Changed the default logger formatter from DefaultLogMessageFormatter to SemanticLogMessageFormatter to enable semantic logging support by default. This allows both positional {0} and named {PropertyName} templates to work out of the box.

Changes:
- Updated akka.conf to use SemanticLogMessageFormatter as default
- Added special case handling in Settings.cs for SemanticLogMessageFormatter singleton instance

All 62 existing logger tests pass, confirming backward compatibility with positional templates while enabling new semantic logging capabilities.

* feat: Add EventFilter support for semantic logging templates

Enables EventFilter to match against semantic logging templates in unit tests, resolving the core issue from GitHub #7932 where EventFilter.Info("BetId:{BetId}") would fail to match log messages using named property syntax.

Changes:
- Modified EventFilterBase.InternalDoMatch to check LogMessage.Format template before falling back to formatted output
- Allows matching against both template patterns ("{UserId}") and formatted values ("12345")
- Added comprehensive tests for EventFilter with semantic templates (exact match, contains, starts with)
- Removed FormatException catching for positional templates to maintain backward compatibility with DefaultLogMessageFormatter

All 66 logger tests pass, including 4 new EventFilter semantic logging tests and existing backward compatibility tests.

* test: Add semantic logging integration tests for log filtering

Added 8 comprehensive tests verifying that log filtering works correctly with semantic logging templates. Tests cover:

- Filtering by formatted message content with named properties
- Filtering by property values (e.g., {AlertLevel} = "CRITICAL")
- Multiple properties in single log message
- Positional templates with filtering (backward compatibility)
- Source filtering combined with semantic logging
- Format specifiers in templates (e.g., {Amount:N2})
- Messages that should pass through filters

All 25 log filter tests pass (17 existing + 8 new), confirming semantic logging integrates seamlessly with the log filtering system introduced in v1.5.21.

* fix: Update ConfigurationSpec to expect SemanticLogMessageFormatter as default

Updated the configuration validation test to expect SemanticLogMessageFormatter instead of DefaultLogMessageFormatter as the default logger formatter, matching the change made in commit f9a2d2c.

All 4 configuration tests pass.

* fix: enable nullable reference types in LogEventExtensions

- Added #nullable enable directive
- Marked 'properties' out parameter as nullable in TryGetProperties
- Ensures proper null safety for the semantic logging API

* test: Add semantic logging edge cases verification test

- Added ShouldHandleSemanticLogEdgeCases test to DefaultLogFormatSpec
- Tests named properties, positional properties, mixed types, null values,
  special characters, booleans, dates, and formatting alignment
- Reuses existing sanitization methods from DefaultLogFormatSpec
- Verifies semantic logging formatter output for various edge cases

* Update API Approval list

* Add new edge case unit tests (failing)

* docs: Add Message Templates spec reference to SemanticLogMessageFormatter

- Added link to https://messagetemplates.org/ specification
- Documented supported syntax (named/positional properties, format specifiers, alignment, escaped braces)
- Documented unsupported syntax (destructuring operators, empty property names)

* fix: Correct escaped brace handling in semantic logging per Message Templates spec

Parser fixes:
- Removed incorrect }} check after placeholder closing brace
- Parser now correctly extracts {UserId} from "{UserId}}" and "{{{UserId}}}"

Formatter fixes:
- Rewrote FormatNamedTemplate to handle }} in literal text correctly
- Added UnescapeBraces helper for templates with no placeholders
- "Use {{ and }}" now correctly produces "Use { and }"

Test updates:
- Updated {:N2} test to document as invalid per Message Templates spec
- Invalid templates have "garbage in, garbage out" behavior (not crashing)

Fixes edge cases reported in commit 1c58a6b.
All 34 semantic logging tests now pass.

* fix: Use culture-independent format specifiers in verify test

The ShouldHandleSemanticLogEdgeCases verify test was failing on CI due
to locale differences:
- {Amount:C} produces $123.45 on US locale but ¤123.45 on invariant
- DateTime.ToString() produces different formats per locale

Changed to culture-independent formats:
- Use ${Amount:F2} (literal $ + fixed-point number) instead of {Amount:C}
- Use {JoinDate:yyyy-MM-dd} (ISO 8601) for dates

* test: Add escaped brace benchmarks and .NET Framework verified file

- Added benchmark category for escaped brace handling to track
  performance of edge case fixes
- Added .Net.verified.txt baseline for .NET Framework 4.8 CI runs

* Add unit tests

* fix: Implement alignment specifiers and null ToString() handling in SemanticLogMessageFormatter

- Add support for alignment specifiers in named templates per Message Templates spec
  - Parse {Name,alignment:format} syntax correctly
  - Apply PadLeft() for positive alignment (right-align)
  - Apply PadRight() for negative alignment (left-align)

- Fix null handling when ToString() returns null
  - Check ToString() result before attempting format operations
  - Return "null" string instead of empty string for null ToString() results
  - Handles both plain and formatted property cases

- Fix test bug: missing '>' character in alignment test format string

These changes ensure the semantic logging formatter correctly implements the
Message Templates specification for alignment and handles defensive edge cases.

---------

Co-authored-by: Gregorius Soedharmo <arkatufus@yahoo.com>
Arkatufus and others added 3 commits December 2, 2025 10:38
…ative to #7937 (#7954) (#7957)

* feat(persistence): add completion callbacks and async handler support

Add completion callback overloads for PersistAll and PersistAllAsync that
invoke a callback after all events have been persisted and their handlers
executed. Also add async handler support (Func<TEvent, Task>) to all
persist methods.

Key changes:
- Add IPendingHandlerInvocation, ISyncHandlerInvocation, IAsyncHandlerInvocation,
  and IStashingInvocation interfaces for type-safe handler invocation
- Add StashingHandlerInvocation, StashingAsyncHandlerInvocation,
  AsyncHandlerInvocation, and AsyncAsyncHandlerInvocation classes
- Add Persist<TEvent>(TEvent, Func<TEvent, Task>) async handler overload
- Add PersistAsync<TEvent>(TEvent, Func<TEvent, Task>) async handler overload
- Add PersistAll overloads with completion callbacks (sync and async)
- Add PersistAllAsync overloads with completion callbacks (sync and async)
- Add DeferAsync<TEvent>(TEvent, Func<TEvent, Task>) async handler overload
- Add internal stashing Defer methods for completion callback support
- Update PeekApplyHandler to handle async handlers via RunTask
- Update PersistingEvents to use IStashingInvocation marker interface

Stashing semantics are preserved: PersistAll completion callbacks use
internal stashing Defer (increments _pendingStashingPersistInvocations),
while PersistAllAsync uses non-stashing DeferAsync.

* chore: update API approval for persistence completion callbacks

Update verified API file to reflect:
- New public methods: Persist/PersistAsync/PersistAll/PersistAllAsync async
  handler overloads and completion callback overloads
- New public method: DeferAsync with async handler
- Internal invocation classes: AsyncHandlerInvocation, StashingHandlerInvocation,
  and IPendingHandlerInvocation are now internal (implementation detail)

* chore: update .NET Framework API approval for persistence completion callbacks

* fixed API approvals

* test(persistence): add empty events tests and convert to async test methods

- Convert all tests to use async/await with ExpectMsgAsync instead of
  sync-over-async ExpectMsg calls
- Add tests for PersistAll/PersistAllAsync with empty events to verify
  completion callbacks are invoked immediately for all overloads:
  - PersistAll with sync completion callback (existing)
  - PersistAll with async completion callback (new)
  - PersistAllAsync with sync completion callback (new)
  - PersistAllAsync with async completion callback (new)
- Update EmptyEventsWithCompletionActor to support all four scenarios

* fix(persistence): use Defer for empty events completion callbacks to maintain ordering

When PersistAll/PersistAllAsync is called with empty events, the completion
callback must still be queued through Defer/DeferAsync to maintain the
in-order execution guarantee. Previously, the callback was invoked immediately
which could cause out-of-order execution if there were pending invocations
from prior Persist/PersistAll calls.

Changes:
- Replace immediate invocation with Defer/DeferAsync for all 8 overloads
  that have completion callbacks when events collection is null or empty
- Add SequentialPersistOrderingActor test actor for ordering verification
- Add test: Persist followed by empty PersistAll maintains execution order
- Add test: Sequential PersistAll with empty in middle maintains order
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants