From 75fdc96e3d27ac345823f147dca0edb6494a2a4d Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Tue, 7 Oct 2025 21:10:11 -0500 Subject: [PATCH 01/12] Fix writer starvation in MemoryJournal by replacing ReaderWriterLockSlim with Monitor Replaced ReaderWriterLockSlim with simple object lock (Monitor) to prevent writer starvation in MemoryJournal. The previous implementation caused flaky test failures when query publishers continuously acquired read locks, preventing persistent actors from acquiring write locks. Changes: - Changed Lock property type from ReaderWriterLockSlim to object in MemoryJournal and SharedMemoryJournal - Replaced all EnterReadLock/ExitReadLock and EnterWriteLock/ExitWriteLock calls with lock statements - Simplified Add() and Delete() methods to use single lock acquisition - Updated API verification files for Akka.Persistence Benefits: - Fair FIFO lock ordering prevents writer starvation - Simpler code with unified lock API - Sufficient performance for in-memory test journal Fixes race condition timeouts in InMemoryAllEventsSpec and InMemoryEventsByTagSpec tests. --- ...ISpec.ApprovePersistence.Core.verified.txt | 209 +++++++++++++----- ...pec.ApprovePersistence.DotNet.verified.txt | 4 +- ...PISpec.ApprovePersistence.Net.verified.txt | 4 +- .../Akka.Persistence/Journal/MemoryJournal.cs | 107 ++------- 4 files changed, 177 insertions(+), 147 deletions(-) diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Core.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Core.verified.txt index 0f5939a1aef..06fd3dfef94 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Core.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Core.verified.txt @@ -4,7 +4,7 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Persistence.Tests")] [assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)] [assembly: System.Runtime.InteropServices.GuidAttribute("e3bcba88-003c-4cda-8a60-f0c2553fe3c8")] -[assembly: System.Runtime.Versioning.TargetFrameworkAttribute(".NETStandard,Version=v2.0", FrameworkDisplayName=".NET Standard 2.0")] +[assembly: System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName=".NET 6.0")] namespace Akka.Persistence { public sealed class AsyncHandlerInvocation : Akka.Persistence.IPendingHandlerInvocation @@ -113,6 +113,18 @@ namespace Akka.Persistence public override int GetHashCode() { } public override string ToString() { } } + public sealed class CheckJournalHealth : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalRequest, Akka.Persistence.IPersistenceMessage + { + public CheckJournalHealth(System.Threading.CancellationToken cancellationToken) { } + public System.Threading.CancellationToken CancellationToken { get; } + public override string ToString() { } + } + public sealed class CheckSnapshotStoreHealth : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotRequest + { + public CheckSnapshotStoreHealth(System.Threading.CancellationToken cancellationToken) { } + public System.Threading.CancellationToken CancellationToken { get; } + public override string ToString() { } + } public sealed class DeleteMessagesFailure : Akka.Actor.INoSerializationVerificationNeeded, System.IEquatable { public DeleteMessagesFailure(System.Exception cause, long toSequenceNr) { } @@ -205,6 +217,7 @@ namespace Akka.Persistence public DiscardConfigurator() { } public Akka.Persistence.IStashOverflowStrategy Create(Akka.Configuration.Config config) { } } + [System.Runtime.CompilerServices.NullableAttribute(0)] public class DiscardToDeadLetterStrategy : Akka.Persistence.IStashOverflowStrategy { public static Akka.Persistence.DiscardToDeadLetterStrategy Instance { get; } @@ -318,6 +331,12 @@ namespace Akka.Persistence { Akka.Persistence.IStashOverflowStrategy Create(Akka.Configuration.Config config); } + public sealed class JournalHealthCheckResponse : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalResponse, Akka.Persistence.IPersistenceMessage + { + public JournalHealthCheckResponse(Akka.Persistence.PersistenceHealthCheckResult result) { } + public Akka.Persistence.PersistenceHealthCheckResult Result { get; } + public override string ToString() { } + } public sealed class LoadSnapshot : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotRequest, System.IEquatable { public LoadSnapshot(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, long toSequenceNr) { } @@ -377,10 +396,37 @@ namespace Akka.Persistence public Akka.Persistence.IStashOverflowStrategy DefaultInternalStashOverflowStrategy { get; } public Akka.Persistence.PersistenceSettings Settings { get; } public Akka.Persistence.Journal.EventAdapters AdaptersFor(string journalPluginId) { } + public System.Threading.Tasks.Task CheckJournalHealthAsync(string journalPluginId, System.Threading.CancellationToken cancellationToken = null) { } + public System.Threading.Tasks.Task CheckSnapshotStoreHealthAsync(string snapshotStorePluginId, System.Threading.CancellationToken cancellationToken = null) { } + [Akka.Annotations.InternalStableApiAttribute()] public Akka.Actor.IActorRef JournalFor(string journalPluginId) { } public string PersistenceId(Akka.Actor.IActorRef actor) { } + [Akka.Annotations.InternalStableApiAttribute()] public Akka.Actor.IActorRef SnapshotStoreFor(string snapshotPluginId) { } } + [System.Runtime.CompilerServices.IsReadOnlyAttribute()] + [System.Runtime.CompilerServices.NullableAttribute(0)] + public struct PersistenceHealthCheckResult : System.IEquatable + { + public PersistenceHealthCheckResult(Akka.Persistence.PersistenceHealthStatus Status, string Description = null, System.Exception Exception = null, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { + 2, + 0, + 0})] System.Collections.Generic.IReadOnlyDictionary Data = null) { } + [System.Runtime.CompilerServices.NullableAttribute(new byte[] { + 2, + 0, + 0})] + public System.Collections.Generic.IReadOnlyDictionary Data { get; set; } + public string Description { get; set; } + public System.Exception Exception { get; set; } + public Akka.Persistence.PersistenceHealthStatus Status { get; set; } + } + public enum PersistenceHealthStatus + { + Healthy = 0, + Degraded = 1, + Unhealthy = 2, + } public sealed class PersistenceSettings : Akka.Actor.Settings { public PersistenceSettings(Akka.Actor.ActorSystem system, Akka.Configuration.Config config) { } @@ -442,34 +488,48 @@ namespace Akka.Persistence protected PersistentActor() { } protected override bool Receive(object message) { } } + [System.Runtime.CompilerServices.NullableAttribute(0)] public abstract class ReceivePersistentActor : Akka.Persistence.UntypedPersistentActor, Akka.Actor.Internal.IInitializableActor { protected ReceivePersistentActor() { } protected void Become(System.Action configure) { } protected void BecomeStacked(System.Action configure) { } - protected void Command(System.Action handler, System.Predicate shouldHandle = null) { } - protected void Command(System.Predicate shouldHandle, System.Action handler) { } - protected void Command(System.Type messageType, System.Action handler, System.Predicate shouldHandle = null) { } + protected void Command<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Action handler, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { + 2, + 1})] System.Predicate shouldHandle = null) { } + protected void Command<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Predicate shouldHandle, System.Action handler) { } + protected void Command(System.Type messageType, System.Action handler, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { + 2, + 1})] System.Predicate shouldHandle = null) { } protected void Command(System.Type messageType, System.Predicate shouldHandle, System.Action handler) { } - protected void Command(System.Func handler) { } + protected void Command<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Func handler) { } protected void Command(System.Type messageType, System.Func handler) { } protected void Command(System.Action handler) { } protected void CommandAny(System.Action handler) { } protected void CommandAnyAsync(System.Func handler) { } - protected void CommandAsync(System.Func handler, System.Predicate shouldHandle = null) { } - protected void CommandAsync(System.Predicate shouldHandle, System.Func handler) { } - protected void CommandAsync(System.Type messageType, System.Func handler, System.Predicate shouldHandle = null) { } + protected void CommandAsync<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Func handler, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { + 2, + 1})] System.Predicate shouldHandle = null) { } + protected void CommandAsync<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Predicate shouldHandle, System.Func handler) { } + protected void CommandAsync(System.Type messageType, System.Func handler, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { + 2, + 1})] System.Predicate shouldHandle = null) { } protected void CommandAsync(System.Type messageType, System.Predicate shouldHandle, System.Func handler) { } protected virtual void OnCommand(object message) { } protected virtual void OnRecover(object message) { } - protected void Recover(System.Action handler, System.Predicate shouldHandle = null) { } - protected void Recover(System.Predicate shouldHandle, System.Action handler) { } - protected void Recover(System.Type messageType, System.Action handler, System.Predicate shouldHandle = null) { } + protected void Recover<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Action handler, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { + 2, + 1})] System.Predicate shouldHandle = null) { } + protected void Recover<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Predicate shouldHandle, System.Action handler) { } + protected void Recover(System.Type messageType, System.Action handler, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { + 2, + 1})] System.Predicate shouldHandle = null) { } protected void Recover(System.Type messageType, System.Predicate shouldHandle, System.Action handler) { } - protected void Recover(System.Func handler) { } + protected void Recover<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Func handler) { } protected void Recover(System.Type messageType, System.Func handler) { } protected void RecoverAny(System.Action handler) { } } + [System.Runtime.CompilerServices.NullableAttribute(0)] public sealed class Recovery { public Recovery() { } @@ -484,6 +544,7 @@ namespace Akka.Persistence } public sealed class RecoveryCompleted { + [System.Runtime.CompilerServices.NullableAttribute(1)] public static readonly Akka.Persistence.RecoveryCompleted Instance; public override bool Equals(object obj) { } public override int GetHashCode() { } @@ -502,10 +563,11 @@ namespace Akka.Persistence public RecoveryTick(bool snapshot) { } public bool Snapshot { get; } } + [System.Runtime.CompilerServices.NullableAttribute(0)] public sealed class RecoveryTimedOutException : Akka.Actor.AkkaException { public RecoveryTimedOutException() { } - public RecoveryTimedOutException(string message, System.Exception cause = null) { } + public RecoveryTimedOutException(string message, [System.Runtime.CompilerServices.NullableAttribute(2)] System.Exception cause = null) { } public RecoveryTimedOutException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } public sealed class ReplayMessages : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalRequest, Akka.Persistence.IPersistenceMessage, System.IEquatable @@ -539,6 +601,7 @@ namespace Akka.Persistence public override int GetHashCode() { } public override string ToString() { } } + [System.Runtime.CompilerServices.NullableAttribute(0)] public sealed class ReplyToStrategy : Akka.Persistence.IStashOverflowStrategy { public ReplyToStrategy(object response) { } @@ -585,7 +648,8 @@ namespace Akka.Persistence } public sealed class SnapshotMetadata : System.IEquatable { - public static System.DateTime TimestampNotSpecified; + [System.ObsoleteAttribute("This constructor is deprecated and will be removed in v1.6. Use the constructor w" + + "ith the timestamp parameter instead. Since v1.5.28", true)] public SnapshotMetadata(string persistenceId, long sequenceNr) { } [Newtonsoft.Json.JsonConstructorAttribute()] public SnapshotMetadata(string persistenceId, long sequenceNr, System.DateTime timestamp) { } @@ -624,6 +688,12 @@ namespace Akka.Persistence public override int GetHashCode() { } public override string ToString() { } } + public sealed class SnapshotStoreHealthCheckResponse : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotResponse + { + public SnapshotStoreHealthCheckResponse(Akka.Persistence.PersistenceHealthCheckResult result) { } + public Akka.Persistence.PersistenceHealthCheckResult Result { get; } + public override string ToString() { } + } public sealed class StashingHandlerInvocation : Akka.Persistence.IPendingHandlerInvocation { public StashingHandlerInvocation(object evt, System.Action handler) { } @@ -635,6 +705,7 @@ namespace Akka.Persistence public ThrowExceptionConfigurator() { } public Akka.Persistence.IStashOverflowStrategy Create(Akka.Configuration.Config config) { } } + [System.Runtime.CompilerServices.NullableAttribute(0)] public class ThrowOverflowExceptionStrategy : Akka.Persistence.IStashOverflowStrategy { public static Akka.Persistence.ThrowOverflowExceptionStrategy Instance { get; } @@ -659,6 +730,7 @@ namespace Akka.Persistence public override int GetHashCode() { } public override string ToString() { } } + [System.Runtime.CompilerServices.NullableAttribute(0)] public abstract class UntypedPersistentActor : Akka.Persistence.Eventsourced { protected UntypedPersistentActor() { } @@ -729,6 +801,28 @@ namespace Akka.Persistence public static Akka.Persistence.WriteMessagesSuccessful Instance { get; } } } +namespace Akka.Persistence.Delivery +{ + [System.Runtime.CompilerServices.NullableAttribute(0)] + public class static EventSourcedProducerQueue + { + public static Akka.Actor.Props Create<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(string persistentId, Akka.Persistence.Delivery.EventSourcedProducerQueue.Settings settings) { } + public static Akka.Actor.Props Create<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(string persistentId, Akka.Actor.IActorRefFactory system) { } + [System.Runtime.CompilerServices.NullableAttribute(0)] + public class Settings : System.IEquatable + { + public System.TimeSpan CleanupUnusedAfter { get; set; } + public bool DeleteEvents { get; set; } + public string JournalPluginId { get; set; } + public int KeepNSnapshots { get; set; } + public System.TimeSpan RestartMaxBackoff { get; set; } + public int SnapshotEvery { get; set; } + public string SnapshotPluginId { get; set; } + public static Akka.Persistence.Delivery.EventSourcedProducerQueue.Settings Create(Akka.Actor.ActorSystem sys) { } + public static Akka.Persistence.Delivery.EventSourcedProducerQueue.Settings Create(Akka.Configuration.Config config) { } + } + } +} namespace Akka.Persistence.Fsm { public class static PersistentFSM @@ -832,26 +926,28 @@ namespace Akka.Persistence.Journal { protected readonly bool CanPublish; protected AsyncWriteJournal() { } - protected abstract System.Threading.Tasks.Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr); - public abstract System.Threading.Tasks.Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr); + public virtual System.Threading.Tasks.Task CheckHealthAsync(System.Threading.CancellationToken cancellationToken = null) { } + protected abstract System.Threading.Tasks.Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr, System.Threading.CancellationToken cancellationToken); + public abstract System.Threading.Tasks.Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr, System.Threading.CancellationToken cancellationToken); protected virtual bool Receive(object message) { } protected virtual bool ReceivePluginInternal(object message) { } protected bool ReceiveWriteJournal(object message) { } public abstract System.Threading.Tasks.Task ReplayMessagesAsync(Akka.Actor.IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, System.Action recoveryCallback); protected static System.Exception TryUnwrapException(System.Exception e) { } - protected abstract System.Threading.Tasks.Task> WriteMessagesAsync(System.Collections.Generic.IEnumerable messages); + protected abstract System.Threading.Tasks.Task> WriteMessagesAsync(System.Collections.Generic.IEnumerable messages, System.Threading.CancellationToken cancellationToken); } - public abstract class AsyncWriteProxy : Akka.Persistence.Journal.AsyncWriteJournal, Akka.Actor.IActorStash, Akka.Actor.IWithUnboundedStash, Akka.Actor.IWithUnrestrictedStash, Akka.Dispatch.IRequiresMessageQueue + public abstract class AsyncWriteProxy : Akka.Persistence.Journal.AsyncWriteJournal, Akka.Actor.IActorStash, Akka.Actor.IWithTimers, Akka.Actor.IWithUnboundedStash, Akka.Actor.IWithUnrestrictedStash, Akka.Dispatch.IRequiresMessageQueue { protected AsyncWriteProxy() { } public Akka.Actor.IStash Stash { get; set; } public abstract System.TimeSpan Timeout { get; } + public Akka.Actor.ITimerScheduler Timers { get; set; } public override void AroundPreStart() { } protected override bool AroundReceive(Akka.Actor.Receive receive, object message) { } - protected override System.Threading.Tasks.Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr) { } - public override System.Threading.Tasks.Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr) { } + protected override System.Threading.Tasks.Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr, System.Threading.CancellationToken cancellationToken) { } + public override System.Threading.Tasks.Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr, System.Threading.CancellationToken cancellationToken) { } public override System.Threading.Tasks.Task ReplayMessagesAsync(Akka.Actor.IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, System.Action recoveryCallback) { } - protected override System.Threading.Tasks.Task> WriteMessagesAsync(System.Collections.Generic.IEnumerable messages) { } + protected override System.Threading.Tasks.Task> WriteMessagesAsync(System.Collections.Generic.IEnumerable messages, System.Threading.CancellationToken cancellationToken) { } public class InitTimeout { public static Akka.Persistence.Journal.AsyncWriteProxy.InitTimeout Instance { get; } @@ -906,6 +1002,7 @@ namespace Akka.Persistence.Journal public System.Collections.Generic.IEnumerable Events { get; } public bool Equals(Akka.Persistence.Journal.IEventSequence other) { } public override bool Equals(object obj) { } + public override int GetHashCode() { } } public class EventAdapters { @@ -927,10 +1024,11 @@ namespace Akka.Persistence.Journal public System.Collections.Generic.IEnumerable Events { get; } public bool Equals(Akka.Persistence.Journal.IEventSequence other) { } public override bool Equals(object obj) { } + public override int GetHashCode() { } } public interface IAsyncRecovery { - System.Threading.Tasks.Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr); + System.Threading.Tasks.Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr, System.Threading.CancellationToken cancellationToken); System.Threading.Tasks.Task ReplayMessagesAsync(Akka.Actor.IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, System.Action recoveryCallback); } public interface IEmptyEventSequence : Akka.Persistence.Journal.IEventSequence { } @@ -939,14 +1037,6 @@ namespace Akka.Persistence.Journal { System.Collections.Generic.IEnumerable Events { get; } } - public interface IMemoryMessages - { - System.Collections.Generic.IDictionary> Add(Akka.Persistence.IPersistentRepresentation persistent); - System.Collections.Generic.IDictionary> Delete(string pid, long seqNr); - long HighestSequenceNr(string pid); - System.Collections.Generic.IEnumerable Read(string pid, long fromSeqNr, long toSeqNr, long max); - System.Collections.Generic.IDictionary> Update(string pid, long seqNr, System.Func updater); - } public interface IReadEventAdapter { Akka.Persistence.Journal.IEventSequence FromJournal(object evt, string manifest); @@ -966,17 +1056,19 @@ namespace Akka.Persistence.Journal public class MemoryJournal : Akka.Persistence.Journal.AsyncWriteJournal { public MemoryJournal() { } - protected virtual System.Collections.Concurrent.ConcurrentDictionary> Messages { get; } + protected virtual System.Collections.Generic.Dictionary DeletedTo { get; } + protected virtual System.Collections.Generic.List EventLog { get; } + protected virtual System.Collections.Generic.Dictionary> EventsByPersistenceId { get; } + protected virtual object Lock { get; } public System.Collections.Generic.IDictionary> Add(Akka.Persistence.IPersistentRepresentation persistent) { } public System.Collections.Generic.IDictionary> Delete(string pid, long seqNr) { } - protected override System.Threading.Tasks.Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr) { } + protected override System.Threading.Tasks.Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr, System.Threading.CancellationToken cancellationToken) { } public long HighestSequenceNr(string pid) { } - public System.Collections.Generic.IEnumerable Read(string pid, long fromSeqNr, long toSeqNr, long max) { } - public override System.Threading.Tasks.Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr) { } + public System.Collections.Generic.IEnumerable Read(string pid, long from, long to, long max) { } + public override System.Threading.Tasks.Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr, System.Threading.CancellationToken cancellationToken) { } protected override bool ReceivePluginInternal(object message) { } public override System.Threading.Tasks.Task ReplayMessagesAsync(Akka.Actor.IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, System.Action recoveryCallback) { } - public System.Collections.Generic.IDictionary> Update(string pid, long seqNr, System.Func updater) { } - protected override System.Threading.Tasks.Task> WriteMessagesAsync(System.Collections.Generic.IEnumerable messages) { } + protected override System.Threading.Tasks.Task> WriteMessagesAsync(System.Collections.Generic.IEnumerable messages, System.Threading.CancellationToken cancellationToken) { } public sealed class CurrentPersistenceIds : Akka.Event.IDeadLetterSuppression { public readonly System.Collections.Generic.IEnumerable AllPersistenceIds; @@ -1033,6 +1125,7 @@ namespace Akka.Persistence.Journal { public readonly int Offset; public readonly Akka.Persistence.IPersistentRepresentation Persistent; + [System.ObsoleteAttribute("If there are tags, they will be stored in the PersistentRepresentation")] public readonly string Tag; public ReplayedTaggedMessage(Akka.Persistence.IPersistentRepresentation persistent, string tag, int offset) { } } @@ -1043,10 +1136,11 @@ namespace Akka.Persistence.Journal public Akka.Actor.IActorRef ReplyTo { get; } } } - public class PersistencePluginProxy : Akka.Actor.ActorBase, Akka.Actor.IActorStash, Akka.Actor.IWithUnboundedStash, Akka.Actor.IWithUnrestrictedStash, Akka.Dispatch.IRequiresMessageQueue + public class PersistencePluginProxy : Akka.Actor.ActorBase, Akka.Actor.IActorStash, Akka.Actor.IWithTimers, Akka.Actor.IWithUnboundedStash, Akka.Actor.IWithUnrestrictedStash, Akka.Dispatch.IRequiresMessageQueue { public PersistencePluginProxy(Akka.Configuration.Config config) { } public Akka.Actor.IStash Stash { get; set; } + public Akka.Actor.ITimerScheduler Timers { get; set; } protected override void PreStart() { } protected override bool Receive(object message) { } public static void SetTargetLocation(Akka.Actor.ActorSystem system, Akka.Actor.Address address) { } @@ -1088,7 +1182,10 @@ namespace Akka.Persistence.Journal public class SharedMemoryJournal : Akka.Persistence.Journal.MemoryJournal { public SharedMemoryJournal() { } - protected override System.Collections.Concurrent.ConcurrentDictionary> Messages { get; } + protected override System.Collections.Generic.Dictionary DeletedTo { get; } + protected override System.Collections.Generic.List EventLog { get; } + protected override System.Collections.Generic.Dictionary> EventsByPersistenceId { get; } + protected override object Lock { get; } } public struct SingleEventSequence : Akka.Persistence.Journal.IEventSequence, System.IEquatable { @@ -1096,12 +1193,15 @@ namespace Akka.Persistence.Journal public System.Collections.Generic.IEnumerable Events { get; } public bool Equals(Akka.Persistence.Journal.IEventSequence other) { } public override bool Equals(object obj) { } + public override int GetHashCode() { } } public struct Tagged { public Tagged(object payload, System.Collections.Generic.IEnumerable tags) { } public Tagged(object payload, System.Collections.Immutable.IImmutableSet tags) { } + [get: System.Runtime.CompilerServices.IsReadOnlyAttribute()] public object Payload { get; } + [get: System.Runtime.CompilerServices.IsReadOnlyAttribute()] public System.Collections.Immutable.IImmutableSet Tags { get; } } public abstract class WriteJournalBase : Akka.Actor.ActorBase @@ -1143,14 +1243,14 @@ namespace Akka.Persistence.Snapshot public class LocalSnapshotStore : Akka.Persistence.Snapshot.SnapshotStore { public LocalSnapshotStore() { } - protected override System.Threading.Tasks.Task DeleteAsync(Akka.Persistence.SnapshotMetadata metadata) { } - protected override System.Threading.Tasks.Task DeleteAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria) { } + protected override System.Threading.Tasks.Task DeleteAsync(Akka.Persistence.SnapshotMetadata metadata, System.Threading.CancellationToken cancellationToken) { } + protected override System.Threading.Tasks.Task DeleteAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken) { } protected System.IO.FileInfo GetSnapshotFileForWrite(Akka.Persistence.SnapshotMetadata metadata, string extension = "") { } - protected override System.Threading.Tasks.Task LoadAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria) { } + protected override System.Threading.Tasks.Task LoadAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken) { } protected override void PreStart() { } protected override bool ReceivePluginInternal(object message) { } protected virtual void Save(Akka.Persistence.SnapshotMetadata metadata, object snapshot) { } - protected override System.Threading.Tasks.Task SaveAsync(Akka.Persistence.SnapshotMetadata metadata, object snapshot) { } + protected override System.Threading.Tasks.Task SaveAsync(Akka.Persistence.SnapshotMetadata metadata, object snapshot, System.Threading.CancellationToken cancellationToken) { } protected void Serialize(System.IO.Stream stream, Akka.Persistence.Serialization.Snapshot snapshot) { } protected System.IO.FileInfo WithOutputStream(Akka.Persistence.SnapshotMetadata metadata, System.Action p) { } } @@ -1158,18 +1258,18 @@ namespace Akka.Persistence.Snapshot { public MemorySnapshotStore() { } protected virtual System.Collections.Generic.List Snapshots { get; } - protected override System.Threading.Tasks.Task DeleteAsync(Akka.Persistence.SnapshotMetadata metadata) { } - protected override System.Threading.Tasks.Task DeleteAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria) { } - protected override System.Threading.Tasks.Task LoadAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria) { } - protected override System.Threading.Tasks.Task SaveAsync(Akka.Persistence.SnapshotMetadata metadata, object snapshot) { } + protected override System.Threading.Tasks.Task DeleteAsync(Akka.Persistence.SnapshotMetadata metadata, System.Threading.CancellationToken cancellationToken) { } + protected override System.Threading.Tasks.Task DeleteAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken) { } + protected override System.Threading.Tasks.Task LoadAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken) { } + protected override System.Threading.Tasks.Task SaveAsync(Akka.Persistence.SnapshotMetadata metadata, object snapshot, System.Threading.CancellationToken cancellationToken) { } } public sealed class NoSnapshotStore : Akka.Persistence.Snapshot.SnapshotStore { public NoSnapshotStore() { } - protected override System.Threading.Tasks.Task DeleteAsync(Akka.Persistence.SnapshotMetadata metadata) { } - protected override System.Threading.Tasks.Task DeleteAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria) { } - protected override System.Threading.Tasks.Task LoadAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria) { } - protected override System.Threading.Tasks.Task SaveAsync(Akka.Persistence.SnapshotMetadata metadata, object snapshot) { } + protected override System.Threading.Tasks.Task DeleteAsync(Akka.Persistence.SnapshotMetadata metadata, System.Threading.CancellationToken cancellationToken) { } + protected override System.Threading.Tasks.Task DeleteAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken) { } + protected override System.Threading.Tasks.Task LoadAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken) { } + protected override System.Threading.Tasks.Task SaveAsync(Akka.Persistence.SnapshotMetadata metadata, object snapshot, System.Threading.CancellationToken cancellationToken) { } public class NoSnapshotStoreException : System.Exception { public NoSnapshotStoreException() { } @@ -1190,11 +1290,12 @@ namespace Akka.Persistence.Snapshot public abstract class SnapshotStore : Akka.Actor.ActorBase { protected SnapshotStore() { } - protected abstract System.Threading.Tasks.Task DeleteAsync(Akka.Persistence.SnapshotMetadata metadata); - protected abstract System.Threading.Tasks.Task DeleteAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria); - protected abstract System.Threading.Tasks.Task LoadAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria); + public virtual System.Threading.Tasks.Task CheckHealthAsync(System.Threading.CancellationToken cancellationToken = null) { } + protected abstract System.Threading.Tasks.Task DeleteAsync(Akka.Persistence.SnapshotMetadata metadata, System.Threading.CancellationToken cancellationToken); + protected abstract System.Threading.Tasks.Task DeleteAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken); + protected abstract System.Threading.Tasks.Task LoadAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken); protected virtual bool Receive(object message) { } protected virtual bool ReceivePluginInternal(object message) { } - protected abstract System.Threading.Tasks.Task SaveAsync(Akka.Persistence.SnapshotMetadata metadata, object snapshot); + protected abstract System.Threading.Tasks.Task SaveAsync(Akka.Persistence.SnapshotMetadata metadata, object snapshot, System.Threading.CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.DotNet.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.DotNet.verified.txt index c00feeb031f..06fd3dfef94 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.DotNet.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.DotNet.verified.txt @@ -1059,7 +1059,7 @@ namespace Akka.Persistence.Journal protected virtual System.Collections.Generic.Dictionary DeletedTo { get; } protected virtual System.Collections.Generic.List EventLog { get; } protected virtual System.Collections.Generic.Dictionary> EventsByPersistenceId { get; } - protected virtual System.Threading.ReaderWriterLockSlim Lock { get; } + protected virtual object Lock { get; } public System.Collections.Generic.IDictionary> Add(Akka.Persistence.IPersistentRepresentation persistent) { } public System.Collections.Generic.IDictionary> Delete(string pid, long seqNr) { } protected override System.Threading.Tasks.Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr, System.Threading.CancellationToken cancellationToken) { } @@ -1185,7 +1185,7 @@ namespace Akka.Persistence.Journal protected override System.Collections.Generic.Dictionary DeletedTo { get; } protected override System.Collections.Generic.List EventLog { get; } protected override System.Collections.Generic.Dictionary> EventsByPersistenceId { get; } - protected override System.Threading.ReaderWriterLockSlim Lock { get; } + protected override object Lock { get; } } public struct SingleEventSequence : Akka.Persistence.Journal.IEventSequence, System.IEquatable { diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Net.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Net.verified.txt index b6eb71055f4..c794e9c34d9 100644 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Net.verified.txt +++ b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Net.verified.txt @@ -1058,7 +1058,7 @@ namespace Akka.Persistence.Journal protected virtual System.Collections.Generic.Dictionary DeletedTo { get; } protected virtual System.Collections.Generic.List EventLog { get; } protected virtual System.Collections.Generic.Dictionary> EventsByPersistenceId { get; } - protected virtual System.Threading.ReaderWriterLockSlim Lock { get; } + protected virtual object Lock { get; } public System.Collections.Generic.IDictionary> Add(Akka.Persistence.IPersistentRepresentation persistent) { } public System.Collections.Generic.IDictionary> Delete(string pid, long seqNr) { } protected override System.Threading.Tasks.Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr, System.Threading.CancellationToken cancellationToken) { } @@ -1184,7 +1184,7 @@ namespace Akka.Persistence.Journal protected override System.Collections.Generic.Dictionary DeletedTo { get; } protected override System.Collections.Generic.List EventLog { get; } protected override System.Collections.Generic.Dictionary> EventsByPersistenceId { get; } - protected override System.Threading.ReaderWriterLockSlim Lock { get; } + protected override object Lock { get; } } public struct SingleEventSequence : Akka.Persistence.Journal.IEventSequence, System.IEquatable { diff --git a/src/core/Akka.Persistence/Journal/MemoryJournal.cs b/src/core/Akka.Persistence/Journal/MemoryJournal.cs index c56bec84c2d..577359df4a2 100644 --- a/src/core/Akka.Persistence/Journal/MemoryJournal.cs +++ b/src/core/Akka.Persistence/Journal/MemoryJournal.cs @@ -33,18 +33,17 @@ public class MemoryJournal : AsyncWriteJournal /// private readonly Dictionary> _eventsByPersistenceId = new(); - private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion); + private readonly object _lock = new(); private readonly Dictionary _deletedTo = new(); protected virtual List EventLog => _eventLog; protected virtual Dictionary> EventsByPersistenceId => _eventsByPersistenceId; - protected virtual ReaderWriterLockSlim Lock => _lock; + protected virtual object Lock => _lock; protected virtual Dictionary DeletedTo => _deletedTo; protected override Task> WriteMessagesAsync(IEnumerable messages, CancellationToken cancellationToken) { - Lock.EnterWriteLock(); - try + lock (Lock) { foreach (var w in messages) { @@ -64,18 +63,13 @@ protected override Task> WriteMessagesAsync(IEnumerabl } } } - finally - { - Lock.ExitWriteLock(); - } return Task.FromResult>(null); } public override Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr, CancellationToken cancellationToken) { - Lock.EnterReadLock(); - try + lock (Lock) { // Use index for O(1) lookup instead of O(n) scan if (!EventsByPersistenceId.TryGetValue(persistenceId, out var events) || events.Count == 0) @@ -87,10 +81,6 @@ public override Task ReadHighestSequenceNrAsync(string persistenceId, long // Deletion is logical only - events remain in index return Task.FromResult(highest); } - finally - { - Lock.ExitReadLock(); - } } public override Task ReplayMessagesAsync(IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, @@ -98,8 +88,7 @@ public override Task ReplayMessagesAsync(IActorContext context, string persisten { IPersistentRepresentation[] messages; - Lock.EnterReadLock(); - try + lock (Lock) { // Use index for O(events_for_entity) instead of O(total_events) if (!EventsByPersistenceId.TryGetValue(persistenceId, out var pidEvents)) @@ -118,10 +107,6 @@ public override Task ReplayMessagesAsync(IActorContext context, string persisten .ToArray(); } } - finally - { - Lock.ExitReadLock(); - } // Execute callbacks outside the lock to avoid potential deadlocks foreach (var message in messages) @@ -134,17 +119,12 @@ public override Task ReplayMessagesAsync(IActorContext context, string persisten protected override Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr, CancellationToken cancellationToken) { - Lock.EnterWriteLock(); - try + lock (Lock) { // Track deletion marker instead of actually removing events // This is simpler and matches the semantics (logical deletion) DeletedTo[persistenceId] = toSequenceNr; } - finally - { - Lock.ExitWriteLock(); - } return Task.CompletedTask; } @@ -154,8 +134,7 @@ protected override Task DeleteMessagesToAsync(string persistenceId, long toSeque /// public IDictionary> Add(IPersistentRepresentation persistent) { - Lock.EnterWriteLock(); - try + lock (Lock) { var timestamped = persistent.WithTimestamp(DateTime.UtcNow.Ticks); @@ -168,24 +147,12 @@ public IDictionary> Add(IPersisten EventsByPersistenceId[timestamped.PersistenceId] = pidEvents; } pidEvents.Add(timestamped); - } - finally - { - Lock.ExitWriteLock(); - } - // Return view of all messages as LinkedList per persistence ID for API compatibility - Lock.EnterReadLock(); - try - { + // Return view of all messages as LinkedList per persistence ID for API compatibility return EventsByPersistenceId.ToDictionary( kvp => kvp.Key, kvp => new LinkedList(kvp.Value)); } - finally - { - Lock.ExitReadLock(); - } } /// @@ -194,31 +161,18 @@ public IDictionary> Add(IPersisten /// public IDictionary> Delete(string pid, long seqNr) { - Lock.EnterWriteLock(); - try + lock (Lock) { var currentDeleted = DeletedTo.GetValueOrDefault(pid, 0L); DeletedTo[pid] = Math.Max(currentDeleted, seqNr); - } - finally - { - Lock.ExitWriteLock(); - } - // Return view of non-deleted messages as LinkedList per persistence ID for API compatibility - // Use index instead of scanning entire event log - Lock.EnterReadLock(); - try - { + // Return view of non-deleted messages as LinkedList per persistence ID for API compatibility + // Use index instead of scanning entire event log return EventsByPersistenceId.ToDictionary( kvp => kvp.Key, kvp => new LinkedList( kvp.Value.Where(e => e.SequenceNr > DeletedTo.GetValueOrDefault(kvp.Key, 0L)))); } - finally - { - Lock.ExitReadLock(); - } } /// @@ -226,8 +180,7 @@ public IDictionary> Delete(string /// public IEnumerable Read(string pid, long from, long to, long max) { - Lock.EnterReadLock(); - try + lock (Lock) { // Use index for O(events_for_entity) instead of O(total_events) if (!EventsByPersistenceId.TryGetValue(pid, out var pidEvents)) @@ -242,10 +195,6 @@ public IEnumerable Read(string pid, long from, long t .Take(max > int.MaxValue ? int.MaxValue : (int)max) .ToArray(); // Materialize under lock } - finally - { - Lock.ExitReadLock(); - } } /// @@ -253,8 +202,7 @@ public IEnumerable Read(string pid, long from, long t /// public long HighestSequenceNr(string pid) { - Lock.EnterReadLock(); - try + lock (Lock) { // Use index for O(1) lookup instead of O(n) scan if (!EventsByPersistenceId.TryGetValue(pid, out var events) || events.Count == 0) @@ -264,10 +212,6 @@ public long HighestSequenceNr(string pid) // Deletion is logical only - events remain in index return events[events.Count - 1].SequenceNr; } - finally - { - Lock.ExitReadLock(); - } } protected override bool ReceivePluginInternal(object message) @@ -300,16 +244,11 @@ protected override bool ReceivePluginInternal(object message) HashSet ids; int count; - Lock.EnterReadLock(); - try + lock (Lock) { ids = new HashSet(EventLog.Skip(offset).Select(p => p.PersistenceId)); count = EventLog.Count; } - finally - { - Lock.ExitReadLock(); - } return Task.FromResult<(IEnumerable Ids, int LastOrdering)>((ids, count)); } @@ -322,8 +261,7 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) IPersistentRepresentation[] snapshot; int count; - Lock.EnterReadLock(); - try + lock (Lock) { // Scan for events with matching tag snapshot = EventLog @@ -334,10 +272,6 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) count = EventLog.Count(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)); } - finally - { - Lock.ExitReadLock(); - } // Send messages outside the lock to avoid potential deadlocks var index = 0; @@ -355,8 +289,7 @@ private Task ReplayAllEventsAsync(ReplayAllEvents replay) IPersistentRepresentation[] snapshot; int count; - Lock.EnterReadLock(); - try + lock (Lock) { snapshot = EventLog .Skip(replay.FromOffset) @@ -365,10 +298,6 @@ private Task ReplayAllEventsAsync(ReplayAllEvents replay) count = EventLog.Count; } - finally - { - Lock.ExitReadLock(); - } // Send messages outside the lock to avoid potential deadlocks var index = 0; @@ -626,12 +555,12 @@ public class SharedMemoryJournal : MemoryJournal { private static readonly List SharedEventLog = new(); private static readonly Dictionary> SharedEventsByPersistenceId = new(); - private static readonly ReaderWriterLockSlim SharedLock = new(LockRecursionPolicy.NoRecursion); + private static readonly object SharedLock = new(); private static readonly Dictionary SharedDeletedTo = new(); protected override List EventLog => SharedEventLog; protected override Dictionary> EventsByPersistenceId => SharedEventsByPersistenceId; - protected override ReaderWriterLockSlim Lock => SharedLock; + protected override object Lock => SharedLock; protected override Dictionary DeletedTo => SharedDeletedTo; } } From efb2113694ebebdb0871fd21f0982ea32ddf69f8 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Wed, 8 Oct 2025 17:13:24 -0500 Subject: [PATCH 02/12] Delete src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Core.verified.txt --- ...ISpec.ApprovePersistence.Core.verified.txt | 1301 ----------------- 1 file changed, 1301 deletions(-) delete mode 100644 src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Core.verified.txt diff --git a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Core.verified.txt b/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Core.verified.txt deleted file mode 100644 index 06fd3dfef94..00000000000 --- a/src/core/Akka.API.Tests/verify/CoreAPISpec.ApprovePersistence.Core.verified.txt +++ /dev/null @@ -1,1301 +0,0 @@ -[assembly: System.Reflection.AssemblyMetadataAttribute("RepositoryUrl", "https://github.com/akkadotnet/akka.net")] -[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Sharding")] -[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Persistence.TCK")] -[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Persistence.Tests")] -[assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)] -[assembly: System.Runtime.InteropServices.GuidAttribute("e3bcba88-003c-4cda-8a60-f0c2553fe3c8")] -[assembly: System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName=".NET 6.0")] -namespace Akka.Persistence -{ - public sealed class AsyncHandlerInvocation : Akka.Persistence.IPendingHandlerInvocation - { - public AsyncHandlerInvocation(object evt, System.Action handler) { } - public object Event { get; } - public System.Action Handler { get; } - } - public abstract class AtLeastOnceDeliveryActor : Akka.Persistence.PersistentActor - { - protected AtLeastOnceDeliveryActor() { } - protected AtLeastOnceDeliveryActor(Akka.Persistence.PersistenceSettings.AtLeastOnceDeliverySettings settings) { } - protected AtLeastOnceDeliveryActor(System.Func overrideSettings) { } - public int MaxUnconfirmedMessages { get; } - public System.TimeSpan RedeliverInterval { get; } - public int RedeliveryBurstLimit { get; } - public int UnconfirmedCount { get; } - public int WarnAfterNumberOfUnconfirmedAttempts { get; } - public override void AroundPostStop() { } - public override void AroundPreRestart(System.Exception cause, object message) { } - protected override bool AroundReceive(Akka.Actor.Receive receive, object message) { } - public bool ConfirmDelivery(long deliveryId) { } - public void Deliver(Akka.Actor.ActorPath destination, System.Func deliveryMessageMapper) { } - public void Deliver(Akka.Actor.ActorSelection destination, System.Func deliveryMessageMapper) { } - public Akka.Persistence.AtLeastOnceDeliverySnapshot GetDeliverySnapshot() { } - protected override void OnReplaySuccess() { } - public void SetDeliverySnapshot(Akka.Persistence.AtLeastOnceDeliverySnapshot snapshot) { } - } - public abstract class AtLeastOnceDeliveryReceiveActor : Akka.Persistence.ReceivePersistentActor - { - protected AtLeastOnceDeliveryReceiveActor() { } - protected AtLeastOnceDeliveryReceiveActor(Akka.Persistence.PersistenceSettings.AtLeastOnceDeliverySettings settings) { } - protected AtLeastOnceDeliveryReceiveActor(System.Func overrideSettings) { } - public int MaxUnconfirmedMessages { get; } - public System.TimeSpan RedeliverInterval { get; } - public int RedeliveryBurstLimit { get; } - public int UnconfirmedCount { get; } - public int WarnAfterNumberOfUnconfirmedAttempts { get; } - public override void AroundPostStop() { } - public override void AroundPreRestart(System.Exception cause, object message) { } - protected override bool AroundReceive(Akka.Actor.Receive receive, object message) { } - public bool ConfirmDelivery(long deliveryId) { } - public void Deliver(Akka.Actor.ActorPath destination, System.Func deliveryMessageMapper) { } - public void Deliver(Akka.Actor.ActorSelection destination, System.Func deliveryMessageMapper) { } - public Akka.Persistence.AtLeastOnceDeliverySnapshot GetDeliverySnapshot() { } - protected override void OnReplaySuccess() { } - public void SetDeliverySnapshot(Akka.Persistence.AtLeastOnceDeliverySnapshot snapshot) { } - } - public class AtLeastOnceDeliverySemantic - { - public AtLeastOnceDeliverySemantic(Akka.Actor.IActorContext context, Akka.Persistence.PersistenceSettings.AtLeastOnceDeliverySettings settings) { } - public int MaxUnconfirmedMessages { get; } - public System.TimeSpan RedeliverInterval { get; } - public int RedeliveryBurstLimit { get; } - public int UnconfirmedCount { get; } - public int WarnAfterNumberOfUnconfirmedAttempts { get; } - public bool AroundReceive(Akka.Actor.Receive receive, object message) { } - public void Cancel() { } - public bool ConfirmDelivery(long deliveryId) { } - public void Deliver(Akka.Actor.ActorPath destination, System.Func deliveryMessageMapper, bool isRecovering) { } - public Akka.Persistence.AtLeastOnceDeliverySnapshot GetDeliverySnapshot() { } - public void OnReplaySuccess() { } - public void SetDeliverySnapshot(Akka.Persistence.AtLeastOnceDeliverySnapshot snapshot) { } - public sealed class Delivery : System.IEquatable - { - public Delivery(Akka.Actor.ActorPath destination, object message, System.DateTime timestamp, int attempt) { } - public int Attempt { get; } - public Akka.Actor.ActorPath Destination { get; } - public object Message { get; } - public System.DateTime Timestamp { get; } - public bool Equals(Akka.Persistence.AtLeastOnceDeliverySemantic.Delivery other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public Akka.Persistence.AtLeastOnceDeliverySemantic.Delivery IncrementedCopy() { } - public override string ToString() { } - } - public sealed class RedeliveryTick : Akka.Actor.INotInfluenceReceiveTimeout, Akka.Event.IDeadLetterSuppression - { - public static Akka.Persistence.AtLeastOnceDeliverySemantic.RedeliveryTick Instance { get; } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - } - } - public sealed class AtLeastOnceDeliverySnapshot : Akka.Persistence.Serialization.IMessage, System.IEquatable - { - public AtLeastOnceDeliverySnapshot(long currentDeliveryId, Akka.Persistence.UnconfirmedDelivery[] unconfirmedDeliveries) { } - public long CurrentDeliveryId { get; } - public Akka.Persistence.UnconfirmedDelivery[] UnconfirmedDeliveries { get; } - public bool Equals(Akka.Persistence.AtLeastOnceDeliverySnapshot other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class AtomicWrite : Akka.Persistence.IPersistentEnvelope, Akka.Persistence.Serialization.IMessage - { - public AtomicWrite(Akka.Persistence.IPersistentRepresentation @event) { } - public AtomicWrite(System.Collections.Immutable.IImmutableList payload) { } - public long HighestSequenceNr { get; } - public long LowestSequenceNr { get; } - public object Payload { get; } - public string PersistenceId { get; } - public Akka.Actor.IActorRef Sender { get; } - public int Size { get; } - public bool Equals(Akka.Persistence.AtomicWrite other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class CheckJournalHealth : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalRequest, Akka.Persistence.IPersistenceMessage - { - public CheckJournalHealth(System.Threading.CancellationToken cancellationToken) { } - public System.Threading.CancellationToken CancellationToken { get; } - public override string ToString() { } - } - public sealed class CheckSnapshotStoreHealth : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotRequest - { - public CheckSnapshotStoreHealth(System.Threading.CancellationToken cancellationToken) { } - public System.Threading.CancellationToken CancellationToken { get; } - public override string ToString() { } - } - public sealed class DeleteMessagesFailure : Akka.Actor.INoSerializationVerificationNeeded, System.IEquatable - { - public DeleteMessagesFailure(System.Exception cause, long toSequenceNr) { } - public System.Exception Cause { get; } - public long ToSequenceNr { get; } - public bool Equals(Akka.Persistence.DeleteMessagesFailure other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class DeleteMessagesSuccess : System.IEquatable - { - public DeleteMessagesSuccess(long toSequenceNr) { } - public long ToSequenceNr { get; } - public bool Equals(Akka.Persistence.DeleteMessagesSuccess other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class DeleteMessagesTo : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalRequest, Akka.Persistence.IPersistenceMessage, System.IEquatable - { - public DeleteMessagesTo(string persistenceId, long toSequenceNr, Akka.Actor.IActorRef persistentActor) { } - public string PersistenceId { get; } - public Akka.Actor.IActorRef PersistentActor { get; } - public long ToSequenceNr { get; } - public bool Equals(Akka.Persistence.DeleteMessagesTo other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class DeleteSnapshot : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotRequest, System.IEquatable - { - public DeleteSnapshot(Akka.Persistence.SnapshotMetadata metadata) { } - public Akka.Persistence.SnapshotMetadata Metadata { get; } - public bool Equals(Akka.Persistence.DeleteSnapshot other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class DeleteSnapshotFailure : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotResponse, System.IEquatable - { - public DeleteSnapshotFailure(Akka.Persistence.SnapshotMetadata metadata, System.Exception cause) { } - public System.Exception Cause { get; } - public Akka.Persistence.SnapshotMetadata Metadata { get; } - public bool Equals(Akka.Persistence.DeleteSnapshotFailure other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class DeleteSnapshotSuccess : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotResponse, System.IEquatable - { - public DeleteSnapshotSuccess(Akka.Persistence.SnapshotMetadata metadata) { } - public Akka.Persistence.SnapshotMetadata Metadata { get; } - public bool Equals(Akka.Persistence.DeleteSnapshotSuccess other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class DeleteSnapshots : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotRequest, System.IEquatable - { - public DeleteSnapshots(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria) { } - public Akka.Persistence.SnapshotSelectionCriteria Criteria { get; } - public string PersistenceId { get; } - public bool Equals(Akka.Persistence.DeleteSnapshots other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class DeleteSnapshotsFailure : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotResponse, System.IEquatable - { - public DeleteSnapshotsFailure(Akka.Persistence.SnapshotSelectionCriteria criteria, System.Exception cause) { } - public System.Exception Cause { get; } - public Akka.Persistence.SnapshotSelectionCriteria Criteria { get; } - public bool Equals(Akka.Persistence.DeleteSnapshotsFailure other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class DeleteSnapshotsSuccess : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotResponse, System.IEquatable - { - public DeleteSnapshotsSuccess(Akka.Persistence.SnapshotSelectionCriteria criteria) { } - public Akka.Persistence.SnapshotSelectionCriteria Criteria { get; } - public bool Equals(Akka.Persistence.DeleteSnapshotsSuccess other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class DiscardConfigurator : Akka.Persistence.IStashOverflowStrategyConfigurator - { - public DiscardConfigurator() { } - public Akka.Persistence.IStashOverflowStrategy Create(Akka.Configuration.Config config) { } - } - [System.Runtime.CompilerServices.NullableAttribute(0)] - public class DiscardToDeadLetterStrategy : Akka.Persistence.IStashOverflowStrategy - { - public static Akka.Persistence.DiscardToDeadLetterStrategy Instance { get; } - } - public abstract class Eventsourced : Akka.Actor.ActorBase, Akka.Actor.IActorStash, Akka.Actor.IWithUnboundedStash, Akka.Actor.IWithUnrestrictedStash, Akka.Dispatch.IRequiresMessageQueue, Akka.Persistence.IPersistenceRecovery, Akka.Persistence.IPersistenceStash, Akka.Persistence.IPersistentIdentity - { - public static readonly System.Func UnstashFilterPredicate; - protected Eventsourced() { } - protected Akka.Persistence.PersistenceExtension Extension { get; } - public virtual Akka.Persistence.IStashOverflowStrategy InternalStashOverflowStrategy { get; } - public bool IsRecovering { get; } - public bool IsRecoveryFinished { get; } - public Akka.Actor.IActorRef Journal { get; } - public string JournalPluginId { get; set; } - public long LastSequenceNr { get; } - protected virtual Akka.Event.ILoggingAdapter Log { get; } - public abstract string PersistenceId { get; } - public virtual Akka.Persistence.Recovery Recovery { get; } - public string SnapshotPluginId { get; set; } - public long SnapshotSequenceNr { get; } - public Akka.Actor.IActorRef SnapshotStore { get; } - public string SnapshotterId { get; } - public Akka.Actor.IStash Stash { get; set; } - public override void AroundPostRestart(System.Exception reason, object message) { } - public override void AroundPostStop() { } - public override void AroundPreRestart(System.Exception cause, object message) { } - public override void AroundPreStart() { } - protected override bool AroundReceive(Akka.Actor.Receive receive, object message) { } - public void DeferAsync(TEvent evt, System.Action handler) { } - public void DeleteMessages(long toSequenceNr) { } - public void DeleteSnapshot(long sequenceNr) { } - public void DeleteSnapshots(Akka.Persistence.SnapshotSelectionCriteria criteria) { } - public void LoadSnapshot(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, long toSequenceNr) { } - protected virtual void OnPersistFailure(System.Exception cause, object @event, long sequenceNr) { } - protected virtual void OnPersistRejected(System.Exception cause, object @event, long sequenceNr) { } - protected virtual void OnRecoveryFailure(System.Exception reason, object message = null) { } - protected virtual void OnReplaySuccess() { } - public void Persist(TEvent @event, System.Action handler) { } - public void PersistAll(System.Collections.Generic.IEnumerable events, System.Action handler) { } - public void PersistAllAsync(System.Collections.Generic.IEnumerable events, System.Action handler) { } - public void PersistAsync(TEvent @event, System.Action handler) { } - protected abstract bool ReceiveCommand(object message); - protected abstract bool ReceiveRecover(object message); - protected void RunTask(System.Func action) { } - public void SaveSnapshot(object snapshot) { } - protected override void Unhandled(object message) { } - } - public interface IJournalMessage : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage { } - public interface IJournalPlugin - { - Akka.Configuration.Config DefaultConfig { get; } - string JournalPath { get; } - } - public interface IJournalRequest : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IPersistenceMessage { } - public interface IJournalResponse : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IPersistenceMessage { } - public interface IPendingHandlerInvocation - { - object Event { get; } - System.Action Handler { get; } - } - public interface IPersistenceMessage : Akka.Actor.INoSerializationVerificationNeeded { } - public interface IPersistenceRecovery - { - Akka.Persistence.Recovery Recovery { get; } - } - public interface IPersistenceStash : Akka.Actor.IActorStash, Akka.Actor.IWithUnboundedStash, Akka.Actor.IWithUnrestrictedStash, Akka.Dispatch.IRequiresMessageQueue - { - Akka.Persistence.IStashOverflowStrategy InternalStashOverflowStrategy { get; } - } - public interface IPersistentEnvelope - { - object Payload { get; } - Akka.Actor.IActorRef Sender { get; } - int Size { get; } - } - public interface IPersistentIdentity - { - string JournalPluginId { get; } - string PersistenceId { get; } - string SnapshotPluginId { get; } - } - public interface IPersistentRepresentation : Akka.Persistence.Serialization.IMessage - { - bool IsDeleted { get; } - string Manifest { get; } - object Payload { get; } - string PersistenceId { get; } - Akka.Actor.IActorRef Sender { get; } - long SequenceNr { get; } - long Timestamp { get; } - string WriterGuid { get; } - Akka.Persistence.IPersistentRepresentation Update(long sequenceNr, string persistenceId, bool isDeleted, Akka.Actor.IActorRef sender, string writerGuid); - Akka.Persistence.IPersistentRepresentation WithManifest(string manifest); - Akka.Persistence.IPersistentRepresentation WithPayload(object payload); - Akka.Persistence.IPersistentRepresentation WithTimestamp(long timestamp); - } - public interface ISnapshotMessage : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage { } - public interface ISnapshotRequest : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage { } - public interface ISnapshotResponse : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage { } - public interface ISnapshotter - { - long SnapshotSequenceNr { get; } - string SnapshotterId { get; } - void DeleteSnapshot(long sequenceNr); - void DeleteSnapshots(Akka.Persistence.SnapshotSelectionCriteria criteria); - void LoadSnapshot(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, long toSequenceNr); - void SaveSnapshot(object snapshot); - } - public interface IStashOverflowStrategy { } - public interface IStashOverflowStrategyConfigurator - { - Akka.Persistence.IStashOverflowStrategy Create(Akka.Configuration.Config config); - } - public sealed class JournalHealthCheckResponse : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalResponse, Akka.Persistence.IPersistenceMessage - { - public JournalHealthCheckResponse(Akka.Persistence.PersistenceHealthCheckResult result) { } - public Akka.Persistence.PersistenceHealthCheckResult Result { get; } - public override string ToString() { } - } - public sealed class LoadSnapshot : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotRequest, System.IEquatable - { - public LoadSnapshot(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, long toSequenceNr) { } - public Akka.Persistence.SnapshotSelectionCriteria Criteria { get; } - public string PersistenceId { get; } - public long ToSequenceNr { get; } - public bool Equals(Akka.Persistence.LoadSnapshot other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class LoadSnapshotFailed : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotResponse - { - public LoadSnapshotFailed(System.Exception cause) { } - public System.Exception Cause { get; } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class LoadSnapshotResult : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotResponse, System.IEquatable - { - public LoadSnapshotResult(Akka.Persistence.SelectedSnapshot snapshot, long toSequenceNr) { } - public Akka.Persistence.SelectedSnapshot Snapshot { get; } - public long ToSequenceNr { get; } - public bool Equals(Akka.Persistence.LoadSnapshotResult other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class LoopMessageSuccess : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalResponse, Akka.Persistence.IPersistenceMessage, System.IEquatable - { - public LoopMessageSuccess(object message, int actorInstanceId) { } - public int ActorInstanceId { get; } - public object Message { get; } - public bool Equals(Akka.Persistence.LoopMessageSuccess other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public class MaxUnconfirmedMessagesExceededException : System.Exception - { - public MaxUnconfirmedMessagesExceededException() { } - public MaxUnconfirmedMessagesExceededException(string message) { } - public MaxUnconfirmedMessagesExceededException(string message, System.Exception innerException) { } - protected MaxUnconfirmedMessagesExceededException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - } - public class Persistence : Akka.Actor.ExtensionIdProvider - { - public Persistence() { } - public static Akka.Persistence.Persistence Instance { get; } - public override Akka.Persistence.PersistenceExtension CreateExtension(Akka.Actor.ExtendedActorSystem system) { } - public static Akka.Configuration.Config DefaultConfig() { } - } - public class PersistenceExtension : Akka.Actor.IExtension - { - public PersistenceExtension(Akka.Actor.ExtendedActorSystem system) { } - public Akka.Persistence.IStashOverflowStrategy DefaultInternalStashOverflowStrategy { get; } - public Akka.Persistence.PersistenceSettings Settings { get; } - public Akka.Persistence.Journal.EventAdapters AdaptersFor(string journalPluginId) { } - public System.Threading.Tasks.Task CheckJournalHealthAsync(string journalPluginId, System.Threading.CancellationToken cancellationToken = null) { } - public System.Threading.Tasks.Task CheckSnapshotStoreHealthAsync(string snapshotStorePluginId, System.Threading.CancellationToken cancellationToken = null) { } - [Akka.Annotations.InternalStableApiAttribute()] - public Akka.Actor.IActorRef JournalFor(string journalPluginId) { } - public string PersistenceId(Akka.Actor.IActorRef actor) { } - [Akka.Annotations.InternalStableApiAttribute()] - public Akka.Actor.IActorRef SnapshotStoreFor(string snapshotPluginId) { } - } - [System.Runtime.CompilerServices.IsReadOnlyAttribute()] - [System.Runtime.CompilerServices.NullableAttribute(0)] - public struct PersistenceHealthCheckResult : System.IEquatable - { - public PersistenceHealthCheckResult(Akka.Persistence.PersistenceHealthStatus Status, string Description = null, System.Exception Exception = null, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { - 2, - 0, - 0})] System.Collections.Generic.IReadOnlyDictionary Data = null) { } - [System.Runtime.CompilerServices.NullableAttribute(new byte[] { - 2, - 0, - 0})] - public System.Collections.Generic.IReadOnlyDictionary Data { get; set; } - public string Description { get; set; } - public System.Exception Exception { get; set; } - public Akka.Persistence.PersistenceHealthStatus Status { get; set; } - } - public enum PersistenceHealthStatus - { - Healthy = 0, - Degraded = 1, - Unhealthy = 2, - } - public sealed class PersistenceSettings : Akka.Actor.Settings - { - public PersistenceSettings(Akka.Actor.ActorSystem system, Akka.Configuration.Config config) { } - public Akka.Persistence.PersistenceSettings.AtLeastOnceDeliverySettings AtLeastOnceDelivery { get; set; } - public Akka.Persistence.PersistenceSettings.InternalSettings Internal { get; } - public Akka.Persistence.PersistenceSettings.ViewSettings View { get; } - public sealed class AtLeastOnceDeliverySettings - { - public AtLeastOnceDeliverySettings(System.TimeSpan redeliverInterval, int redeliveryBurstLimit, int warnAfterNumberOfUnconfirmedAttempts, int maxUnconfirmedMessages) { } - public AtLeastOnceDeliverySettings(Akka.Configuration.Config config) { } - public int MaxUnconfirmedMessages { get; } - public System.TimeSpan RedeliverInterval { get; } - public int RedeliveryBurstLimit { get; } - public int WarnAfterNumberOfUnconfirmedAttempts { get; } - public Akka.Persistence.PersistenceSettings.AtLeastOnceDeliverySettings WithMaxUnconfirmedMessages(int maxUnconfirmedMessages) { } - public Akka.Persistence.PersistenceSettings.AtLeastOnceDeliverySettings WithRedeliverInterval(System.TimeSpan redeliverInterval) { } - public Akka.Persistence.PersistenceSettings.AtLeastOnceDeliverySettings WithRedeliveryBurstLimit(int redeliveryBurstLimit) { } - public Akka.Persistence.PersistenceSettings.AtLeastOnceDeliverySettings WithUnconfirmedAttemptsToWarn(int unconfirmedAttemptsToWarn) { } - } - public sealed class InternalSettings - { - public InternalSettings(Akka.Configuration.Config config) { } - public bool PublishConfirmations { get; } - public bool PublishPluginCommands { get; } - } - public sealed class ViewSettings - { - public ViewSettings(Akka.Configuration.Config config) { } - public bool AutoUpdate { get; } - public System.TimeSpan AutoUpdateInterval { get; } - public long AutoUpdateReplayMax { get; } - } - } - [Akka.Annotations.InternalApiAttribute()] - public class Persistent : Akka.Persistence.IPersistentRepresentation, Akka.Persistence.Serialization.IMessage, System.IEquatable - { - public Persistent(object payload, long sequenceNr = 0, string persistenceId = null, string manifest = null, bool isDeleted = False, Akka.Actor.IActorRef sender = null, string writerGuid = null, long timestamp = 0) { } - public bool IsDeleted { get; } - public string Manifest { get; } - public object Payload { get; } - public string PersistenceId { get; } - public Akka.Actor.IActorRef Sender { get; } - public long SequenceNr { get; } - public long Timestamp { get; } - public static string Undefined { get; } - public string WriterGuid { get; } - public bool Equals(Akka.Persistence.IPersistentRepresentation other) { } - public override bool Equals(object obj) { } - public bool Equals(Akka.Persistence.Persistent other) { } - public override int GetHashCode() { } - public override string ToString() { } - public Akka.Persistence.IPersistentRepresentation Update(long sequenceNr, string persistenceId, bool isDeleted, Akka.Actor.IActorRef sender, string writerGuid) { } - public Akka.Persistence.IPersistentRepresentation WithManifest(string manifest) { } - public Akka.Persistence.IPersistentRepresentation WithPayload(object payload) { } - public Akka.Persistence.IPersistentRepresentation WithTimestamp(long newTimestamp) { } - } - public abstract class PersistentActor : Akka.Persistence.Eventsourced - { - protected PersistentActor() { } - protected override bool Receive(object message) { } - } - [System.Runtime.CompilerServices.NullableAttribute(0)] - public abstract class ReceivePersistentActor : Akka.Persistence.UntypedPersistentActor, Akka.Actor.Internal.IInitializableActor - { - protected ReceivePersistentActor() { } - protected void Become(System.Action configure) { } - protected void BecomeStacked(System.Action configure) { } - protected void Command<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Action handler, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { - 2, - 1})] System.Predicate shouldHandle = null) { } - protected void Command<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Predicate shouldHandle, System.Action handler) { } - protected void Command(System.Type messageType, System.Action handler, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { - 2, - 1})] System.Predicate shouldHandle = null) { } - protected void Command(System.Type messageType, System.Predicate shouldHandle, System.Action handler) { } - protected void Command<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Func handler) { } - protected void Command(System.Type messageType, System.Func handler) { } - protected void Command(System.Action handler) { } - protected void CommandAny(System.Action handler) { } - protected void CommandAnyAsync(System.Func handler) { } - protected void CommandAsync<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Func handler, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { - 2, - 1})] System.Predicate shouldHandle = null) { } - protected void CommandAsync<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Predicate shouldHandle, System.Func handler) { } - protected void CommandAsync(System.Type messageType, System.Func handler, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { - 2, - 1})] System.Predicate shouldHandle = null) { } - protected void CommandAsync(System.Type messageType, System.Predicate shouldHandle, System.Func handler) { } - protected virtual void OnCommand(object message) { } - protected virtual void OnRecover(object message) { } - protected void Recover<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Action handler, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { - 2, - 1})] System.Predicate shouldHandle = null) { } - protected void Recover<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Predicate shouldHandle, System.Action handler) { } - protected void Recover(System.Type messageType, System.Action handler, [System.Runtime.CompilerServices.NullableAttribute(new byte[] { - 2, - 1})] System.Predicate shouldHandle = null) { } - protected void Recover(System.Type messageType, System.Predicate shouldHandle, System.Action handler) { } - protected void Recover<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Func handler) { } - protected void Recover(System.Type messageType, System.Func handler) { } - protected void RecoverAny(System.Action handler) { } - } - [System.Runtime.CompilerServices.NullableAttribute(0)] - public sealed class Recovery - { - public Recovery() { } - public Recovery(Akka.Persistence.SnapshotSelectionCriteria fromSnapshot) { } - public Recovery(Akka.Persistence.SnapshotSelectionCriteria fromSnapshot, long toSequenceNr) { } - public Recovery(Akka.Persistence.SnapshotSelectionCriteria fromSnapshot = null, long toSequenceNr = 9223372036854775807, long replayMax = 9223372036854775807) { } - public static Akka.Persistence.Recovery Default { get; } - public Akka.Persistence.SnapshotSelectionCriteria FromSnapshot { get; } - public static Akka.Persistence.Recovery None { get; } - public long ReplayMax { get; } - public long ToSequenceNr { get; } - } - public sealed class RecoveryCompleted - { - [System.Runtime.CompilerServices.NullableAttribute(1)] - public static readonly Akka.Persistence.RecoveryCompleted Instance; - public override bool Equals(object obj) { } - public override int GetHashCode() { } - } - public sealed class RecoverySuccess : Akka.Actor.INoSerializationVerificationNeeded, Akka.Event.IDeadLetterSuppression, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalResponse, Akka.Persistence.IPersistenceMessage, System.IEquatable - { - public RecoverySuccess(long highestSequenceNr) { } - public long HighestSequenceNr { get; } - public bool Equals(Akka.Persistence.RecoverySuccess other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class RecoveryTick - { - public RecoveryTick(bool snapshot) { } - public bool Snapshot { get; } - } - [System.Runtime.CompilerServices.NullableAttribute(0)] - public sealed class RecoveryTimedOutException : Akka.Actor.AkkaException - { - public RecoveryTimedOutException() { } - public RecoveryTimedOutException(string message, [System.Runtime.CompilerServices.NullableAttribute(2)] System.Exception cause = null) { } - public RecoveryTimedOutException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - } - public sealed class ReplayMessages : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalRequest, Akka.Persistence.IPersistenceMessage, System.IEquatable - { - public ReplayMessages(long fromSequenceNr, long toSequenceNr, long max, string persistenceId, Akka.Actor.IActorRef persistentActor) { } - public long FromSequenceNr { get; } - public long Max { get; } - public string PersistenceId { get; } - public Akka.Actor.IActorRef PersistentActor { get; } - public long ToSequenceNr { get; } - public bool Equals(Akka.Persistence.ReplayMessages other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class ReplayMessagesFailure : Akka.Actor.INoSerializationVerificationNeeded, Akka.Event.IDeadLetterSuppression, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalResponse, Akka.Persistence.IPersistenceMessage, System.IEquatable - { - public ReplayMessagesFailure(System.Exception cause) { } - public System.Exception Cause { get; } - public bool Equals(Akka.Persistence.ReplayMessagesFailure other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class ReplayedMessage : Akka.Actor.INoSerializationVerificationNeeded, Akka.Event.IDeadLetterSuppression, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalResponse, Akka.Persistence.IPersistenceMessage, System.IEquatable - { - public ReplayedMessage(Akka.Persistence.IPersistentRepresentation persistent) { } - public Akka.Persistence.IPersistentRepresentation Persistent { get; } - public bool Equals(Akka.Persistence.ReplayedMessage other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - [System.Runtime.CompilerServices.NullableAttribute(0)] - public sealed class ReplyToStrategy : Akka.Persistence.IStashOverflowStrategy - { - public ReplyToStrategy(object response) { } - public object Response { get; } - } - public sealed class SaveSnapshot : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotRequest, System.IEquatable - { - public SaveSnapshot(Akka.Persistence.SnapshotMetadata metadata, object snapshot) { } - public Akka.Persistence.SnapshotMetadata Metadata { get; } - public object Snapshot { get; } - public bool Equals(Akka.Persistence.SaveSnapshot other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class SaveSnapshotFailure : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotResponse, System.IEquatable - { - public SaveSnapshotFailure(Akka.Persistence.SnapshotMetadata metadata, System.Exception cause) { } - public System.Exception Cause { get; } - public Akka.Persistence.SnapshotMetadata Metadata { get; } - public bool Equals(Akka.Persistence.SaveSnapshotFailure other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class SaveSnapshotSuccess : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotResponse, System.IEquatable - { - public SaveSnapshotSuccess(Akka.Persistence.SnapshotMetadata metadata) { } - public Akka.Persistence.SnapshotMetadata Metadata { get; } - public bool Equals(Akka.Persistence.SaveSnapshotSuccess other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class SelectedSnapshot : System.IEquatable - { - public SelectedSnapshot(Akka.Persistence.SnapshotMetadata metadata, object snapshot) { } - public Akka.Persistence.SnapshotMetadata Metadata { get; } - public object Snapshot { get; } - public bool Equals(Akka.Persistence.SelectedSnapshot other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class SnapshotMetadata : System.IEquatable - { - [System.ObsoleteAttribute("This constructor is deprecated and will be removed in v1.6. Use the constructor w" + - "ith the timestamp parameter instead. Since v1.5.28", true)] - public SnapshotMetadata(string persistenceId, long sequenceNr) { } - [Newtonsoft.Json.JsonConstructorAttribute()] - public SnapshotMetadata(string persistenceId, long sequenceNr, System.DateTime timestamp) { } - public static System.Collections.Generic.IComparer Comparer { get; } - public string PersistenceId { get; } - public long SequenceNr { get; } - public System.DateTime Timestamp { get; } - public override bool Equals(object obj) { } - public bool Equals(Akka.Persistence.SnapshotMetadata other) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class SnapshotOffer : System.IEquatable - { - public SnapshotOffer(Akka.Persistence.SnapshotMetadata metadata, object snapshot) { } - public Akka.Persistence.SnapshotMetadata Metadata { get; } - public object Snapshot { get; } - public bool Equals(Akka.Persistence.SnapshotOffer other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class SnapshotSelectionCriteria : System.IEquatable - { - [Newtonsoft.Json.JsonConstructorAttribute()] - public SnapshotSelectionCriteria(long maxSequenceNr, System.DateTime maxTimeStamp, long minSequenceNr = 0, System.Nullable minTimestamp = null) { } - public SnapshotSelectionCriteria(long maxSequenceNr) { } - public static Akka.Persistence.SnapshotSelectionCriteria Latest { get; } - public long MaxSequenceNr { get; } - public System.DateTime MaxTimeStamp { get; } - public long MinSequenceNr { get; } - public System.Nullable MinTimestamp { get; } - public static Akka.Persistence.SnapshotSelectionCriteria None { get; } - public bool Equals(Akka.Persistence.SnapshotSelectionCriteria other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class SnapshotStoreHealthCheckResponse : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IPersistenceMessage, Akka.Persistence.ISnapshotMessage, Akka.Persistence.ISnapshotResponse - { - public SnapshotStoreHealthCheckResponse(Akka.Persistence.PersistenceHealthCheckResult result) { } - public Akka.Persistence.PersistenceHealthCheckResult Result { get; } - public override string ToString() { } - } - public sealed class StashingHandlerInvocation : Akka.Persistence.IPendingHandlerInvocation - { - public StashingHandlerInvocation(object evt, System.Action handler) { } - public object Event { get; } - public System.Action Handler { get; } - } - public sealed class ThrowExceptionConfigurator : Akka.Persistence.IStashOverflowStrategyConfigurator - { - public ThrowExceptionConfigurator() { } - public Akka.Persistence.IStashOverflowStrategy Create(Akka.Configuration.Config config) { } - } - [System.Runtime.CompilerServices.NullableAttribute(0)] - public class ThrowOverflowExceptionStrategy : Akka.Persistence.IStashOverflowStrategy - { - public static Akka.Persistence.ThrowOverflowExceptionStrategy Instance { get; } - } - public sealed class UnconfirmedDelivery : System.IEquatable - { - public UnconfirmedDelivery(long deliveryId, Akka.Actor.ActorPath destination, object message) { } - public long DeliveryId { get; } - public Akka.Actor.ActorPath Destination { get; } - public object Message { get; } - public bool Equals(Akka.Persistence.UnconfirmedDelivery other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class UnconfirmedWarning : System.IEquatable - { - public UnconfirmedWarning(Akka.Persistence.UnconfirmedDelivery[] unconfirmedDeliveries) { } - public Akka.Persistence.UnconfirmedDelivery[] UnconfirmedDeliveries { get; } - public bool Equals(Akka.Persistence.UnconfirmedWarning other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - [System.Runtime.CompilerServices.NullableAttribute(0)] - public abstract class UntypedPersistentActor : Akka.Persistence.Eventsourced - { - protected UntypedPersistentActor() { } - protected static Akka.Actor.IUntypedActorContext Context { get; } - protected void Become(Akka.Actor.UntypedReceive receive) { } - protected void BecomeStacked(Akka.Actor.UntypedReceive receive) { } - protected abstract void OnCommand(object message); - protected abstract void OnRecover(object message); - protected override bool Receive(object message) { } - protected virtual bool ReceiveCommand(object message) { } - protected virtual bool ReceiveRecover(object message) { } - } - public sealed class WriteMessageFailure : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalResponse, Akka.Persistence.IPersistenceMessage, System.IEquatable - { - public WriteMessageFailure(Akka.Persistence.IPersistentRepresentation persistent, System.Exception cause, int actorInstanceId) { } - public int ActorInstanceId { get; } - public System.Exception Cause { get; } - public Akka.Persistence.IPersistentRepresentation Persistent { get; } - public bool Equals(Akka.Persistence.WriteMessageFailure other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class WriteMessageRejected : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalResponse, Akka.Persistence.IPersistenceMessage, System.IEquatable - { - public WriteMessageRejected(Akka.Persistence.IPersistentRepresentation persistent, System.Exception cause, int actorInstanceId) { } - public int ActorInstanceId { get; } - public System.Exception Cause { get; } - public Akka.Persistence.IPersistentRepresentation Persistent { get; } - public bool Equals(Akka.Persistence.WriteMessageRejected other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class WriteMessageSuccess : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalResponse, Akka.Persistence.IPersistenceMessage, System.IEquatable - { - public WriteMessageSuccess(Akka.Persistence.IPersistentRepresentation persistent, int actorInstanceId) { } - public int ActorInstanceId { get; } - public Akka.Persistence.IPersistentRepresentation Persistent { get; } - public bool Equals(Akka.Persistence.WriteMessageSuccess other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class WriteMessages : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalRequest, Akka.Persistence.IPersistenceMessage, System.IEquatable - { - public WriteMessages(System.Collections.Generic.IEnumerable messages, Akka.Actor.IActorRef persistentActor, int actorInstanceId) { } - public int ActorInstanceId { get; } - public System.Collections.Generic.IEnumerable Messages { get; } - public Akka.Actor.IActorRef PersistentActor { get; } - public bool Equals(Akka.Persistence.WriteMessages other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class WriteMessagesFailed : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalResponse, Akka.Persistence.IPersistenceMessage, System.IEquatable - { - public WriteMessagesFailed(System.Exception cause, int writeCount) { } - public System.Exception Cause { get; } - public int WriteCount { get; } - public bool Equals(Akka.Persistence.WriteMessagesFailed other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class WriteMessagesSuccessful : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalResponse, Akka.Persistence.IPersistenceMessage - { - public static Akka.Persistence.WriteMessagesSuccessful Instance { get; } - } -} -namespace Akka.Persistence.Delivery -{ - [System.Runtime.CompilerServices.NullableAttribute(0)] - public class static EventSourcedProducerQueue - { - public static Akka.Actor.Props Create<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(string persistentId, Akka.Persistence.Delivery.EventSourcedProducerQueue.Settings settings) { } - public static Akka.Actor.Props Create<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(string persistentId, Akka.Actor.IActorRefFactory system) { } - [System.Runtime.CompilerServices.NullableAttribute(0)] - public class Settings : System.IEquatable - { - public System.TimeSpan CleanupUnusedAfter { get; set; } - public bool DeleteEvents { get; set; } - public string JournalPluginId { get; set; } - public int KeepNSnapshots { get; set; } - public System.TimeSpan RestartMaxBackoff { get; set; } - public int SnapshotEvery { get; set; } - public string SnapshotPluginId { get; set; } - public static Akka.Persistence.Delivery.EventSourcedProducerQueue.Settings Create(Akka.Actor.ActorSystem sys) { } - public static Akka.Persistence.Delivery.EventSourcedProducerQueue.Settings Create(Akka.Configuration.Config config) { } - } - } -} -namespace Akka.Persistence.Fsm -{ - public class static PersistentFSM - { - public interface IFsmState - { - string Identifier { get; } - } - public class PersistentFSMSnapshot : Akka.Persistence.Serialization.IMessage - { - public PersistentFSMSnapshot(string stateIdentifier, TD data, System.Nullable timeout) { } - public TD Data { get; } - public string StateIdentifier { get; } - public System.Nullable Timeout { get; } - protected bool Equals(Akka.Persistence.Fsm.PersistentFSM.PersistentFSMSnapshot other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - } - public class StateChangeEvent : Akka.Persistence.Serialization.IMessage - { - public StateChangeEvent(string stateIdentifier, System.Nullable timeout) { } - public string StateIdentifier { get; } - public System.Nullable Timeout { get; } - } - public class State - { - public State(TS stateName, TD stateData, System.Nullable timeout = null, Akka.Actor.FSMBase.Reason stopReason = null, System.Collections.Generic.IReadOnlyList replies = null, System.Collections.Generic.IReadOnlyList domainEvents = null, System.Action afterTransitionDo = null, bool notifies = True) { } - public System.Action AfterTransitionDo { get; } - public System.Collections.Generic.IReadOnlyList DomainEvents { get; } - public System.Collections.Generic.IReadOnlyList Replies { get; set; } - public TD StateData { get; } - public TS StateName { get; } - public Akka.Actor.FSMBase.Reason StopReason { get; } - public System.Nullable Timeout { get; } - public Akka.Persistence.Fsm.PersistentFSM.State AndThen(System.Action handler) { } - public Akka.Persistence.Fsm.PersistentFSM.State Applying(params TE[] events) { } - public Akka.Persistence.Fsm.PersistentFSM.State ForMax(System.TimeSpan timeout) { } - public Akka.Persistence.Fsm.PersistentFSM.State Replying(object replyValue) { } - public override string ToString() { } - [System.ObsoleteAttribute("Internal API easily to be confused with regular FSM\'s using. Use regular events (" + - "`Applying`). Internally, `copy` can be used instead.")] - public Akka.Persistence.Fsm.PersistentFSM.State Using(TD nextStateData) { } - } - } - public abstract class PersistentFSMBase : Akka.Persistence.PersistentActor, Akka.Routing.IListeners - { - protected PersistentFSMBase() { } - public Akka.Routing.ListenerSupport Listeners { get; } - public TData NextStateData { get; } - public TData StateData { get; } - public TState StateName { get; } - protected System.Collections.Generic.IEnumerable StateNames { get; } - protected virtual void ApplyState(Akka.Persistence.Fsm.PersistentFSM.State nextState) { } - public void CancelTimer(string name) { } - public Akka.Persistence.Fsm.PersistentFSM.State GoTo(TState nextStateName) { } - public bool IsTimerActive(string name) { } - protected virtual void LogTermination(Akka.Actor.FSMBase.Reason reason) { } - public void OnTermination(System.Action> terminationHandler) { } - public void OnTransition(Akka.Persistence.Fsm.PersistentFSMBase.TransitionHandler transitionHandler) { } - protected override void PostStop() { } - protected override bool ReceiveCommand(object message) { } - public void SetStateTimeout(TState state, System.Nullable timeout) { } - public void SetTimer(string name, object msg, System.TimeSpan timeout, bool repeat = False) { } - public void StartWith(TState stateName, TData stateData, System.Nullable timeout = null) { } - public Akka.Persistence.Fsm.PersistentFSM.State Stay() { } - public Akka.Persistence.Fsm.PersistentFSM.State Stop() { } - public Akka.Persistence.Fsm.PersistentFSM.State Stop(Akka.Actor.FSMBase.Reason reason) { } - public Akka.Persistence.Fsm.PersistentFSM.State Stop(Akka.Actor.FSMBase.Reason reason, TData stateData) { } - public Akka.Persistence.Fsm.PersistentFSMBase.TransformHelper Transform(Akka.Persistence.Fsm.PersistentFSMBase.StateFunction func) { } - public void When(TState stateName, Akka.Persistence.Fsm.PersistentFSMBase.StateFunction func, System.Nullable timeout = null) { } - public void WhenUnhandled(Akka.Persistence.Fsm.PersistentFSMBase.StateFunction stateFunction) { } - public delegate Akka.Persistence.Fsm.PersistentFSM.State StateFunction(Akka.Actor.FSMBase.Event fsmEvent, Akka.Persistence.Fsm.PersistentFSM.State state = null); - public sealed class TransformHelper - { - public TransformHelper(Akka.Persistence.Fsm.PersistentFSMBase.StateFunction func) { } - public Akka.Persistence.Fsm.PersistentFSMBase.StateFunction Func { get; } - public Akka.Persistence.Fsm.PersistentFSMBase.StateFunction Using(System.Func, Akka.Persistence.Fsm.PersistentFSM.State> andThen) { } - } - public delegate void TransitionHandler(TState initialState, TState nextState); - } - public abstract class PersistentFSM : Akka.Persistence.Fsm.PersistentFSMBase - where TState : Akka.Persistence.Fsm.PersistentFSM.IFsmState - { - protected PersistentFSM() { } - protected abstract TData ApplyEvent(TEvent domainEvent, TData currentData); - protected override void ApplyState(Akka.Persistence.Fsm.PersistentFSM.State nextState) { } - protected virtual void OnRecoveryCompleted() { } - protected override bool ReceiveRecover(object message) { } - public void SaveStateSnapshot() { } - } -} -namespace Akka.Persistence.Journal -{ - public class AsyncReplayTimeoutException : Akka.Actor.AkkaException - { - public AsyncReplayTimeoutException() { } - public AsyncReplayTimeoutException(string message) { } - protected AsyncReplayTimeoutException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - } - public abstract class AsyncWriteJournal : Akka.Persistence.Journal.WriteJournalBase, Akka.Persistence.Journal.IAsyncRecovery - { - protected readonly bool CanPublish; - protected AsyncWriteJournal() { } - public virtual System.Threading.Tasks.Task CheckHealthAsync(System.Threading.CancellationToken cancellationToken = null) { } - protected abstract System.Threading.Tasks.Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr, System.Threading.CancellationToken cancellationToken); - public abstract System.Threading.Tasks.Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr, System.Threading.CancellationToken cancellationToken); - protected virtual bool Receive(object message) { } - protected virtual bool ReceivePluginInternal(object message) { } - protected bool ReceiveWriteJournal(object message) { } - public abstract System.Threading.Tasks.Task ReplayMessagesAsync(Akka.Actor.IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, System.Action recoveryCallback); - protected static System.Exception TryUnwrapException(System.Exception e) { } - protected abstract System.Threading.Tasks.Task> WriteMessagesAsync(System.Collections.Generic.IEnumerable messages, System.Threading.CancellationToken cancellationToken); - } - public abstract class AsyncWriteProxy : Akka.Persistence.Journal.AsyncWriteJournal, Akka.Actor.IActorStash, Akka.Actor.IWithTimers, Akka.Actor.IWithUnboundedStash, Akka.Actor.IWithUnrestrictedStash, Akka.Dispatch.IRequiresMessageQueue - { - protected AsyncWriteProxy() { } - public Akka.Actor.IStash Stash { get; set; } - public abstract System.TimeSpan Timeout { get; } - public Akka.Actor.ITimerScheduler Timers { get; set; } - public override void AroundPreStart() { } - protected override bool AroundReceive(Akka.Actor.Receive receive, object message) { } - protected override System.Threading.Tasks.Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr, System.Threading.CancellationToken cancellationToken) { } - public override System.Threading.Tasks.Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr, System.Threading.CancellationToken cancellationToken) { } - public override System.Threading.Tasks.Task ReplayMessagesAsync(Akka.Actor.IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, System.Action recoveryCallback) { } - protected override System.Threading.Tasks.Task> WriteMessagesAsync(System.Collections.Generic.IEnumerable messages, System.Threading.CancellationToken cancellationToken) { } - public class InitTimeout - { - public static Akka.Persistence.Journal.AsyncWriteProxy.InitTimeout Instance { get; } - } - } - public class static AsyncWriteTarget - { - public sealed class DeleteMessagesTo : System.IEquatable - { - public DeleteMessagesTo(string persistenceId, long toSequenceNr) { } - public string PersistenceId { get; } - public long ToSequenceNr { get; } - public bool Equals(Akka.Persistence.Journal.AsyncWriteTarget.DeleteMessagesTo other) { } - } - public sealed class ReplayFailure - { - public ReplayFailure(System.Exception cause) { } - public System.Exception Cause { get; } - } - public sealed class ReplayMessages : System.IEquatable - { - public ReplayMessages(string persistenceId, long fromSequenceNr, long toSequenceNr, long max) { } - public long FromSequenceNr { get; } - public long Max { get; } - public string PersistenceId { get; } - public long ToSequenceNr { get; } - public bool Equals(Akka.Persistence.Journal.AsyncWriteTarget.ReplayMessages other) { } - } - public sealed class ReplaySuccess : System.IEquatable - { - public ReplaySuccess(long highestSequenceNr) { } - public long HighestSequenceNr { get; } - public bool Equals(Akka.Persistence.Journal.AsyncWriteTarget.ReplaySuccess other) { } - } - public sealed class WriteMessages - { - public WriteMessages(System.Collections.Generic.IEnumerable messages) { } - public Akka.Persistence.AtomicWrite[] Messages { get; } - } - } - public sealed class CombinedReadEventAdapter : Akka.Persistence.Journal.IEventAdapter, Akka.Persistence.Journal.IReadEventAdapter, Akka.Persistence.Journal.IWriteEventAdapter - { - public CombinedReadEventAdapter(System.Collections.Generic.IEnumerable adapters) { } - public System.Collections.Generic.IEnumerable Adapters { get; } - public Akka.Persistence.Journal.IEventSequence FromJournal(object evt, string manifest) { } - public string Manifest(object evt) { } - public object ToJournal(object evt) { } - } - public sealed class EmptyEventSequence : Akka.Persistence.Journal.IEmptyEventSequence, Akka.Persistence.Journal.IEventSequence, System.IEquatable - { - public static readonly Akka.Persistence.Journal.EmptyEventSequence Instance; - public System.Collections.Generic.IEnumerable Events { get; } - public bool Equals(Akka.Persistence.Journal.IEventSequence other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - } - public class EventAdapters - { - protected EventAdapters(System.Collections.Concurrent.ConcurrentDictionary map, System.Collections.Generic.IEnumerable> bindings, Akka.Event.ILoggingAdapter log) { } - public static Akka.Persistence.Journal.EventAdapters Create(Akka.Actor.ExtendedActorSystem system, Akka.Configuration.Config config) { } - public Akka.Persistence.Journal.IEventAdapter Get() { } - public virtual Akka.Persistence.Journal.IEventAdapter Get(System.Type type) { } - } - public class static EventSequence - { - public static Akka.Persistence.Journal.IEventSequence Empty; - public static Akka.Persistence.Journal.IEventSequence Create(params object[] events) { } - public static Akka.Persistence.Journal.IEventSequence Create(System.Collections.Generic.IEnumerable events) { } - public static Akka.Persistence.Journal.IEventSequence Single(object e) { } - } - public class EventSequence : Akka.Persistence.Journal.IEventSequence, System.IEquatable - { - public EventSequence(System.Collections.Generic.IEnumerable events) { } - public System.Collections.Generic.IEnumerable Events { get; } - public bool Equals(Akka.Persistence.Journal.IEventSequence other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - } - public interface IAsyncRecovery - { - System.Threading.Tasks.Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr, System.Threading.CancellationToken cancellationToken); - System.Threading.Tasks.Task ReplayMessagesAsync(Akka.Actor.IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, System.Action recoveryCallback); - } - public interface IEmptyEventSequence : Akka.Persistence.Journal.IEventSequence { } - public interface IEventAdapter : Akka.Persistence.Journal.IReadEventAdapter, Akka.Persistence.Journal.IWriteEventAdapter { } - public interface IEventSequence - { - System.Collections.Generic.IEnumerable Events { get; } - } - public interface IReadEventAdapter - { - Akka.Persistence.Journal.IEventSequence FromJournal(object evt, string manifest); - } - public interface IWriteEventAdapter - { - string Manifest(object evt); - object ToJournal(object evt); - } - public sealed class IdentityEventAdapter : Akka.Persistence.Journal.IEventAdapter, Akka.Persistence.Journal.IReadEventAdapter, Akka.Persistence.Journal.IWriteEventAdapter - { - public static Akka.Persistence.Journal.IdentityEventAdapter Instance { get; } - public Akka.Persistence.Journal.IEventSequence FromJournal(object evt, string manifest) { } - public string Manifest(object evt) { } - public object ToJournal(object evt) { } - } - public class MemoryJournal : Akka.Persistence.Journal.AsyncWriteJournal - { - public MemoryJournal() { } - protected virtual System.Collections.Generic.Dictionary DeletedTo { get; } - protected virtual System.Collections.Generic.List EventLog { get; } - protected virtual System.Collections.Generic.Dictionary> EventsByPersistenceId { get; } - protected virtual object Lock { get; } - public System.Collections.Generic.IDictionary> Add(Akka.Persistence.IPersistentRepresentation persistent) { } - public System.Collections.Generic.IDictionary> Delete(string pid, long seqNr) { } - protected override System.Threading.Tasks.Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr, System.Threading.CancellationToken cancellationToken) { } - public long HighestSequenceNr(string pid) { } - public System.Collections.Generic.IEnumerable Read(string pid, long from, long to, long max) { } - public override System.Threading.Tasks.Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr, System.Threading.CancellationToken cancellationToken) { } - protected override bool ReceivePluginInternal(object message) { } - public override System.Threading.Tasks.Task ReplayMessagesAsync(Akka.Actor.IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, System.Action recoveryCallback) { } - protected override System.Threading.Tasks.Task> WriteMessagesAsync(System.Collections.Generic.IEnumerable messages, System.Threading.CancellationToken cancellationToken) { } - public sealed class CurrentPersistenceIds : Akka.Event.IDeadLetterSuppression - { - public readonly System.Collections.Generic.IEnumerable AllPersistenceIds; - public readonly int HighestOrderingNumber; - public CurrentPersistenceIds(System.Collections.Generic.IEnumerable allPersistenceIds, int highestOrderingNumber) { } - } - public sealed class EventReplayFailure - { - public EventReplayFailure(System.Exception cause) { } - public System.Exception Cause { get; } - public bool Equals(Akka.Persistence.Journal.MemoryJournal.EventReplayFailure other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class EventReplaySuccess - { - public EventReplaySuccess(int highestSequenceNr) { } - public int HighestSequenceNr { get; } - public bool Equals(Akka.Persistence.Journal.MemoryJournal.EventReplaySuccess other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - } - public sealed class ReplayAllEvents : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalRequest, Akka.Persistence.IPersistenceMessage - { - public readonly int FromOffset; - public readonly long Max; - public readonly Akka.Actor.IActorRef ReplyTo; - public readonly int ToOffset; - public ReplayAllEvents(int fromOffset, int toOffset, long max, Akka.Actor.IActorRef replyTo) { } - } - public sealed class ReplayTaggedMessages : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalRequest, Akka.Persistence.IPersistenceMessage - { - public readonly int FromOffset; - public readonly int Max; - public readonly Akka.Actor.IActorRef ReplyTo; - public readonly string Tag; - public readonly int ToOffset; - public ReplayTaggedMessages(int fromOffset, int toOffset, int max, string tag, Akka.Actor.IActorRef replyTo) { } - } - public sealed class ReplayTaggedMessagesSuccess - { - public ReplayTaggedMessagesSuccess(int highestSequenceNr) { } - public int HighestSequenceNr { get; } - } - public sealed class ReplayedEvent : Akka.Actor.INoSerializationVerificationNeeded, Akka.Event.IDeadLetterSuppression - { - public readonly int Offset; - public readonly Akka.Persistence.IPersistentRepresentation Persistent; - public ReplayedEvent(Akka.Persistence.IPersistentRepresentation persistent, int offset) { } - } - public sealed class ReplayedTaggedMessage : Akka.Actor.INoSerializationVerificationNeeded, Akka.Event.IDeadLetterSuppression - { - public readonly int Offset; - public readonly Akka.Persistence.IPersistentRepresentation Persistent; - [System.ObsoleteAttribute("If there are tags, they will be stored in the PersistentRepresentation")] - public readonly string Tag; - public ReplayedTaggedMessage(Akka.Persistence.IPersistentRepresentation persistent, string tag, int offset) { } - } - public sealed class SelectCurrentPersistenceIds : Akka.Actor.INoSerializationVerificationNeeded, Akka.Persistence.IJournalMessage, Akka.Persistence.IJournalRequest, Akka.Persistence.IPersistenceMessage - { - public SelectCurrentPersistenceIds(int offset, Akka.Actor.IActorRef replyTo) { } - public int Offset { get; } - public Akka.Actor.IActorRef ReplyTo { get; } - } - } - public class PersistencePluginProxy : Akka.Actor.ActorBase, Akka.Actor.IActorStash, Akka.Actor.IWithTimers, Akka.Actor.IWithUnboundedStash, Akka.Actor.IWithUnrestrictedStash, Akka.Dispatch.IRequiresMessageQueue - { - public PersistencePluginProxy(Akka.Configuration.Config config) { } - public Akka.Actor.IStash Stash { get; set; } - public Akka.Actor.ITimerScheduler Timers { get; set; } - protected override void PreStart() { } - protected override bool Receive(object message) { } - public static void SetTargetLocation(Akka.Actor.ActorSystem system, Akka.Actor.Address address) { } - public static void Start(Akka.Actor.ActorSystem system) { } - public sealed class TargetLocation - { - public TargetLocation(Akka.Actor.Address address) { } - public Akka.Actor.Address Address { get; } - } - } - public class PersistencePluginProxyExtension : Akka.Actor.ExtensionIdProvider, Akka.Actor.IExtension - { - public PersistencePluginProxyExtension(Akka.Actor.ActorSystem system) { } - public override Akka.Persistence.Journal.PersistencePluginProxyExtension CreateExtension(Akka.Actor.ExtendedActorSystem system) { } - } - public class ReplayFilter : Akka.Actor.ActorBase - { - public ReplayFilter(Akka.Actor.IActorRef persistentActor, Akka.Persistence.Journal.ReplayFilterMode mode, int windowSize, int maxOldWriters, bool debugEnabled) { } - public bool DebugEnabled { get; } - public int MaxOldWriters { get; } - public Akka.Persistence.Journal.ReplayFilterMode Mode { get; } - public Akka.Actor.IActorRef PersistentActor { get; } - public int WindowSize { get; } - public static Akka.Actor.Props Props(Akka.Actor.IActorRef persistentActor, Akka.Persistence.Journal.ReplayFilterMode mode, int windowSize, int maxOldWriters, bool debugEnabled) { } - protected override bool Receive(object message) { } - } - public enum ReplayFilterMode - { - Fail = 0, - Warn = 1, - RepairByDiscardOld = 2, - Disabled = 3, - } - public sealed class SetStore - { - public readonly Akka.Actor.IActorRef Store; - public SetStore(Akka.Actor.IActorRef store) { } - } - public class SharedMemoryJournal : Akka.Persistence.Journal.MemoryJournal - { - public SharedMemoryJournal() { } - protected override System.Collections.Generic.Dictionary DeletedTo { get; } - protected override System.Collections.Generic.List EventLog { get; } - protected override System.Collections.Generic.Dictionary> EventsByPersistenceId { get; } - protected override object Lock { get; } - } - public struct SingleEventSequence : Akka.Persistence.Journal.IEventSequence, System.IEquatable - { - public SingleEventSequence(object e) { } - public System.Collections.Generic.IEnumerable Events { get; } - public bool Equals(Akka.Persistence.Journal.IEventSequence other) { } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - } - public struct Tagged - { - public Tagged(object payload, System.Collections.Generic.IEnumerable tags) { } - public Tagged(object payload, System.Collections.Immutable.IImmutableSet tags) { } - [get: System.Runtime.CompilerServices.IsReadOnlyAttribute()] - public object Payload { get; } - [get: System.Runtime.CompilerServices.IsReadOnlyAttribute()] - public System.Collections.Immutable.IImmutableSet Tags { get; } - } - public abstract class WriteJournalBase : Akka.Actor.ActorBase - { - protected WriteJournalBase() { } - [Akka.Annotations.InternalApiAttribute()] - protected System.Collections.Generic.IEnumerable AdaptFromJournal(Akka.Persistence.IPersistentRepresentation representation) { } - protected Akka.Persistence.IPersistentRepresentation AdaptToJournal(Akka.Persistence.IPersistentRepresentation representation) { } - protected System.Collections.Generic.IEnumerable PreparePersistentBatch(System.Collections.Generic.IEnumerable resequenceables) { } - } -} -namespace Akka.Persistence.Serialization -{ - public interface IMessage { } - public sealed class PersistenceMessageSerializer : Akka.Serialization.Serializer - { - public PersistenceMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } - public override bool IncludeManifest { get; } - public override object FromBinary(byte[] bytes, System.Type type) { } - public override byte[] ToBinary(object obj) { } - } - public class PersistenceSnapshotSerializer : Akka.Serialization.Serializer - { - public PersistenceSnapshotSerializer(Akka.Actor.ExtendedActorSystem system) { } - public override bool IncludeManifest { get; } - public override object FromBinary(byte[] bytes, System.Type type) { } - public override byte[] ToBinary(object obj) { } - } - public sealed class Snapshot - { - public Snapshot(object data) { } - public object Data { get; } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - } -} -namespace Akka.Persistence.Snapshot -{ - public class LocalSnapshotStore : Akka.Persistence.Snapshot.SnapshotStore - { - public LocalSnapshotStore() { } - protected override System.Threading.Tasks.Task DeleteAsync(Akka.Persistence.SnapshotMetadata metadata, System.Threading.CancellationToken cancellationToken) { } - protected override System.Threading.Tasks.Task DeleteAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken) { } - protected System.IO.FileInfo GetSnapshotFileForWrite(Akka.Persistence.SnapshotMetadata metadata, string extension = "") { } - protected override System.Threading.Tasks.Task LoadAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken) { } - protected override void PreStart() { } - protected override bool ReceivePluginInternal(object message) { } - protected virtual void Save(Akka.Persistence.SnapshotMetadata metadata, object snapshot) { } - protected override System.Threading.Tasks.Task SaveAsync(Akka.Persistence.SnapshotMetadata metadata, object snapshot, System.Threading.CancellationToken cancellationToken) { } - protected void Serialize(System.IO.Stream stream, Akka.Persistence.Serialization.Snapshot snapshot) { } - protected System.IO.FileInfo WithOutputStream(Akka.Persistence.SnapshotMetadata metadata, System.Action p) { } - } - public class MemorySnapshotStore : Akka.Persistence.Snapshot.SnapshotStore - { - public MemorySnapshotStore() { } - protected virtual System.Collections.Generic.List Snapshots { get; } - protected override System.Threading.Tasks.Task DeleteAsync(Akka.Persistence.SnapshotMetadata metadata, System.Threading.CancellationToken cancellationToken) { } - protected override System.Threading.Tasks.Task DeleteAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken) { } - protected override System.Threading.Tasks.Task LoadAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken) { } - protected override System.Threading.Tasks.Task SaveAsync(Akka.Persistence.SnapshotMetadata metadata, object snapshot, System.Threading.CancellationToken cancellationToken) { } - } - public sealed class NoSnapshotStore : Akka.Persistence.Snapshot.SnapshotStore - { - public NoSnapshotStore() { } - protected override System.Threading.Tasks.Task DeleteAsync(Akka.Persistence.SnapshotMetadata metadata, System.Threading.CancellationToken cancellationToken) { } - protected override System.Threading.Tasks.Task DeleteAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken) { } - protected override System.Threading.Tasks.Task LoadAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken) { } - protected override System.Threading.Tasks.Task SaveAsync(Akka.Persistence.SnapshotMetadata metadata, object snapshot, System.Threading.CancellationToken cancellationToken) { } - public class NoSnapshotStoreException : System.Exception - { - public NoSnapshotStoreException() { } - public NoSnapshotStoreException(string message) { } - public NoSnapshotStoreException(string message, System.Exception innerException) { } - protected NoSnapshotStoreException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - } - } - public class SnapshotEntry - { - public SnapshotEntry() { } - public string Id { get; set; } - public string PersistenceId { get; set; } - public long SequenceNr { get; set; } - public object Snapshot { get; set; } - public long Timestamp { get; set; } - } - public abstract class SnapshotStore : Akka.Actor.ActorBase - { - protected SnapshotStore() { } - public virtual System.Threading.Tasks.Task CheckHealthAsync(System.Threading.CancellationToken cancellationToken = null) { } - protected abstract System.Threading.Tasks.Task DeleteAsync(Akka.Persistence.SnapshotMetadata metadata, System.Threading.CancellationToken cancellationToken); - protected abstract System.Threading.Tasks.Task DeleteAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken); - protected abstract System.Threading.Tasks.Task LoadAsync(string persistenceId, Akka.Persistence.SnapshotSelectionCriteria criteria, System.Threading.CancellationToken cancellationToken); - protected virtual bool Receive(object message) { } - protected virtual bool ReceivePluginInternal(object message) { } - protected abstract System.Threading.Tasks.Task SaveAsync(Akka.Persistence.SnapshotMetadata metadata, object snapshot, System.Threading.CancellationToken cancellationToken); - } -} \ No newline at end of file From 83b2600036b6b8d9f0210aea779c7f806d5703af Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 9 Oct 2025 10:44:27 -0500 Subject: [PATCH 03/12] Add diagnostic logging to MemoryJournal to track lock contention Added comprehensive telemetry to investigate intermittent test failures in InMemoryEventsByTagSpec.ReadJournal_live_query_EventsByTag_should_find_events_from_offset_exclusive. Changes: - Added Stopwatch-based timing instrumentation to track lock acquisition wait times in WriteMessagesAsync and ReplayTaggedMessagesAsync - Added DEBUG logging for all journal message types (ReplayTaggedMessages, ReplayAllEvents, SelectCurrentPersistenceIds) - Logs thread IDs, lock wait times, lock hold times, and event counts - Enabled DEBUG log level in InMemoryEventsByTagSpec test configuration Diagnostic output includes: - "[DIAG] WriteMessagesAsync called on thread X, attempting to acquire lock" - "[DIAG] Lock acquired after Xms on thread Y" - "[DIAG] Lock released after holding for Xms" - "[DIAG] Wrote event for {persistenceId}, seq {seqNr}, total events: {count}" - "[DIAG] ReplayTaggedMessages lock acquired after Xms" - "[DIAG] Found X events matching tag 'Y'" This instrumentation will capture timing data on CI/CD build servers to determine if the test failures are caused by: 1. Lock contention/fairness issues with Monitor 2. Thread pool starvation 3. Async execution context delays 4. RecoveryPermitter bottlenecks All diagnostic logging uses [DIAG] prefix for easy filtering. These changes are temporary for diagnosis and will be reverted once root cause is identified. --- .../InMemoryEventsByTagSpec.cs | 2 +- .../Akka.Persistence/Journal/MemoryJournal.cs | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/contrib/persistence/Akka.Persistence.Query.InMemory.Tests/InMemoryEventsByTagSpec.cs b/src/contrib/persistence/Akka.Persistence.Query.InMemory.Tests/InMemoryEventsByTagSpec.cs index b85629be893..613635f078a 100644 --- a/src/contrib/persistence/Akka.Persistence.Query.InMemory.Tests/InMemoryEventsByTagSpec.cs +++ b/src/contrib/persistence/Akka.Persistence.Query.InMemory.Tests/InMemoryEventsByTagSpec.cs @@ -14,7 +14,7 @@ namespace Akka.Persistence.Query.InMemory.Tests public class InMemoryEventsByTagSpec : EventsByTagSpec { private static Config Config() => ConfigurationFactory.ParseString(@" - akka.loglevel = INFO + akka.loglevel = DEBUG akka.persistence.journal.inmem { event-adapters { color-tagger = ""Akka.Persistence.TCK.Query.ColorFruitTagger, Akka.Persistence.TCK"" diff --git a/src/core/Akka.Persistence/Journal/MemoryJournal.cs b/src/core/Akka.Persistence/Journal/MemoryJournal.cs index 577359df4a2..b9c9716271a 100644 --- a/src/core/Akka.Persistence/Journal/MemoryJournal.cs +++ b/src/core/Akka.Persistence/Journal/MemoryJournal.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -43,8 +44,16 @@ public class MemoryJournal : AsyncWriteJournal protected override Task> WriteMessagesAsync(IEnumerable messages, CancellationToken cancellationToken) { + var waitStartTime = Stopwatch.GetTimestamp(); + var threadId = Environment.CurrentManagedThreadId; + Context.GetLogger().Debug("[DIAG] WriteMessagesAsync called on thread {0}, attempting to acquire lock", threadId); + lock (Lock) { + var lockAcquiredTime = Stopwatch.GetTimestamp(); + var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; + Context.GetLogger().Debug("[DIAG] Lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); + foreach (var w in messages) { foreach (var p in (IEnumerable)w.Payload) @@ -60,8 +69,17 @@ protected override Task> WriteMessagesAsync(IEnumerabl EventsByPersistenceId[persistentRepresentation.PersistenceId] = pidEvents; } pidEvents.Add(persistentRepresentation); + + Context.GetLogger().Debug("[DIAG] Wrote event for {0}, seq {1}, total events in log: {2}", + persistentRepresentation.PersistenceId, + persistentRepresentation.SequenceNr, + EventLog.Count); } } + + var lockReleasedTime = Stopwatch.GetTimestamp(); + var holdTimeMs = (lockReleasedTime - lockAcquiredTime) * 1000.0 / Stopwatch.Frequency; + Context.GetLogger().Debug("[DIAG] Lock released after holding for {0:F2}ms on thread {1}", holdTimeMs, threadId); } return Task.FromResult>(null); @@ -219,16 +237,21 @@ protected override bool ReceivePluginInternal(object message) switch (message) { case SelectCurrentPersistenceIds request: + Context.GetLogger().Debug("[DIAG] Received SelectCurrentPersistenceIds from {0}", request.ReplyTo); SelectAllPersistenceIdsAsync(request.Offset) .PipeTo(request.ReplyTo, success: result => new CurrentPersistenceIds(result.Item1, result.LastOrdering)); return true; case ReplayTaggedMessages replay: + Context.GetLogger().Debug("[DIAG] Received ReplayTaggedMessages for tag '{0}', fromOffset={1}, max={2}, toOffset={3}, replyTo={4}", + replay.Tag, replay.FromOffset, replay.Max, replay.ToOffset, replay.ReplyTo); ReplayTaggedMessagesAsync(replay) .PipeTo(replay.ReplyTo, success: h => new ReplayTaggedMessagesSuccess(h), failure: e => new ReplayMessagesFailure(e)); return true; case ReplayAllEvents replay: + Context.GetLogger().Debug("[DIAG] Received ReplayAllEvents fromOffset={0}, max={1}, replyTo={2}", + replay.FromOffset, replay.Max, replay.ReplyTo); ReplayAllEventsAsync(replay) .PipeTo(replay.ReplyTo, success: h => new EventReplaySuccess(h), failure: e => new EventReplayFailure(e)); @@ -258,11 +281,20 @@ protected override bool ReceivePluginInternal(object message) /// private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) { + var waitStartTime = Stopwatch.GetTimestamp(); + var threadId = Environment.CurrentManagedThreadId; + var log = Context.GetLogger(); + log.Debug("[DIAG] ReplayTaggedMessagesAsync starting for tag '{0}' on thread {1}, attempting to acquire lock", replay.Tag, threadId); + IPersistentRepresentation[] snapshot; int count; lock (Lock) { + var lockAcquiredTime = Stopwatch.GetTimestamp(); + var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; + log.Debug("[DIAG] ReplayTaggedMessages lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); + // Scan for events with matching tag snapshot = EventLog .Where(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)) @@ -271,6 +303,9 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) .ToArray(); count = EventLog.Count(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)); + + log.Debug("[DIAG] Found {0} events matching tag '{1}', total tagged events: {2}, EventLog size: {3}", + snapshot.Length, replay.Tag, count, EventLog.Count); } // Send messages outside the lock to avoid potential deadlocks @@ -278,9 +313,12 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) foreach (var persistence in snapshot) { replay.ReplyTo.Tell(new ReplayedTaggedMessage(persistence, replay.Tag, replay.FromOffset + index), ActorRefs.NoSender); + log.Debug("[DIAG] Sent ReplayedTaggedMessage for {0}, seq {1}, offset {2}", + persistence.PersistenceId, persistence.SequenceNr, replay.FromOffset + index); index++; } + log.Debug("[DIAG] ReplayTaggedMessagesAsync completed, returning highestSequenceNr={0}", count - 1); return Task.FromResult(count - 1); } From 9f645dcc103407ffd5e733904a3aac89e65050f2 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 9 Oct 2025 13:41:35 -0500 Subject: [PATCH 04/12] Add diagnostic logging to TestActor to trace message flow Added comprehensive logging to TestActor to diagnose where the actor gets stuck when the test times out: - PreStart: Confirms actor creation - OnRecover: Traces recovery message flow - OnCommand: Confirms message receipt - Before/after Persist call: Identifies if Persist blocks - Persist callback: Confirms persist completion This will identify whether the issue is: 1. Actor not starting 2. Actor stuck in recovery 3. Message not reaching OnCommand 4. Persist call blocking/deadlocking 5. Persist callback never executing All logs use [DIAG-ACTOR] prefix to distinguish from journal logs. --- src/core/Akka.Persistence.TCK/Query/TestActor.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/core/Akka.Persistence.TCK/Query/TestActor.cs b/src/core/Akka.Persistence.TCK/Query/TestActor.cs index 336f4ec72e2..2144b157e28 100644 --- a/src/core/Akka.Persistence.TCK/Query/TestActor.cs +++ b/src/core/Akka.Persistence.TCK/Query/TestActor.cs @@ -34,12 +34,20 @@ public TestActor(string persistenceId) public override string PersistenceId { get; } + protected override void PreStart() + { + Log.Debug("[DIAG-ACTOR] TestActor {0} PreStart called", PersistenceId); + base.PreStart(); + } + protected override void OnRecover(object message) { + Log.Debug("[DIAG-ACTOR] TestActor {0} OnRecover: {1}", PersistenceId, message); } protected override void OnCommand(object message) { + Log.Debug("[DIAG-ACTOR] TestActor {0} OnCommand received: {1}", PersistenceId, message); switch (message) { case DeleteCommand delete: @@ -47,8 +55,14 @@ protected override void OnCommand(object message) Become(WhileDeleting(Sender)); // need to wait for delete ACK to return break; case string cmd: + Log.Debug("[DIAG-ACTOR] TestActor {0} calling Persist for: {1}", PersistenceId, cmd); var sender = Sender; - Persist(cmd, e => sender.Tell($"{e}-done")); + Persist(cmd, e => + { + Log.Debug("[DIAG-ACTOR] TestActor {0} Persist callback executing for: {1}", PersistenceId, e); + sender.Tell($"{e}-done"); + }); + Log.Debug("[DIAG-ACTOR] TestActor {0} Persist call returned for: {1}", PersistenceId, cmd); break; } } From 570ec8f977c3614e9dde2247d7949c1c365d15c8 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 9 Oct 2025 13:46:27 -0500 Subject: [PATCH 05/12] Fix potential deadlock caused by Context.GetLogger() from async continuation CRITICAL BUG FIX: Replaced Context.GetLogger() calls with Debug.WriteLine in WriteMessagesAsync and ReplayTaggedMessagesAsync. Root Cause: These methods are called from async continuations on thread pool threads (not actor threads) due to ConfigureAwait(false) in AsyncWriteJournal.ExecuteBatch. Calling Context.GetLogger() from non-actor threads is unsafe and can cause deadlock: 1. Context.GetLogger() accesses actor state/mailbox 2. If actor is waiting for WriteMessagesSuccess but logger tries to interact with actor system... 3. Circular wait/deadlock! This explains BOTH the ReaderWriterLockSlim and Monitor failures: - With ReaderWriterLockSlim: Logging from async thread while holding write lock tried to access actor context, causing contention with read-locked queries - With Monitor: Same issue, different lock mechanism The lock type wasn't the problem - accessing actor Context from thread pool threads was the root cause all along. Using Debug.WriteLine instead ensures diagnostic logging doesn't interfere with actor threading model or cause deadlocks. --- .../Akka.Persistence/Journal/MemoryJournal.cs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/core/Akka.Persistence/Journal/MemoryJournal.cs b/src/core/Akka.Persistence/Journal/MemoryJournal.cs index b9c9716271a..61d1a771260 100644 --- a/src/core/Akka.Persistence/Journal/MemoryJournal.cs +++ b/src/core/Akka.Persistence/Journal/MemoryJournal.cs @@ -16,6 +16,10 @@ using Akka.Event; using Akka.Util.Internal; +#if DEBUG +using System.IO; +#endif + namespace Akka.Persistence.Journal { /// @@ -46,13 +50,17 @@ protected override Task> WriteMessagesAsync(IEnumerabl { var waitStartTime = Stopwatch.GetTimestamp(); var threadId = Environment.CurrentManagedThreadId; - Context.GetLogger().Debug("[DIAG] WriteMessagesAsync called on thread {0}, attempting to acquire lock", threadId); + + // IMPORTANT: Cannot use Context.GetLogger() here - this method is called from async continuation + // on thread pool thread (not actor thread) due to ConfigureAwait(false) in ExecuteBatch. + // Using Debug.WriteLine instead for diagnostic logging to avoid potential deadlock. + Debug.WriteLine($"[DIAG] WriteMessagesAsync called on thread {threadId}, attempting to acquire lock"); lock (Lock) { var lockAcquiredTime = Stopwatch.GetTimestamp(); var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; - Context.GetLogger().Debug("[DIAG] Lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); + Debug.WriteLine($"[DIAG] Lock acquired after {waitTimeMs:F2}ms on thread {threadId}"); foreach (var w in messages) { @@ -70,16 +78,13 @@ protected override Task> WriteMessagesAsync(IEnumerabl } pidEvents.Add(persistentRepresentation); - Context.GetLogger().Debug("[DIAG] Wrote event for {0}, seq {1}, total events in log: {2}", - persistentRepresentation.PersistenceId, - persistentRepresentation.SequenceNr, - EventLog.Count); + Debug.WriteLine($"[DIAG] Wrote event for {persistentRepresentation.PersistenceId}, seq {persistentRepresentation.SequenceNr}, total events in log: {EventLog.Count}"); } } var lockReleasedTime = Stopwatch.GetTimestamp(); var holdTimeMs = (lockReleasedTime - lockAcquiredTime) * 1000.0 / Stopwatch.Frequency; - Context.GetLogger().Debug("[DIAG] Lock released after holding for {0:F2}ms on thread {1}", holdTimeMs, threadId); + Debug.WriteLine($"[DIAG] Lock released after holding for {holdTimeMs:F2}ms on thread {threadId}"); } return Task.FromResult>(null); @@ -283,8 +288,9 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) { var waitStartTime = Stopwatch.GetTimestamp(); var threadId = Environment.CurrentManagedThreadId; - var log = Context.GetLogger(); - log.Debug("[DIAG] ReplayTaggedMessagesAsync starting for tag '{0}' on thread {1}, attempting to acquire lock", replay.Tag, threadId); + + // Using Debug.WriteLine to avoid Context access from potentially wrong thread + Debug.WriteLine($"[DIAG] ReplayTaggedMessagesAsync starting for tag '{replay.Tag}' on thread {threadId}, attempting to acquire lock"); IPersistentRepresentation[] snapshot; int count; @@ -293,7 +299,7 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) { var lockAcquiredTime = Stopwatch.GetTimestamp(); var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; - log.Debug("[DIAG] ReplayTaggedMessages lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); + Debug.WriteLine($"[DIAG] ReplayTaggedMessages lock acquired after {waitTimeMs:F2}ms on thread {threadId}"); // Scan for events with matching tag snapshot = EventLog @@ -304,8 +310,7 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) count = EventLog.Count(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)); - log.Debug("[DIAG] Found {0} events matching tag '{1}', total tagged events: {2}, EventLog size: {3}", - snapshot.Length, replay.Tag, count, EventLog.Count); + Debug.WriteLine($"[DIAG] Found {snapshot.Length} events matching tag '{replay.Tag}', total tagged events: {count}, EventLog size: {EventLog.Count}"); } // Send messages outside the lock to avoid potential deadlocks @@ -313,12 +318,11 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) foreach (var persistence in snapshot) { replay.ReplyTo.Tell(new ReplayedTaggedMessage(persistence, replay.Tag, replay.FromOffset + index), ActorRefs.NoSender); - log.Debug("[DIAG] Sent ReplayedTaggedMessage for {0}, seq {1}, offset {2}", - persistence.PersistenceId, persistence.SequenceNr, replay.FromOffset + index); + Debug.WriteLine($"[DIAG] Sent ReplayedTaggedMessage for {persistence.PersistenceId}, seq {persistence.SequenceNr}, offset {replay.FromOffset + index}"); index++; } - log.Debug("[DIAG] ReplayTaggedMessagesAsync completed, returning highestSequenceNr={0}", count - 1); + Debug.WriteLine($"[DIAG] ReplayTaggedMessagesAsync completed, returning highestSequenceNr={count - 1}"); return Task.FromResult(count - 1); } From 77c572eb9b324a202a60da634adbeb82e3463b9c Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 9 Oct 2025 13:48:00 -0500 Subject: [PATCH 06/12] Remove ConfigureAwait(false) from AsyncWriteJournal.ExecuteBatch Removed ConfigureAwait(false) to ensure async continuations run on the actor's dispatcher thread rather than arbitrary thread pool threads. Rationale: ConfigureAwait(false) causes the continuation after await to run on any available thread pool thread. In the actor model, this can cause issues: 1. Loss of synchronization context - no guarantee about memory visibility of changes made in WriteMessagesAsync 2. Timing/fairness issues under high contention when multiple actors compete for the same lock on different thread pool threads with different priorities 3. Potential for Context access violations if any code in the chain tries to access actor state By removing ConfigureAwait(false), the continuation will attempt to resume on the actor's original dispatcher thread, maintaining proper actor threading semantics and improving determinism under load. This may have been contributing to the intermittent test failures in InMemoryEventsByTagSpec even before diagnostic logging was added. --- src/core/Akka.Persistence/Journal/AsyncWriteJournal.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/Akka.Persistence/Journal/AsyncWriteJournal.cs b/src/core/Akka.Persistence/Journal/AsyncWriteJournal.cs index 4939d2cd342..f195ae4fdc4 100644 --- a/src/core/Akka.Persistence/Journal/AsyncWriteJournal.cs +++ b/src/core/Akka.Persistence/Journal/AsyncWriteJournal.cs @@ -391,8 +391,10 @@ private async Task ExecuteBatch(WriteMessages message, int atomicWriteCount, IAc // try in case AsyncWriteMessages throws try { + // NOTE: Not using ConfigureAwait(false) to ensure continuation runs on actor's dispatcher thread + // This ensures proper synchronization context and avoids timing issues with lock contention var writeResult = - await _breaker.WithCircuitBreaker((prepared, awj: this), (state, ct) => state.awj.WriteMessagesAsync(state.prepared, ct)).ConfigureAwait(false); + await _breaker.WithCircuitBreaker((prepared, awj: this), (state, ct) => state.awj.WriteMessagesAsync(state.prepared, ct)); ProcessResults(writeResult, atomicWriteCount, message, _resequencer, resequencerCounter, self); } From 0a749efb881b78cd27571506f493a8fb72836f28 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 9 Oct 2025 13:54:05 -0500 Subject: [PATCH 07/12] Fix ambiguous Debug reference by fully qualifying System.Diagnostics.Debug --- .../Akka.Persistence/Journal/MemoryJournal.cs | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/core/Akka.Persistence/Journal/MemoryJournal.cs b/src/core/Akka.Persistence/Journal/MemoryJournal.cs index 61d1a771260..b038f8a54fc 100644 --- a/src/core/Akka.Persistence/Journal/MemoryJournal.cs +++ b/src/core/Akka.Persistence/Journal/MemoryJournal.cs @@ -16,10 +16,6 @@ using Akka.Event; using Akka.Util.Internal; -#if DEBUG -using System.IO; -#endif - namespace Akka.Persistence.Journal { /// @@ -53,14 +49,14 @@ protected override Task> WriteMessagesAsync(IEnumerabl // IMPORTANT: Cannot use Context.GetLogger() here - this method is called from async continuation // on thread pool thread (not actor thread) due to ConfigureAwait(false) in ExecuteBatch. - // Using Debug.WriteLine instead for diagnostic logging to avoid potential deadlock. - Debug.WriteLine($"[DIAG] WriteMessagesAsync called on thread {threadId}, attempting to acquire lock"); + // Using System.Diagnostics.Debug.WriteLine instead for diagnostic logging to avoid potential deadlock. + System.Diagnostics.Debug.WriteLine($"[DIAG] WriteMessagesAsync called on thread {threadId}, attempting to acquire lock"); lock (Lock) { var lockAcquiredTime = Stopwatch.GetTimestamp(); var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; - Debug.WriteLine($"[DIAG] Lock acquired after {waitTimeMs:F2}ms on thread {threadId}"); + System.Diagnostics.Debug.WriteLine($"[DIAG] Lock acquired after {waitTimeMs:F2}ms on thread {threadId}"); foreach (var w in messages) { @@ -78,13 +74,13 @@ protected override Task> WriteMessagesAsync(IEnumerabl } pidEvents.Add(persistentRepresentation); - Debug.WriteLine($"[DIAG] Wrote event for {persistentRepresentation.PersistenceId}, seq {persistentRepresentation.SequenceNr}, total events in log: {EventLog.Count}"); + System.Diagnostics.Debug.WriteLine($"[DIAG] Wrote event for {persistentRepresentation.PersistenceId}, seq {persistentRepresentation.SequenceNr}, total events in log: {EventLog.Count}"); } } var lockReleasedTime = Stopwatch.GetTimestamp(); var holdTimeMs = (lockReleasedTime - lockAcquiredTime) * 1000.0 / Stopwatch.Frequency; - Debug.WriteLine($"[DIAG] Lock released after holding for {holdTimeMs:F2}ms on thread {threadId}"); + System.Diagnostics.Debug.WriteLine($"[DIAG] Lock released after holding for {holdTimeMs:F2}ms on thread {threadId}"); } return Task.FromResult>(null); @@ -289,8 +285,8 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) var waitStartTime = Stopwatch.GetTimestamp(); var threadId = Environment.CurrentManagedThreadId; - // Using Debug.WriteLine to avoid Context access from potentially wrong thread - Debug.WriteLine($"[DIAG] ReplayTaggedMessagesAsync starting for tag '{replay.Tag}' on thread {threadId}, attempting to acquire lock"); + // Using System.Diagnostics.Debug.WriteLine to avoid Context access from potentially wrong thread + System.Diagnostics.Debug.WriteLine($"[DIAG] ReplayTaggedMessagesAsync starting for tag '{replay.Tag}' on thread {threadId}, attempting to acquire lock"); IPersistentRepresentation[] snapshot; int count; @@ -299,7 +295,7 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) { var lockAcquiredTime = Stopwatch.GetTimestamp(); var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; - Debug.WriteLine($"[DIAG] ReplayTaggedMessages lock acquired after {waitTimeMs:F2}ms on thread {threadId}"); + System.Diagnostics.Debug.WriteLine($"[DIAG] ReplayTaggedMessages lock acquired after {waitTimeMs:F2}ms on thread {threadId}"); // Scan for events with matching tag snapshot = EventLog @@ -310,7 +306,7 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) count = EventLog.Count(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)); - Debug.WriteLine($"[DIAG] Found {snapshot.Length} events matching tag '{replay.Tag}', total tagged events: {count}, EventLog size: {EventLog.Count}"); + System.Diagnostics.Debug.WriteLine($"[DIAG] Found {snapshot.Length} events matching tag '{replay.Tag}', total tagged events: {count}, EventLog size: {EventLog.Count}"); } // Send messages outside the lock to avoid potential deadlocks @@ -318,11 +314,11 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) foreach (var persistence in snapshot) { replay.ReplyTo.Tell(new ReplayedTaggedMessage(persistence, replay.Tag, replay.FromOffset + index), ActorRefs.NoSender); - Debug.WriteLine($"[DIAG] Sent ReplayedTaggedMessage for {persistence.PersistenceId}, seq {persistence.SequenceNr}, offset {replay.FromOffset + index}"); + System.Diagnostics.Debug.WriteLine($"[DIAG] Sent ReplayedTaggedMessage for {persistence.PersistenceId}, seq {persistence.SequenceNr}, offset {replay.FromOffset + index}"); index++; } - Debug.WriteLine($"[DIAG] ReplayTaggedMessagesAsync completed, returning highestSequenceNr={count - 1}"); + System.Diagnostics.Debug.WriteLine($"[DIAG] ReplayTaggedMessagesAsync completed, returning highestSequenceNr={count - 1}"); return Task.FromResult(count - 1); } From 8083e1333767e9e2878ebcc1e50edcb59dbfe8fe Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Thu, 9 Oct 2025 16:53:24 -0500 Subject: [PATCH 08/12] Replace Debug.WriteLine with cached logger for diagnostic logging Replaced System.Diagnostics.Debug.WriteLine calls with proper Akka.NET logging using a cached ILoggingAdapter instance. The cached logger is safe to use from thread pool threads after initial lazy initialization on the actor thread. This is a cleaner approach than Debug.WriteLine and ensures diagnostic logs appear in the normal Akka.NET logging infrastructure where they can be controlled via log level configuration. --- .../Akka.Persistence/Journal/MemoryJournal.cs | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/core/Akka.Persistence/Journal/MemoryJournal.cs b/src/core/Akka.Persistence/Journal/MemoryJournal.cs index b038f8a54fc..724db6cb0db 100644 --- a/src/core/Akka.Persistence/Journal/MemoryJournal.cs +++ b/src/core/Akka.Persistence/Journal/MemoryJournal.cs @@ -37,6 +37,13 @@ public class MemoryJournal : AsyncWriteJournal private readonly object _lock = new(); private readonly Dictionary _deletedTo = new(); + private ILoggingAdapter _log; + + /// + /// Cached logger instance that's safe to use from any thread after first initialization on actor thread. + /// + protected ILoggingAdapter Log => _log ??= Context.GetLogger(); + protected virtual List EventLog => _eventLog; protected virtual Dictionary> EventsByPersistenceId => _eventsByPersistenceId; protected virtual object Lock => _lock; @@ -47,16 +54,14 @@ protected override Task> WriteMessagesAsync(IEnumerabl var waitStartTime = Stopwatch.GetTimestamp(); var threadId = Environment.CurrentManagedThreadId; - // IMPORTANT: Cannot use Context.GetLogger() here - this method is called from async continuation - // on thread pool thread (not actor thread) due to ConfigureAwait(false) in ExecuteBatch. - // Using System.Diagnostics.Debug.WriteLine instead for diagnostic logging to avoid potential deadlock. - System.Diagnostics.Debug.WriteLine($"[DIAG] WriteMessagesAsync called on thread {threadId}, attempting to acquire lock"); + // Safe to use cached logger from thread pool thread since it was initialized on actor thread + Log.Debug("[DIAG] WriteMessagesAsync called on thread {0}, attempting to acquire lock", threadId); lock (Lock) { var lockAcquiredTime = Stopwatch.GetTimestamp(); var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; - System.Diagnostics.Debug.WriteLine($"[DIAG] Lock acquired after {waitTimeMs:F2}ms on thread {threadId}"); + Log.Debug("[DIAG] Lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); foreach (var w in messages) { @@ -74,13 +79,16 @@ protected override Task> WriteMessagesAsync(IEnumerabl } pidEvents.Add(persistentRepresentation); - System.Diagnostics.Debug.WriteLine($"[DIAG] Wrote event for {persistentRepresentation.PersistenceId}, seq {persistentRepresentation.SequenceNr}, total events in log: {EventLog.Count}"); + Log.Debug("[DIAG] Wrote event for {0}, seq {1}, total events in log: {2}", + persistentRepresentation.PersistenceId, + persistentRepresentation.SequenceNr, + EventLog.Count); } } var lockReleasedTime = Stopwatch.GetTimestamp(); var holdTimeMs = (lockReleasedTime - lockAcquiredTime) * 1000.0 / Stopwatch.Frequency; - System.Diagnostics.Debug.WriteLine($"[DIAG] Lock released after holding for {holdTimeMs:F2}ms on thread {threadId}"); + Log.Debug("[DIAG] Lock released after holding for {0:F2}ms on thread {1}", holdTimeMs, threadId); } return Task.FromResult>(null); @@ -285,8 +293,8 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) var waitStartTime = Stopwatch.GetTimestamp(); var threadId = Environment.CurrentManagedThreadId; - // Using System.Diagnostics.Debug.WriteLine to avoid Context access from potentially wrong thread - System.Diagnostics.Debug.WriteLine($"[DIAG] ReplayTaggedMessagesAsync starting for tag '{replay.Tag}' on thread {threadId}, attempting to acquire lock"); + // Safe to use cached logger from thread pool thread since it was initialized on actor thread + Log.Debug("[DIAG] ReplayTaggedMessagesAsync starting for tag '{0}' on thread {1}, attempting to acquire lock", replay.Tag, threadId); IPersistentRepresentation[] snapshot; int count; @@ -295,7 +303,7 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) { var lockAcquiredTime = Stopwatch.GetTimestamp(); var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; - System.Diagnostics.Debug.WriteLine($"[DIAG] ReplayTaggedMessages lock acquired after {waitTimeMs:F2}ms on thread {threadId}"); + Log.Debug("[DIAG] ReplayTaggedMessages lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); // Scan for events with matching tag snapshot = EventLog @@ -306,7 +314,8 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) count = EventLog.Count(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)); - System.Diagnostics.Debug.WriteLine($"[DIAG] Found {snapshot.Length} events matching tag '{replay.Tag}', total tagged events: {count}, EventLog size: {EventLog.Count}"); + Log.Debug("[DIAG] Found {0} events matching tag '{1}', total tagged events: {2}, EventLog size: {3}", + snapshot.Length, replay.Tag, count, EventLog.Count); } // Send messages outside the lock to avoid potential deadlocks @@ -314,11 +323,12 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) foreach (var persistence in snapshot) { replay.ReplyTo.Tell(new ReplayedTaggedMessage(persistence, replay.Tag, replay.FromOffset + index), ActorRefs.NoSender); - System.Diagnostics.Debug.WriteLine($"[DIAG] Sent ReplayedTaggedMessage for {persistence.PersistenceId}, seq {persistence.SequenceNr}, offset {replay.FromOffset + index}"); + Log.Debug("[DIAG] Sent ReplayedTaggedMessage for {0}, seq {1}, offset {2}", + persistence.PersistenceId, persistence.SequenceNr, replay.FromOffset + index); index++; } - System.Diagnostics.Debug.WriteLine($"[DIAG] ReplayTaggedMessagesAsync completed, returning highestSequenceNr={count - 1}"); + Log.Debug("[DIAG] ReplayTaggedMessagesAsync completed, returning highestSequenceNr={0}", count - 1); return Task.FromResult(count - 1); } From 653d1b480f853eedfa5be275e9a0a6c3c425dd0b Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Mon, 13 Oct 2025 17:02:17 -0500 Subject: [PATCH 09/12] remove public API changes --- .../Akka.Persistence/Journal/MemoryJournal.cs | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/core/Akka.Persistence/Journal/MemoryJournal.cs b/src/core/Akka.Persistence/Journal/MemoryJournal.cs index 724db6cb0db..75eacc5af70 100644 --- a/src/core/Akka.Persistence/Journal/MemoryJournal.cs +++ b/src/core/Akka.Persistence/Journal/MemoryJournal.cs @@ -37,12 +37,7 @@ public class MemoryJournal : AsyncWriteJournal private readonly object _lock = new(); private readonly Dictionary _deletedTo = new(); - private ILoggingAdapter _log; - - /// - /// Cached logger instance that's safe to use from any thread after first initialization on actor thread. - /// - protected ILoggingAdapter Log => _log ??= Context.GetLogger(); + private ILoggingAdapter _log = Context.GetLogger(); protected virtual List EventLog => _eventLog; protected virtual Dictionary> EventsByPersistenceId => _eventsByPersistenceId; @@ -55,13 +50,13 @@ protected override Task> WriteMessagesAsync(IEnumerabl var threadId = Environment.CurrentManagedThreadId; // Safe to use cached logger from thread pool thread since it was initialized on actor thread - Log.Debug("[DIAG] WriteMessagesAsync called on thread {0}, attempting to acquire lock", threadId); + _log.Debug("[DIAG] WriteMessagesAsync called on thread {0}, attempting to acquire lock", threadId); lock (Lock) { var lockAcquiredTime = Stopwatch.GetTimestamp(); var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; - Log.Debug("[DIAG] Lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); + _log.Debug("[DIAG] Lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); foreach (var w in messages) { @@ -79,7 +74,7 @@ protected override Task> WriteMessagesAsync(IEnumerabl } pidEvents.Add(persistentRepresentation); - Log.Debug("[DIAG] Wrote event for {0}, seq {1}, total events in log: {2}", + _log.Debug("[DIAG] Wrote event for {0}, seq {1}, total events in log: {2}", persistentRepresentation.PersistenceId, persistentRepresentation.SequenceNr, EventLog.Count); @@ -88,7 +83,7 @@ protected override Task> WriteMessagesAsync(IEnumerabl var lockReleasedTime = Stopwatch.GetTimestamp(); var holdTimeMs = (lockReleasedTime - lockAcquiredTime) * 1000.0 / Stopwatch.Frequency; - Log.Debug("[DIAG] Lock released after holding for {0:F2}ms on thread {1}", holdTimeMs, threadId); + _log.Debug("[DIAG] Lock released after holding for {0:F2}ms on thread {1}", holdTimeMs, threadId); } return Task.FromResult>(null); @@ -294,7 +289,7 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) var threadId = Environment.CurrentManagedThreadId; // Safe to use cached logger from thread pool thread since it was initialized on actor thread - Log.Debug("[DIAG] ReplayTaggedMessagesAsync starting for tag '{0}' on thread {1}, attempting to acquire lock", replay.Tag, threadId); + _log.Debug("[DIAG] ReplayTaggedMessagesAsync starting for tag '{0}' on thread {1}, attempting to acquire lock", replay.Tag, threadId); IPersistentRepresentation[] snapshot; int count; @@ -303,7 +298,7 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) { var lockAcquiredTime = Stopwatch.GetTimestamp(); var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; - Log.Debug("[DIAG] ReplayTaggedMessages lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); + _log.Debug("[DIAG] ReplayTaggedMessages lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); // Scan for events with matching tag snapshot = EventLog @@ -314,7 +309,7 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) count = EventLog.Count(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)); - Log.Debug("[DIAG] Found {0} events matching tag '{1}', total tagged events: {2}, EventLog size: {3}", + _log.Debug("[DIAG] Found {0} events matching tag '{1}', total tagged events: {2}, EventLog size: {3}", snapshot.Length, replay.Tag, count, EventLog.Count); } @@ -323,12 +318,12 @@ private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) foreach (var persistence in snapshot) { replay.ReplyTo.Tell(new ReplayedTaggedMessage(persistence, replay.Tag, replay.FromOffset + index), ActorRefs.NoSender); - Log.Debug("[DIAG] Sent ReplayedTaggedMessage for {0}, seq {1}, offset {2}", + _log.Debug("[DIAG] Sent ReplayedTaggedMessage for {0}, seq {1}, offset {2}", persistence.PersistenceId, persistence.SequenceNr, replay.FromOffset + index); index++; } - Log.Debug("[DIAG] ReplayTaggedMessagesAsync completed, returning highestSequenceNr={0}", count - 1); + _log.Debug("[DIAG] ReplayTaggedMessagesAsync completed, returning highestSequenceNr={0}", count - 1); return Task.FromResult(count - 1); } From 08555ae657bb9c3d55f24718eba7ffd948d8eb8d Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Tue, 14 Oct 2025 07:56:03 -0500 Subject: [PATCH 10/12] Wrap MemoryJournal async methods in Task.Run to prevent thread starvation All async methods in MemoryJournal were doing synchronous work while blocking the calling thread. This could cause thread pool starvation in test environments where many actors are starting simultaneously. Changes: - WriteMessagesAsync: Wrapped in Task.Run - ReadHighestSequenceNrAsync: Wrapped in Task.Run - ReplayMessagesAsync: Wrapped in Task.Run - DeleteMessagesToAsync: Wrapped in Task.Run - ReplayTaggedMessagesAsync: Wrapped in Task.Run - ReplayAllEventsAsync: Wrapped in Task.Run All diagnostic logging preserved for troubleshooting. --- .../Akka.Persistence/Journal/MemoryJournal.cs | 260 +++++++++--------- 1 file changed, 137 insertions(+), 123 deletions(-) diff --git a/src/core/Akka.Persistence/Journal/MemoryJournal.cs b/src/core/Akka.Persistence/Journal/MemoryJournal.cs index 75eacc5af70..b9654f35e51 100644 --- a/src/core/Akka.Persistence/Journal/MemoryJournal.cs +++ b/src/core/Akka.Persistence/Journal/MemoryJournal.cs @@ -46,109 +46,117 @@ public class MemoryJournal : AsyncWriteJournal protected override Task> WriteMessagesAsync(IEnumerable messages, CancellationToken cancellationToken) { - var waitStartTime = Stopwatch.GetTimestamp(); - var threadId = Environment.CurrentManagedThreadId; - - // Safe to use cached logger from thread pool thread since it was initialized on actor thread - _log.Debug("[DIAG] WriteMessagesAsync called on thread {0}, attempting to acquire lock", threadId); - - lock (Lock) + return Task.Run(() => { - var lockAcquiredTime = Stopwatch.GetTimestamp(); - var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; - _log.Debug("[DIAG] Lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); + var waitStartTime = Stopwatch.GetTimestamp(); + var threadId = Environment.CurrentManagedThreadId; - foreach (var w in messages) - { - foreach (var p in (IEnumerable)w.Payload) - { - var persistentRepresentation = p.WithTimestamp(DateTime.UtcNow.Ticks); + // Safe to use cached logger from thread pool thread since it was initialized on actor thread + _log.Debug("[DIAG] WriteMessagesAsync called on thread {0}, attempting to acquire lock", threadId); - // Maintain both indexes on write - EventLog.Add(persistentRepresentation); + lock (Lock) + { + var lockAcquiredTime = Stopwatch.GetTimestamp(); + var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; + _log.Debug("[DIAG] Lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); - if (!EventsByPersistenceId.TryGetValue(persistentRepresentation.PersistenceId, out var pidEvents)) + foreach (var w in messages) + { + foreach (var p in (IEnumerable)w.Payload) { - pidEvents = new List(); - EventsByPersistenceId[persistentRepresentation.PersistenceId] = pidEvents; + var persistentRepresentation = p.WithTimestamp(DateTime.UtcNow.Ticks); + + // Maintain both indexes on write + EventLog.Add(persistentRepresentation); + + if (!EventsByPersistenceId.TryGetValue(persistentRepresentation.PersistenceId, out var pidEvents)) + { + pidEvents = new List(); + EventsByPersistenceId[persistentRepresentation.PersistenceId] = pidEvents; + } + pidEvents.Add(persistentRepresentation); + + _log.Debug("[DIAG] Wrote event for {0}, seq {1}, total events in log: {2}", + persistentRepresentation.PersistenceId, + persistentRepresentation.SequenceNr, + EventLog.Count); } - pidEvents.Add(persistentRepresentation); - - _log.Debug("[DIAG] Wrote event for {0}, seq {1}, total events in log: {2}", - persistentRepresentation.PersistenceId, - persistentRepresentation.SequenceNr, - EventLog.Count); } - } - var lockReleasedTime = Stopwatch.GetTimestamp(); - var holdTimeMs = (lockReleasedTime - lockAcquiredTime) * 1000.0 / Stopwatch.Frequency; - _log.Debug("[DIAG] Lock released after holding for {0:F2}ms on thread {1}", holdTimeMs, threadId); - } + var lockReleasedTime = Stopwatch.GetTimestamp(); + var holdTimeMs = (lockReleasedTime - lockAcquiredTime) * 1000.0 / Stopwatch.Frequency; + _log.Debug("[DIAG] Lock released after holding for {0:F2}ms on thread {1}", holdTimeMs, threadId); + } - return Task.FromResult>(null); + return (IImmutableList)null; + }, cancellationToken); } public override Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr, CancellationToken cancellationToken) { - lock (Lock) + return Task.Run(() => { - // Use index for O(1) lookup instead of O(n) scan - if (!EventsByPersistenceId.TryGetValue(persistenceId, out var events) || events.Count == 0) - return Task.FromResult(0L); + lock (Lock) + { + // Use index for O(1) lookup instead of O(n) scan + if (!EventsByPersistenceId.TryGetValue(persistenceId, out var events) || events.Count == 0) + return 0L; - var highest = events[events.Count - 1].SequenceNr; + var highest = events[events.Count - 1].SequenceNr; - // Return actual highest sequence number from journal - // Deletion is logical only - events remain in index - return Task.FromResult(highest); - } + // Return actual highest sequence number from journal + // Deletion is logical only - events remain in index + return highest; + } + }, cancellationToken); } public override Task ReplayMessagesAsync(IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, Action recoveryCallback) { - IPersistentRepresentation[] messages; - - lock (Lock) + return Task.Run(() => { - // Use index for O(events_for_entity) instead of O(total_events) - if (!EventsByPersistenceId.TryGetValue(persistenceId, out var pidEvents)) + IPersistentRepresentation[] messages; + + lock (Lock) { - messages = Array.Empty(); + // Use index for O(events_for_entity) instead of O(total_events) + if (!EventsByPersistenceId.TryGetValue(persistenceId, out var pidEvents)) + { + messages = Array.Empty(); + } + else + { + var deletedToSeq = DeletedTo.GetValueOrDefault(persistenceId, 0L); + + messages = pidEvents + .Where(e => e.SequenceNr > deletedToSeq // Skip deleted messages + && e.SequenceNr >= fromSequenceNr + && e.SequenceNr <= toSequenceNr) + .Take(max > int.MaxValue ? int.MaxValue : (int)max) + .ToArray(); + } } - else - { - var deletedToSeq = DeletedTo.GetValueOrDefault(persistenceId, 0L); - messages = pidEvents - .Where(e => e.SequenceNr > deletedToSeq // Skip deleted messages - && e.SequenceNr >= fromSequenceNr - && e.SequenceNr <= toSequenceNr) - .Take(max > int.MaxValue ? int.MaxValue : (int)max) - .ToArray(); + // Execute callbacks outside the lock to avoid potential deadlocks + foreach (var message in messages) + { + recoveryCallback(message); } - } - - // Execute callbacks outside the lock to avoid potential deadlocks - foreach (var message in messages) - { - recoveryCallback(message); - } - - return Task.CompletedTask; + }); } protected override Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr, CancellationToken cancellationToken) { - lock (Lock) + return Task.Run(() => { - // Track deletion marker instead of actually removing events - // This is simpler and matches the semantics (logical deletion) - DeletedTo[persistenceId] = toSequenceNr; - } - - return Task.CompletedTask; + lock (Lock) + { + // Track deletion marker instead of actually removing events + // This is simpler and matches the semantics (logical deletion) + DeletedTo[persistenceId] = toSequenceNr; + } + }, cancellationToken); } /// @@ -285,72 +293,78 @@ protected override bool ReceivePluginInternal(object message) /// private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) { - var waitStartTime = Stopwatch.GetTimestamp(); - var threadId = Environment.CurrentManagedThreadId; - - // Safe to use cached logger from thread pool thread since it was initialized on actor thread - _log.Debug("[DIAG] ReplayTaggedMessagesAsync starting for tag '{0}' on thread {1}, attempting to acquire lock", replay.Tag, threadId); + return Task.Run(() => + { + var waitStartTime = Stopwatch.GetTimestamp(); + var threadId = Environment.CurrentManagedThreadId; - IPersistentRepresentation[] snapshot; - int count; + // Safe to use cached logger from thread pool thread since it was initialized on actor thread + _log.Debug("[DIAG] ReplayTaggedMessagesAsync starting for tag '{0}' on thread {1}, attempting to acquire lock", replay.Tag, threadId); - lock (Lock) - { - var lockAcquiredTime = Stopwatch.GetTimestamp(); - var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; - _log.Debug("[DIAG] ReplayTaggedMessages lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); + IPersistentRepresentation[] snapshot; + int count; - // Scan for events with matching tag - snapshot = EventLog - .Where(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)) - .Skip(replay.FromOffset) - .Take(replay.Max) - .ToArray(); + lock (Lock) + { + var lockAcquiredTime = Stopwatch.GetTimestamp(); + var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; + _log.Debug("[DIAG] ReplayTaggedMessages lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); + + // Scan for events with matching tag + snapshot = EventLog + .Where(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)) + .Skip(replay.FromOffset) + .Take(replay.Max) + .ToArray(); - count = EventLog.Count(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)); + count = EventLog.Count(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)); - _log.Debug("[DIAG] Found {0} events matching tag '{1}', total tagged events: {2}, EventLog size: {3}", - snapshot.Length, replay.Tag, count, EventLog.Count); - } + _log.Debug("[DIAG] Found {0} events matching tag '{1}', total tagged events: {2}, EventLog size: {3}", + snapshot.Length, replay.Tag, count, EventLog.Count); + } - // Send messages outside the lock to avoid potential deadlocks - var index = 0; - foreach (var persistence in snapshot) - { - replay.ReplyTo.Tell(new ReplayedTaggedMessage(persistence, replay.Tag, replay.FromOffset + index), ActorRefs.NoSender); - _log.Debug("[DIAG] Sent ReplayedTaggedMessage for {0}, seq {1}, offset {2}", - persistence.PersistenceId, persistence.SequenceNr, replay.FromOffset + index); - index++; - } + // Send messages outside the lock to avoid potential deadlocks + var index = 0; + foreach (var persistence in snapshot) + { + replay.ReplyTo.Tell(new ReplayedTaggedMessage(persistence, replay.Tag, replay.FromOffset + index), ActorRefs.NoSender); + _log.Debug("[DIAG] Sent ReplayedTaggedMessage for {0}, seq {1}, offset {2}", + persistence.PersistenceId, persistence.SequenceNr, replay.FromOffset + index); + index++; + } - _log.Debug("[DIAG] ReplayTaggedMessagesAsync completed, returning highestSequenceNr={0}", count - 1); - return Task.FromResult(count - 1); + _log.Debug("[DIAG] ReplayTaggedMessagesAsync completed, returning highestSequenceNr={0}", count - 1); + return count - 1; + }); } private Task ReplayAllEventsAsync(ReplayAllEvents replay) { - IPersistentRepresentation[] snapshot; - int count; - - lock (Lock) + return Task.Run(() => { - snapshot = EventLog - .Skip(replay.FromOffset) - .Take((int)replay.Max) - .ToArray(); + IPersistentRepresentation[] snapshot; + int count; - count = EventLog.Count; - } + lock (Lock) + { + snapshot = EventLog + .Skip(replay.FromOffset) + .Take((int)replay.Max) + .ToArray(); - // Send messages outside the lock to avoid potential deadlocks - var index = 0; - foreach (var message in snapshot) - { - replay.ReplyTo.Tell(new ReplayedEvent(message, replay.FromOffset + index), ActorRefs.NoSender); - index++; - } + count = EventLog.Count; + } + + // Send messages outside the lock to avoid potential deadlocks + var index = 0; + foreach (var message in snapshot) + { + replay.ReplyTo.Tell(new ReplayedEvent(message, replay.FromOffset + index), ActorRefs.NoSender); + index++; + } - return Task.FromResult(count - 1); + return count - 1; + }); } #region QueryAPI From 692804d82cf1c7e72812aa2097e16b986afaf120 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Mon, 1 Dec 2025 21:51:47 -0600 Subject: [PATCH 11/12] fix(persistence): resolve MemoryJournal writer starvation with copy-on-read pattern Root cause: Locks were held during Tell() message operations, causing writer starvation when query publishers continuously polled the journal. Solution: Copy-on-read pattern - snapshot data under lock (fast ~1-3ms), release lock, then send messages outside the lock. Changes: - Remove Task.Run() wrappers that just moved blocking to thread pool - Remove diagnostic logging added during investigation - Revert test log level from DEBUG to INFO --- .../InMemoryEventsByTagSpec.cs | 2 +- .../Akka.Persistence.TCK/Query/TestActor.cs | 17 +- .../Akka.Persistence/Journal/MemoryJournal.cs | 243 +++++++----------- 3 files changed, 95 insertions(+), 167 deletions(-) diff --git a/src/contrib/persistence/Akka.Persistence.Query.InMemory.Tests/InMemoryEventsByTagSpec.cs b/src/contrib/persistence/Akka.Persistence.Query.InMemory.Tests/InMemoryEventsByTagSpec.cs index 613635f078a..b85629be893 100644 --- a/src/contrib/persistence/Akka.Persistence.Query.InMemory.Tests/InMemoryEventsByTagSpec.cs +++ b/src/contrib/persistence/Akka.Persistence.Query.InMemory.Tests/InMemoryEventsByTagSpec.cs @@ -14,7 +14,7 @@ namespace Akka.Persistence.Query.InMemory.Tests public class InMemoryEventsByTagSpec : EventsByTagSpec { private static Config Config() => ConfigurationFactory.ParseString(@" - akka.loglevel = DEBUG + akka.loglevel = INFO akka.persistence.journal.inmem { event-adapters { color-tagger = ""Akka.Persistence.TCK.Query.ColorFruitTagger, Akka.Persistence.TCK"" diff --git a/src/core/Akka.Persistence.TCK/Query/TestActor.cs b/src/core/Akka.Persistence.TCK/Query/TestActor.cs index 2144b157e28..f583bea681d 100644 --- a/src/core/Akka.Persistence.TCK/Query/TestActor.cs +++ b/src/core/Akka.Persistence.TCK/Query/TestActor.cs @@ -8,7 +8,6 @@ using System.Collections.Immutable; using System.Linq; using Akka.Actor; -using Akka.Event; using Akka.Persistence.Journal; namespace Akka.Persistence.TCK.Query @@ -34,20 +33,12 @@ public TestActor(string persistenceId) public override string PersistenceId { get; } - protected override void PreStart() - { - Log.Debug("[DIAG-ACTOR] TestActor {0} PreStart called", PersistenceId); - base.PreStart(); - } - protected override void OnRecover(object message) { - Log.Debug("[DIAG-ACTOR] TestActor {0} OnRecover: {1}", PersistenceId, message); } protected override void OnCommand(object message) { - Log.Debug("[DIAG-ACTOR] TestActor {0} OnCommand received: {1}", PersistenceId, message); switch (message) { case DeleteCommand delete: @@ -55,14 +46,8 @@ protected override void OnCommand(object message) Become(WhileDeleting(Sender)); // need to wait for delete ACK to return break; case string cmd: - Log.Debug("[DIAG-ACTOR] TestActor {0} calling Persist for: {1}", PersistenceId, cmd); var sender = Sender; - Persist(cmd, e => - { - Log.Debug("[DIAG-ACTOR] TestActor {0} Persist callback executing for: {1}", PersistenceId, e); - sender.Tell($"{e}-done"); - }); - Log.Debug("[DIAG-ACTOR] TestActor {0} Persist call returned for: {1}", PersistenceId, cmd); + Persist(cmd, e => sender.Tell($"{e}-done")); break; } } diff --git a/src/core/Akka.Persistence/Journal/MemoryJournal.cs b/src/core/Akka.Persistence/Journal/MemoryJournal.cs index b9654f35e51..577359df4a2 100644 --- a/src/core/Akka.Persistence/Journal/MemoryJournal.cs +++ b/src/core/Akka.Persistence/Journal/MemoryJournal.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -37,8 +36,6 @@ public class MemoryJournal : AsyncWriteJournal private readonly object _lock = new(); private readonly Dictionary _deletedTo = new(); - private ILoggingAdapter _log = Context.GetLogger(); - protected virtual List EventLog => _eventLog; protected virtual Dictionary> EventsByPersistenceId => _eventsByPersistenceId; protected virtual object Lock => _lock; @@ -46,117 +43,90 @@ public class MemoryJournal : AsyncWriteJournal protected override Task> WriteMessagesAsync(IEnumerable messages, CancellationToken cancellationToken) { - return Task.Run(() => + lock (Lock) { - var waitStartTime = Stopwatch.GetTimestamp(); - var threadId = Environment.CurrentManagedThreadId; - - // Safe to use cached logger from thread pool thread since it was initialized on actor thread - _log.Debug("[DIAG] WriteMessagesAsync called on thread {0}, attempting to acquire lock", threadId); - - lock (Lock) + foreach (var w in messages) { - var lockAcquiredTime = Stopwatch.GetTimestamp(); - var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; - _log.Debug("[DIAG] Lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); - - foreach (var w in messages) + foreach (var p in (IEnumerable)w.Payload) { - foreach (var p in (IEnumerable)w.Payload) + var persistentRepresentation = p.WithTimestamp(DateTime.UtcNow.Ticks); + + // Maintain both indexes on write + EventLog.Add(persistentRepresentation); + + if (!EventsByPersistenceId.TryGetValue(persistentRepresentation.PersistenceId, out var pidEvents)) { - var persistentRepresentation = p.WithTimestamp(DateTime.UtcNow.Ticks); - - // Maintain both indexes on write - EventLog.Add(persistentRepresentation); - - if (!EventsByPersistenceId.TryGetValue(persistentRepresentation.PersistenceId, out var pidEvents)) - { - pidEvents = new List(); - EventsByPersistenceId[persistentRepresentation.PersistenceId] = pidEvents; - } - pidEvents.Add(persistentRepresentation); - - _log.Debug("[DIAG] Wrote event for {0}, seq {1}, total events in log: {2}", - persistentRepresentation.PersistenceId, - persistentRepresentation.SequenceNr, - EventLog.Count); + pidEvents = new List(); + EventsByPersistenceId[persistentRepresentation.PersistenceId] = pidEvents; } + pidEvents.Add(persistentRepresentation); } - - var lockReleasedTime = Stopwatch.GetTimestamp(); - var holdTimeMs = (lockReleasedTime - lockAcquiredTime) * 1000.0 / Stopwatch.Frequency; - _log.Debug("[DIAG] Lock released after holding for {0:F2}ms on thread {1}", holdTimeMs, threadId); } + } - return (IImmutableList)null; - }, cancellationToken); + return Task.FromResult>(null); } public override Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr, CancellationToken cancellationToken) { - return Task.Run(() => + lock (Lock) { - lock (Lock) - { - // Use index for O(1) lookup instead of O(n) scan - if (!EventsByPersistenceId.TryGetValue(persistenceId, out var events) || events.Count == 0) - return 0L; + // Use index for O(1) lookup instead of O(n) scan + if (!EventsByPersistenceId.TryGetValue(persistenceId, out var events) || events.Count == 0) + return Task.FromResult(0L); - var highest = events[events.Count - 1].SequenceNr; + var highest = events[events.Count - 1].SequenceNr; - // Return actual highest sequence number from journal - // Deletion is logical only - events remain in index - return highest; - } - }, cancellationToken); + // Return actual highest sequence number from journal + // Deletion is logical only - events remain in index + return Task.FromResult(highest); + } } public override Task ReplayMessagesAsync(IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, Action recoveryCallback) { - return Task.Run(() => - { - IPersistentRepresentation[] messages; + IPersistentRepresentation[] messages; - lock (Lock) + lock (Lock) + { + // Use index for O(events_for_entity) instead of O(total_events) + if (!EventsByPersistenceId.TryGetValue(persistenceId, out var pidEvents)) { - // Use index for O(events_for_entity) instead of O(total_events) - if (!EventsByPersistenceId.TryGetValue(persistenceId, out var pidEvents)) - { - messages = Array.Empty(); - } - else - { - var deletedToSeq = DeletedTo.GetValueOrDefault(persistenceId, 0L); - - messages = pidEvents - .Where(e => e.SequenceNr > deletedToSeq // Skip deleted messages - && e.SequenceNr >= fromSequenceNr - && e.SequenceNr <= toSequenceNr) - .Take(max > int.MaxValue ? int.MaxValue : (int)max) - .ToArray(); - } + messages = Array.Empty(); } - - // Execute callbacks outside the lock to avoid potential deadlocks - foreach (var message in messages) + else { - recoveryCallback(message); + var deletedToSeq = DeletedTo.GetValueOrDefault(persistenceId, 0L); + + messages = pidEvents + .Where(e => e.SequenceNr > deletedToSeq // Skip deleted messages + && e.SequenceNr >= fromSequenceNr + && e.SequenceNr <= toSequenceNr) + .Take(max > int.MaxValue ? int.MaxValue : (int)max) + .ToArray(); } - }); + } + + // Execute callbacks outside the lock to avoid potential deadlocks + foreach (var message in messages) + { + recoveryCallback(message); + } + + return Task.CompletedTask; } protected override Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr, CancellationToken cancellationToken) { - return Task.Run(() => + lock (Lock) { - lock (Lock) - { - // Track deletion marker instead of actually removing events - // This is simpler and matches the semantics (logical deletion) - DeletedTo[persistenceId] = toSequenceNr; - } - }, cancellationToken); + // Track deletion marker instead of actually removing events + // This is simpler and matches the semantics (logical deletion) + DeletedTo[persistenceId] = toSequenceNr; + } + + return Task.CompletedTask; } /// @@ -249,21 +219,16 @@ protected override bool ReceivePluginInternal(object message) switch (message) { case SelectCurrentPersistenceIds request: - Context.GetLogger().Debug("[DIAG] Received SelectCurrentPersistenceIds from {0}", request.ReplyTo); SelectAllPersistenceIdsAsync(request.Offset) .PipeTo(request.ReplyTo, success: result => new CurrentPersistenceIds(result.Item1, result.LastOrdering)); return true; case ReplayTaggedMessages replay: - Context.GetLogger().Debug("[DIAG] Received ReplayTaggedMessages for tag '{0}', fromOffset={1}, max={2}, toOffset={3}, replyTo={4}", - replay.Tag, replay.FromOffset, replay.Max, replay.ToOffset, replay.ReplyTo); ReplayTaggedMessagesAsync(replay) .PipeTo(replay.ReplyTo, success: h => new ReplayTaggedMessagesSuccess(h), failure: e => new ReplayMessagesFailure(e)); return true; case ReplayAllEvents replay: - Context.GetLogger().Debug("[DIAG] Received ReplayAllEvents fromOffset={0}, max={1}, replyTo={2}", - replay.FromOffset, replay.Max, replay.ReplyTo); ReplayAllEventsAsync(replay) .PipeTo(replay.ReplyTo, success: h => new EventReplaySuccess(h), failure: e => new EventReplayFailure(e)); @@ -293,78 +258,56 @@ protected override bool ReceivePluginInternal(object message) /// private Task ReplayTaggedMessagesAsync(ReplayTaggedMessages replay) { - return Task.Run(() => - { - var waitStartTime = Stopwatch.GetTimestamp(); - var threadId = Environment.CurrentManagedThreadId; - - // Safe to use cached logger from thread pool thread since it was initialized on actor thread - _log.Debug("[DIAG] ReplayTaggedMessagesAsync starting for tag '{0}' on thread {1}, attempting to acquire lock", replay.Tag, threadId); - - IPersistentRepresentation[] snapshot; - int count; - - lock (Lock) - { - var lockAcquiredTime = Stopwatch.GetTimestamp(); - var waitTimeMs = (lockAcquiredTime - waitStartTime) * 1000.0 / Stopwatch.Frequency; - _log.Debug("[DIAG] ReplayTaggedMessages lock acquired after {0:F2}ms on thread {1}", waitTimeMs, threadId); - - // Scan for events with matching tag - snapshot = EventLog - .Where(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)) - .Skip(replay.FromOffset) - .Take(replay.Max) - .ToArray(); - - count = EventLog.Count(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)); + IPersistentRepresentation[] snapshot; + int count; - _log.Debug("[DIAG] Found {0} events matching tag '{1}', total tagged events: {2}, EventLog size: {3}", - snapshot.Length, replay.Tag, count, EventLog.Count); - } + lock (Lock) + { + // Scan for events with matching tag + snapshot = EventLog + .Where(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)) + .Skip(replay.FromOffset) + .Take(replay.Max) + .ToArray(); + + count = EventLog.Count(e => e.Payload is Tagged tagged && tagged.Tags.Contains(replay.Tag)); + } - // Send messages outside the lock to avoid potential deadlocks - var index = 0; - foreach (var persistence in snapshot) - { - replay.ReplyTo.Tell(new ReplayedTaggedMessage(persistence, replay.Tag, replay.FromOffset + index), ActorRefs.NoSender); - _log.Debug("[DIAG] Sent ReplayedTaggedMessage for {0}, seq {1}, offset {2}", - persistence.PersistenceId, persistence.SequenceNr, replay.FromOffset + index); - index++; - } + // Send messages outside the lock to avoid potential deadlocks + var index = 0; + foreach (var persistence in snapshot) + { + replay.ReplyTo.Tell(new ReplayedTaggedMessage(persistence, replay.Tag, replay.FromOffset + index), ActorRefs.NoSender); + index++; + } - _log.Debug("[DIAG] ReplayTaggedMessagesAsync completed, returning highestSequenceNr={0}", count - 1); - return count - 1; - }); + return Task.FromResult(count - 1); } private Task ReplayAllEventsAsync(ReplayAllEvents replay) { - return Task.Run(() => - { - IPersistentRepresentation[] snapshot; - int count; + IPersistentRepresentation[] snapshot; + int count; - lock (Lock) - { - snapshot = EventLog - .Skip(replay.FromOffset) - .Take((int)replay.Max) - .ToArray(); + lock (Lock) + { + snapshot = EventLog + .Skip(replay.FromOffset) + .Take((int)replay.Max) + .ToArray(); - count = EventLog.Count; - } + count = EventLog.Count; + } - // Send messages outside the lock to avoid potential deadlocks - var index = 0; - foreach (var message in snapshot) - { - replay.ReplyTo.Tell(new ReplayedEvent(message, replay.FromOffset + index), ActorRefs.NoSender); - index++; - } + // Send messages outside the lock to avoid potential deadlocks + var index = 0; + foreach (var message in snapshot) + { + replay.ReplyTo.Tell(new ReplayedEvent(message, replay.FromOffset + index), ActorRefs.NoSender); + index++; + } - return count - 1; - }); + return Task.FromResult(count - 1); } #region QueryAPI From 2c9bfd3e8f9503a0fbf99c6297ceb2988b7b0ccb Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Mon, 1 Dec 2025 22:13:52 -0600 Subject: [PATCH 12/12] fix: add missing Akka.Event using for ILoggingAdapter extension methods The Log.Error() call in TestActor requires the Akka.Event namespace for the ILoggingAdapter extension methods. --- src/core/Akka.Persistence.TCK/Query/TestActor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/Akka.Persistence.TCK/Query/TestActor.cs b/src/core/Akka.Persistence.TCK/Query/TestActor.cs index f583bea681d..336f4ec72e2 100644 --- a/src/core/Akka.Persistence.TCK/Query/TestActor.cs +++ b/src/core/Akka.Persistence.TCK/Query/TestActor.cs @@ -8,6 +8,7 @@ using System.Collections.Immutable; using System.Linq; using Akka.Actor; +using Akka.Event; using Akka.Persistence.Journal; namespace Akka.Persistence.TCK.Query