From f9b3f2c246ee44a306cfb0f31936e7a8a77ac413 Mon Sep 17 00:00:00 2001 From: Danthar Date: Tue, 8 Apr 2025 13:25:17 +0200 Subject: [PATCH 01/12] some comment corrections and persistence config extension --- .../Akka.Persistence/Journal/WriteJournal.cs | 13 ++++++----- src/core/Akka.Persistence/persistence.conf | 10 ++++++-- src/core/Akka/Actor/SupervisorStrategy.cs | 23 +++---------------- 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/core/Akka.Persistence/Journal/WriteJournal.cs b/src/core/Akka.Persistence/Journal/WriteJournal.cs index f0de1e73496..871e9047a27 100644 --- a/src/core/Akka.Persistence/Journal/WriteJournal.cs +++ b/src/core/Akka.Persistence/Journal/WriteJournal.cs @@ -14,7 +14,7 @@ namespace Akka.Persistence.Journal { /// - /// TBD + /// Base class for the journal persistence /// public abstract class WriteJournalBase : ActorBase { @@ -30,10 +30,11 @@ protected WriteJournalBase() } /// - /// TBD + /// Creates a sequence of write actions to be executed based on the given messages. + /// Applies any registered EventAdapters to the payloads. /// - /// TBD - /// TBD + /// list of messages to write + /// protected IEnumerable PreparePersistentBatch(IEnumerable resequenceables) { foreach (var resequenceable in resequenceables) @@ -53,7 +54,7 @@ protected IEnumerable PreparePersistentBatch(IEnumerable - /// INTERNAL API + /// Apply registered eventadapter to the data payload /// [InternalApi] protected IEnumerable AdaptFromJournal(IPersistentRepresentation representation) @@ -65,7 +66,7 @@ protected IEnumerable AdaptFromJournal(IPersistentRep } /// - /// INTERNAL API + /// Apply any registered eventadapter to the data payload /// protected IPersistentRepresentation AdaptToJournal(IPersistentRepresentation representation) { diff --git a/src/core/Akka.Persistence/persistence.conf b/src/core/Akka.Persistence/persistence.conf index 3c1273edca5..11c6d083a30 100644 --- a/src/core/Akka.Persistence/persistence.conf +++ b/src/core/Akka.Persistence/persistence.conf @@ -114,7 +114,10 @@ akka.persistence { # Dispatcher for message replay. replay-dispatcher = "akka.persistence.dispatchers.default-replay-dispatcher" - + # journal supervisor strategy used. + # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. + # by default it restarts the journal on crash + journal-supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" # Default serializer used as manifest serializer when applicable and payload serializer when no specific binding overrides are specified serializer = "json" @@ -173,7 +176,10 @@ akka.persistence { # Dispatcher for the plugin actor. plugin-dispatcher = "akka.persistence.dispatchers.default-plugin-dispatcher" - + # snapshot supervisor strategy used. + # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. + # by default it restarts the snapshot on crash + snapshot-supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" # Default serializer used as manifest serializer when applicable and payload serializer when no specific binding overrides are specified serializer = "json" circuit-breaker { diff --git a/src/core/Akka/Actor/SupervisorStrategy.cs b/src/core/Akka/Actor/SupervisorStrategy.cs index d5246feb48d..9bb5a4e5ca7 100644 --- a/src/core/Akka/Actor/SupervisorStrategy.cs +++ b/src/core/Akka/Actor/SupervisorStrategy.cs @@ -615,15 +615,7 @@ protected override Directive Handle(IActorRef child, Exception exception) return Decider.Decide(exception); } - /// - /// TBD - /// - /// TBD - /// TBD - /// TBD - /// TBD - /// TBD - /// TBD + /// public override void ProcessFailure(IActorContext context, bool restart, IActorRef child, Exception cause, ChildRestartStats stats, IReadOnlyCollection children) { if (children.Count > 0) @@ -645,12 +637,7 @@ public override void ProcessFailure(IActorContext context, bool restart, IActorR } } - /// - /// TBD - /// - /// TBD - /// TBD - /// TBD + /// public override void HandleChildTerminated(IActorContext actorContext, IActorRef child, IEnumerable children) { //Intentionally left blank @@ -998,14 +985,10 @@ public override int GetHashCode() } /// - /// TBD + /// Base configurator class used for configuring the guardian-supervisor-strategy /// public abstract class SupervisorStrategyConfigurator { - /// - /// TBD - /// - /// TBD public abstract SupervisorStrategy Create(); /// From 79c19f2a568644f28f8d3332685911f909bb7ca8 Mon Sep 17 00:00:00 2001 From: Danthar Date: Thu, 10 Apr 2025 20:32:40 +0200 Subject: [PATCH 02/12] SupervisorStrategyConfigurator integration/usage and comments --- src/core/Akka.Persistence/Persistence.cs | 13 ++++++++++--- src/core/Akka.Persistence/persistence.conf | 8 ++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/core/Akka.Persistence/Persistence.cs b/src/core/Akka.Persistence/Persistence.cs index d6fc71537b4..e030b8226e0 100644 --- a/src/core/Akka.Persistence/Persistence.cs +++ b/src/core/Akka.Persistence/Persistence.cs @@ -302,11 +302,18 @@ private static IActorRef CreatePlugin(ExtendedActorSystem system, string configP var pluginType = Type.GetType(pluginTypeName, true); var pluginDispatcherId = pluginConfig.GetString("plugin-dispatcher", null); object[] pluginActorArgs = pluginType.GetConstructor(new[] { typeof(Config) }) != null ? new object[] { pluginConfig } : null; - var pluginActorProps = new Props(pluginType, pluginActorArgs).WithDispatcher(pluginDispatcherId); - + + //todo wrap in backoffsupervisor ? + //todo provide docs with examples on how to use this and possibly make decisions on crashes. With explanations of intended scenarios + + //supervisor-strategy is defined by default in the fallback configs. So we always expect to get a value here even if the user has not explicitly defined anything + var configurator = SupervisorStrategyConfigurator.CreateConfigurator(pluginConfig.GetString("supervisor-strategy")); + + var pluginActorProps = new Props(pluginType, pluginActorArgs).WithDispatcher(pluginDispatcherId).WithSupervisorStrategy(configurator.Create()); + return system.SystemActorOf(pluginActorProps, pluginActorName); } - + private static EventAdapters CreateAdapters(ExtendedActorSystem system, string configPath) { var pluginConfig = system.Settings.Config.GetConfig(configPath); diff --git a/src/core/Akka.Persistence/persistence.conf b/src/core/Akka.Persistence/persistence.conf index 11c6d083a30..2b3c64e53a0 100644 --- a/src/core/Akka.Persistence/persistence.conf +++ b/src/core/Akka.Persistence/persistence.conf @@ -115,9 +115,9 @@ akka.persistence { # Dispatcher for message replay. replay-dispatcher = "akka.persistence.dispatchers.default-replay-dispatcher" # journal supervisor strategy used. - # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. + # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. And have a parameterless constructor # by default it restarts the journal on crash - journal-supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" + supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" # Default serializer used as manifest serializer when applicable and payload serializer when no specific binding overrides are specified serializer = "json" @@ -177,9 +177,9 @@ akka.persistence { # Dispatcher for the plugin actor. plugin-dispatcher = "akka.persistence.dispatchers.default-plugin-dispatcher" # snapshot supervisor strategy used. - # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. + # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. And have a parameterless constructor # by default it restarts the snapshot on crash - snapshot-supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" + supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" # Default serializer used as manifest serializer when applicable and payload serializer when no specific binding overrides are specified serializer = "json" circuit-breaker { From 6a002468e67d10ce9fc230e4590a0eee2e592efb Mon Sep 17 00:00:00 2001 From: Danthar Date: Thu, 10 Apr 2025 20:37:56 +0200 Subject: [PATCH 03/12] doc tweaks --- src/core/Akka.Persistence/persistence.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/Akka.Persistence/persistence.conf b/src/core/Akka.Persistence/persistence.conf index 2b3c64e53a0..af28d4800e7 100644 --- a/src/core/Akka.Persistence/persistence.conf +++ b/src/core/Akka.Persistence/persistence.conf @@ -118,6 +118,7 @@ akka.persistence { # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. And have a parameterless constructor # by default it restarts the journal on crash supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" + # Default serializer used as manifest serializer when applicable and payload serializer when no specific binding overrides are specified serializer = "json" @@ -176,10 +177,12 @@ akka.persistence { # Dispatcher for the plugin actor. plugin-dispatcher = "akka.persistence.dispatchers.default-plugin-dispatcher" + # snapshot supervisor strategy used. # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. And have a parameterless constructor # by default it restarts the snapshot on crash supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" + # Default serializer used as manifest serializer when applicable and payload serializer when no specific binding overrides are specified serializer = "json" circuit-breaker { From 7e2f1fd21b80cf3671500d58e4a0071f566d0ca8 Mon Sep 17 00:00:00 2001 From: Danthar Date: Thu, 10 Apr 2025 21:01:46 +0200 Subject: [PATCH 04/12] Docs --- .../custom-persistence-provider.md | 10 +++++++ docs/articles/persistence/storage-plugins.md | 28 +++++++++++++++++++ .../Journal/AsyncWriteJournal.cs | 7 +---- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/docs/articles/persistence/custom-persistence-provider.md b/docs/articles/persistence/custom-persistence-provider.md index 519c3208611..51c53960a71 100644 --- a/docs/articles/persistence/custom-persistence-provider.md +++ b/docs/articles/persistence/custom-persistence-provider.md @@ -217,6 +217,11 @@ akka.persistence.journal-plugin-fallback { # Dispatcher for message replay. replay-dispatcher = "akka.persistence.dispatchers.default-replay-dispatcher" + # journal supervisor strategy used. + # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. And have a parameterless constructor + # by default it restarts the journal on crash + supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" + # Default serializer used as manifest serializer when applicable # and payload serializer when no specific binding overrides are specified serializer = "json" @@ -406,6 +411,11 @@ akka.persistence.snapshot-store-plugin-fallback { # Dispatcher for the plugin actor. plugin-dispatcher = "akka.persistence.dispatchers.default-plugin-dispatcher" + # journal supervisor strategy used. + # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. And have a parameterless constructor + # by default it restarts the journal on crash + supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" + # Default serializer used as manifest serializer when applicable # and payload serializer when no specific binding overrides are specified serializer = "json" diff --git a/docs/articles/persistence/storage-plugins.md b/docs/articles/persistence/storage-plugins.md index aa8c63c35bc..19a383ce1f1 100644 --- a/docs/articles/persistence/storage-plugins.md +++ b/docs/articles/persistence/storage-plugins.md @@ -36,3 +36,31 @@ akka { } } ``` + + +### Controlling journal or snapshot crash behavior. + +By default the base implementations upon which all journal or snapshot-store implementations are build upon provides out of the box behavior for dealing with errors that occur during the writing or reading of data from the underlying store. Errors that occur will be communicated with the persistentactor that is using them at that time. +So in general once started succesfully the journal or snapshot-store will be ready and available for the duration of your application, and wont crash. However in the case they do crash, due to unforseen circumstances the default behavior is to immediatly restart them. This is generally the behavior you want. +But in case you do want to customize how the system handles the crashing of the journal or snapshot-store. You can specify your own supervision strategy using the `supervisor-strategy` property. +This class needs to inherit from `Akka.Actor.SupervisorStrategyConfigurator` and have a parameterless constructor. +Configuration example: + +```hocon +akka { + persistence { + journal { + plugin = "akka.persistence.journal.sqlite" + auto-start-journals = ["akka.persistence.journal.sqlite"] + supervisor-strategy = "My.full.namespace.CustomSupervisorStrategyConfigurator" + } + snapshot-store { + plugin = "akka.persistence.snapshot-store.sqlite" + auto-start-snapshot-stores = ["akka.persistence.snapshot-store.sqlite"] + supervisor-strategy = "My.full.namespace.CustomSupervisorStrategyConfigurator" + } + } +} +``` + +One such case could be to detect and handle misconfigured application settings during startup. For example if your using a SQL based journal and you misconfigured the connectionstring you might opt to return a supervisionstrategy that detects certain network connection errors, and after a few retries signals your application to shutdown instead of continue running with a journal or snapshot-store that in all likelyhood will never be able to recover, forever stuck in a restart loop while your application is running. \ No newline at end of file diff --git a/src/core/Akka.Persistence/Journal/AsyncWriteJournal.cs b/src/core/Akka.Persistence/Journal/AsyncWriteJournal.cs index 55b09aefd1e..317d2acf187 100644 --- a/src/core/Akka.Persistence/Journal/AsyncWriteJournal.cs +++ b/src/core/Akka.Persistence/Journal/AsyncWriteJournal.cs @@ -189,12 +189,7 @@ protected sealed override bool Receive(object message) { return ReceiveWriteJournal(message) || ReceivePluginInternal(message); } - - /// - /// TBD - /// - /// TBD - /// TBD + protected bool ReceiveWriteJournal(object message) { switch (message) From 1b19f8a6149ce7ad731889c892d3376b93f3e840 Mon Sep 17 00:00:00 2001 From: Danthar Date: Thu, 17 Apr 2025 12:29:58 +0200 Subject: [PATCH 05/12] Config spec and comments --- .../custom-persistence-provider.md | 4 +-- .../PersistenceConfigSpec.cs | 26 +++++++++++++++++++ src/core/Akka/Actor/SupervisorStrategy.cs | 18 +++---------- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/docs/articles/persistence/custom-persistence-provider.md b/docs/articles/persistence/custom-persistence-provider.md index 51c53960a71..cae463bbbb0 100644 --- a/docs/articles/persistence/custom-persistence-provider.md +++ b/docs/articles/persistence/custom-persistence-provider.md @@ -411,9 +411,9 @@ akka.persistence.snapshot-store-plugin-fallback { # Dispatcher for the plugin actor. plugin-dispatcher = "akka.persistence.dispatchers.default-plugin-dispatcher" - # journal supervisor strategy used. + # snapshot-store supervisor strategy used. # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. And have a parameterless constructor - # by default it restarts the journal on crash + # by default it restarts the snapshot-store on crash supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" # Default serializer used as manifest serializer when applicable diff --git a/src/core/Akka.Persistence.Tests/PersistenceConfigSpec.cs b/src/core/Akka.Persistence.Tests/PersistenceConfigSpec.cs index 4223bbf522e..2cd471664ad 100644 --- a/src/core/Akka.Persistence.Tests/PersistenceConfigSpec.cs +++ b/src/core/Akka.Persistence.Tests/PersistenceConfigSpec.cs @@ -100,6 +100,32 @@ public PersistenceConfigSpec(ITestOutputHelper output) : base(SpecConfig, output { } + /// + /// Verify that the journal config contains the expected default + /// + [Fact] + public void Journal_has_supervision_strategy_configured() + { + var persistence = Persistence.Instance.Apply(Sys); + + var config = persistence.JournalConfigFor("akka.persistence.journal.test1"); + var defaultstrategy = config.GetString("supervisor-strategy"); + defaultstrategy.ShouldBe(typeof(Akka.Actor.DefaultSupervisorStrategy).FullName); + } + + /// + /// Verify that the snapshot config contains the expected default + /// + [Fact] + public void Snapshot_has_supervision_strategy_configured() + { + var persistence = Persistence.Instance.Apply(Sys); + + var config = persistence.JournalConfigFor("akka.persistence.snapshot-store.test1"); + var defaultstrategy = config.GetString("supervisor-strategy"); + defaultstrategy.ShouldBe(typeof(Akka.Actor.DefaultSupervisorStrategy).FullName); + } + [Fact] public void Persistence_should_use_inmem_journal_by_default() { diff --git a/src/core/Akka/Actor/SupervisorStrategy.cs b/src/core/Akka/Actor/SupervisorStrategy.cs index 9bb5a4e5ca7..84ebbcabde0 100644 --- a/src/core/Akka/Actor/SupervisorStrategy.cs +++ b/src/core/Akka/Actor/SupervisorStrategy.cs @@ -1020,30 +1020,20 @@ public static SupervisorStrategyConfigurator CreateConfigurator(string typeName) } } - /// - /// TBD - /// + public class DefaultSupervisorStrategy : SupervisorStrategyConfigurator { - /// - /// TBD - /// - /// TBD + public override SupervisorStrategy Create() { return SupervisorStrategy.DefaultStrategy; } } - /// - /// TBD - /// + public class StoppingSupervisorStrategy : SupervisorStrategyConfigurator { - /// - /// TBD - /// - /// TBD + public override SupervisorStrategy Create() { return SupervisorStrategy.StoppingStrategy; From 91cf6a9454c34c387a1ce930e4d87998b98f6b11 Mon Sep 17 00:00:00 2001 From: Danthar Date: Thu, 17 Apr 2025 16:10:02 +0200 Subject: [PATCH 06/12] testcase that strat is actually applied --- .../PersistenceConfigSpec.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/core/Akka.Persistence.Tests/PersistenceConfigSpec.cs b/src/core/Akka.Persistence.Tests/PersistenceConfigSpec.cs index 2cd471664ad..6b2e5167187 100644 --- a/src/core/Akka.Persistence.Tests/PersistenceConfigSpec.cs +++ b/src/core/Akka.Persistence.Tests/PersistenceConfigSpec.cs @@ -101,7 +101,9 @@ public PersistenceConfigSpec(ITestOutputHelper output) : base(SpecConfig, output } /// - /// Verify that the journal config contains the expected default + /// Verify that the journal config contains the expected default from our fallback configs + /// No spec for when the user overrides that because its not the goal to test the hocon config system. + /// Merely that the plugin system here properly applies the fallback config for this config value. /// [Fact] public void Journal_has_supervision_strategy_configured() @@ -114,7 +116,9 @@ public void Journal_has_supervision_strategy_configured() } /// - /// Verify that the snapshot config contains the expected default + /// Verify that the snapshot config contains the expected default from our fallback configs + /// No spec for when the user overrides that because its not the goal to test the hocon config system. + /// Merely that the plugin system here properly applies the fallback config for this config value. /// [Fact] public void Snapshot_has_supervision_strategy_configured() @@ -126,6 +130,21 @@ public void Snapshot_has_supervision_strategy_configured() defaultstrategy.ShouldBe(typeof(Akka.Actor.DefaultSupervisorStrategy).FullName); } + [Fact] + public void Journal_has_custom_supervision_strategy_applied() + { + var persistence = Persistence.Instance.Apply(Sys); + var journal = persistence.JournalFor(""); // get the default journal + + //waves magic wand + var magicref = journal as ActorRefWithCell; + var appliedStrat = magicref.Underlying.Props.SupervisorStrategy; + //because the default value for our supervisor strategy is: Akka.Actor.DefaultSupervisorStrategy + //we verify that the strat returned is the same as currently applied + //for completeness we should also test with a custom strategy + appliedStrat.ShouldBe(SupervisorStrategy.DefaultStrategy); + } + [Fact] public void Persistence_should_use_inmem_journal_by_default() { From 8d79bcfe6c57f20d9db7ce59ee1877dfcd47d931 Mon Sep 17 00:00:00 2001 From: Danthar Date: Fri, 18 Apr 2025 17:03:29 +0200 Subject: [PATCH 07/12] update test to use custom strategy --- .../PersistenceConfigSpec.cs | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/core/Akka.Persistence.Tests/PersistenceConfigSpec.cs b/src/core/Akka.Persistence.Tests/PersistenceConfigSpec.cs index 6b2e5167187..7c9b9e3a285 100644 --- a/src/core/Akka.Persistence.Tests/PersistenceConfigSpec.cs +++ b/src/core/Akka.Persistence.Tests/PersistenceConfigSpec.cs @@ -5,11 +5,15 @@ // //----------------------------------------------------------------------- +using System; +using System.Collections.Generic; using Akka.Actor; +using Akka.Actor.Internal; using Akka.Configuration; using Akka.Persistence.Journal; using Akka.Persistence.Snapshot; using Akka.TestKit; +using Akka.Util; using Xunit; using Xunit.Abstractions; @@ -68,6 +72,32 @@ protected internal override bool AroundReceive(Receive receive, object message) } } + public class TestSupervisorConfigurator : SupervisorStrategyConfigurator + { + public override SupervisorStrategy Create() + { + return new CustomStrategy(10,TimeSpan.FromSeconds(5),ex => + { + //detect unrecoverable exception here + return Directive.Stop; + }); + } + } + + public class CustomStrategy : OneForOneStrategy + { + public CustomStrategy(int? maxNrOfRetries, TimeSpan? withinTimeRange, Func localOnlyDecider) : base(maxNrOfRetries, withinTimeRange, localOnlyDecider) + { + } + + public override void HandleChildTerminated(IActorContext actorContext, IActorRef child, IEnumerable children) + { + //because the journal does not has child actors, the ref is always the actor itself. So optionally do something special here + //to indicate to the system that the journal crashed in an unrecoverable way. + } + } + + #endregion private static readonly string SpecConfig = @" @@ -82,6 +112,12 @@ class = ""Akka.Persistence.Tests.PersistenceConfigSpec+TestJournal, Akka.Persist plugin-dispatcher = ""akka.actor.default-dispatcher"" test-value = ""B"" } + test3 { + class = ""Akka.Persistence.Tests.PersistenceConfigSpec+TestJournal, Akka.Persistence.Tests"" + plugin-dispatcher = ""akka.actor.default-dispatcher"" + test-value = ""B"" + supervisor-strategy = ""Akka.Persistence.Tests.PersistenceConfigSpec+TestSupervisorConfigurator, Akka.Persistence.Tests"" + } } akka.persistence.snapshot-store { test1 { @@ -110,7 +146,7 @@ public void Journal_has_supervision_strategy_configured() { var persistence = Persistence.Instance.Apply(Sys); - var config = persistence.JournalConfigFor("akka.persistence.journal.test1"); + var config = persistence.JournalConfigFor("akka.persistence.journal.test2"); var defaultstrategy = config.GetString("supervisor-strategy"); defaultstrategy.ShouldBe(typeof(Akka.Actor.DefaultSupervisorStrategy).FullName); } @@ -134,15 +170,15 @@ public void Snapshot_has_supervision_strategy_configured() public void Journal_has_custom_supervision_strategy_applied() { var persistence = Persistence.Instance.Apply(Sys); - var journal = persistence.JournalFor(""); // get the default journal + var journal = persistence.JournalFor("akka.persistence.journal.test3"); //get our journal with the custom configuration //waves magic wand var magicref = journal as ActorRefWithCell; var appliedStrat = magicref.Underlying.Props.SupervisorStrategy; - //because the default value for our supervisor strategy is: Akka.Actor.DefaultSupervisorStrategy + //because the configured value for our supervisor strategy is our CustomStrategy //we verify that the strat returned is the same as currently applied - //for completeness we should also test with a custom strategy - appliedStrat.ShouldBe(SupervisorStrategy.DefaultStrategy); + var customstrategy = new TestSupervisorConfigurator().Create(); + appliedStrat.GetType().ShouldBe(customstrategy.GetType()); } [Fact] From fd869fb5c82b4e5f74346c69b2b48983b58f3889 Mon Sep 17 00:00:00 2001 From: Danthar Date: Fri, 18 Apr 2025 17:12:59 +0200 Subject: [PATCH 08/12] more docs --- docs/articles/persistence/storage-plugins.md | 25 +++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/articles/persistence/storage-plugins.md b/docs/articles/persistence/storage-plugins.md index 19a383ce1f1..7e5475b70b0 100644 --- a/docs/articles/persistence/storage-plugins.md +++ b/docs/articles/persistence/storage-plugins.md @@ -52,15 +52,34 @@ akka { journal { plugin = "akka.persistence.journal.sqlite" auto-start-journals = ["akka.persistence.journal.sqlite"] - supervisor-strategy = "My.full.namespace.CustomSupervisorStrategyConfigurator" + supervisor-strategy = "My.full.namespace.CustomSupervisorStrategyConfigurator" } snapshot-store { plugin = "akka.persistence.snapshot-store.sqlite" auto-start-snapshot-stores = ["akka.persistence.snapshot-store.sqlite"] - supervisor-strategy = "My.full.namespace.CustomSupervisorStrategyConfigurator" + supervisor-strategy = "My.full.namespace.CustomSupervisorStrategyConfigurator" } } } ``` -One such case could be to detect and handle misconfigured application settings during startup. For example if your using a SQL based journal and you misconfigured the connectionstring you might opt to return a supervisionstrategy that detects certain network connection errors, and after a few retries signals your application to shutdown instead of continue running with a journal or snapshot-store that in all likelyhood will never be able to recover, forever stuck in a restart loop while your application is running. \ No newline at end of file +One such case could be to detect and handle misconfigured application settings during startup. For example if your using a SQL based journal and you misconfigured the connectionstring you might opt to return a supervisionstrategy that detects certain network connection errors, and after a few retries signals your application to shutdown instead of continue running with a journal or snapshot-store that in all likelyhood will never be able to recover, forever stuck in a restart loop while your application is running. + +An example of what this could look like is this: + +``` + + public class MyCustomSupervisorConfigurator : SupervisorStrategyConfigurator + { + public override SupervisorStrategy Create() + { + //optionally only stop if the error occurs more then x times in y period + //this will be highly likely if its an unrecoverable error during start/init of the journal/snapshot store + return new OneForOneStrategy(10,TimeSpan.FromSeconds(5),ex => + { + //detect unrecoverable exception here + return Directive.Stop; + }); + } + } +``` \ No newline at end of file From 4a1eae0d36299420bd86ab84a3f999c658146c22 Mon Sep 17 00:00:00 2001 From: Danthar Date: Fri, 18 Apr 2025 17:14:53 +0200 Subject: [PATCH 09/12] syntax highlight --- docs/articles/persistence/storage-plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/persistence/storage-plugins.md b/docs/articles/persistence/storage-plugins.md index 7e5475b70b0..1ff5c764450 100644 --- a/docs/articles/persistence/storage-plugins.md +++ b/docs/articles/persistence/storage-plugins.md @@ -67,7 +67,7 @@ One such case could be to detect and handle misconfigured application settings d An example of what this could look like is this: -``` +```csharp public class MyCustomSupervisorConfigurator : SupervisorStrategyConfigurator { From c30617759f9a489c574d0bd70f7180f944da979b Mon Sep 17 00:00:00 2001 From: Danthar Date: Fri, 18 Apr 2025 17:18:03 +0200 Subject: [PATCH 10/12] todo removal --- src/core/Akka.Persistence/Persistence.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/Akka.Persistence/Persistence.cs b/src/core/Akka.Persistence/Persistence.cs index e030b8226e0..d6d8f052719 100644 --- a/src/core/Akka.Persistence/Persistence.cs +++ b/src/core/Akka.Persistence/Persistence.cs @@ -304,8 +304,7 @@ private static IActorRef CreatePlugin(ExtendedActorSystem system, string configP object[] pluginActorArgs = pluginType.GetConstructor(new[] { typeof(Config) }) != null ? new object[] { pluginConfig } : null; //todo wrap in backoffsupervisor ? - //todo provide docs with examples on how to use this and possibly make decisions on crashes. With explanations of intended scenarios - + //supervisor-strategy is defined by default in the fallback configs. So we always expect to get a value here even if the user has not explicitly defined anything var configurator = SupervisorStrategyConfigurator.CreateConfigurator(pluginConfig.GetString("supervisor-strategy")); From cbf49fcfc7c00801d86a698717aabc9b6ac5f552 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Thu, 8 May 2025 00:56:59 +0700 Subject: [PATCH 11/12] Fix DocFX warnings --- docs/articles/persistence/custom-persistence-provider.md | 2 +- docs/articles/persistence/storage-plugins.md | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/articles/persistence/custom-persistence-provider.md b/docs/articles/persistence/custom-persistence-provider.md index cae463bbbb0..ca30c385622 100644 --- a/docs/articles/persistence/custom-persistence-provider.md +++ b/docs/articles/persistence/custom-persistence-provider.md @@ -221,7 +221,7 @@ akka.persistence.journal-plugin-fallback { # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. And have a parameterless constructor # by default it restarts the journal on crash supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" - + # Default serializer used as manifest serializer when applicable # and payload serializer when no specific binding overrides are specified serializer = "json" diff --git a/docs/articles/persistence/storage-plugins.md b/docs/articles/persistence/storage-plugins.md index 1ff5c764450..751f958b6ff 100644 --- a/docs/articles/persistence/storage-plugins.md +++ b/docs/articles/persistence/storage-plugins.md @@ -37,8 +37,7 @@ akka { } ``` - -### Controlling journal or snapshot crash behavior. +### Controlling Journal or Snapshot Crash Behavior By default the base implementations upon which all journal or snapshot-store implementations are build upon provides out of the box behavior for dealing with errors that occur during the writing or reading of data from the underlying store. Errors that occur will be communicated with the persistentactor that is using them at that time. So in general once started succesfully the journal or snapshot-store will be ready and available for the duration of your application, and wont crash. However in the case they do crash, due to unforseen circumstances the default behavior is to immediatly restart them. This is generally the behavior you want. @@ -82,4 +81,4 @@ An example of what this could look like is this: }); } } -``` \ No newline at end of file +``` From 7316eb92efa43e2fea2b2b0ecf4077e141e51592 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Thu, 8 May 2025 01:00:40 +0700 Subject: [PATCH 12/12] Fix documentation typos --- docs/articles/persistence/storage-plugins.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/articles/persistence/storage-plugins.md b/docs/articles/persistence/storage-plugins.md index 751f958b6ff..d2ba4983100 100644 --- a/docs/articles/persistence/storage-plugins.md +++ b/docs/articles/persistence/storage-plugins.md @@ -39,10 +39,10 @@ akka { ### Controlling Journal or Snapshot Crash Behavior -By default the base implementations upon which all journal or snapshot-store implementations are build upon provides out of the box behavior for dealing with errors that occur during the writing or reading of data from the underlying store. Errors that occur will be communicated with the persistentactor that is using them at that time. -So in general once started succesfully the journal or snapshot-store will be ready and available for the duration of your application, and wont crash. However in the case they do crash, due to unforseen circumstances the default behavior is to immediatly restart them. This is generally the behavior you want. +By default the base implementations upon which all journal or snapshot-store implementations are build upon provides out of the box behavior for dealing with errors that occur during the writing or reading of data from the underlying store. Errors that occur will be communicated with the persistent actor that is using them at that time. +So in general once started successfully the journal or snapshot-store will be ready and available for the duration of your application, and won't crash. However in the case they do crash, due to unforeseen circumstances the default behavior is to immediately restart them. This is generally the behavior you want. But in case you do want to customize how the system handles the crashing of the journal or snapshot-store. You can specify your own supervision strategy using the `supervisor-strategy` property. -This class needs to inherit from `Akka.Actor.SupervisorStrategyConfigurator` and have a parameterless constructor. +This class needs to inherit from `Akka.Actor.SupervisorStrategyConfigurator` and have a parameter-less constructor. Configuration example: ```hocon @@ -62,7 +62,7 @@ akka { } ``` -One such case could be to detect and handle misconfigured application settings during startup. For example if your using a SQL based journal and you misconfigured the connectionstring you might opt to return a supervisionstrategy that detects certain network connection errors, and after a few retries signals your application to shutdown instead of continue running with a journal or snapshot-store that in all likelyhood will never be able to recover, forever stuck in a restart loop while your application is running. +One such case could be to detect and handle misconfigured application settings during startup. For example if your using a SQL based journal and you misconfigured the connection string you might opt to return a supervision strategy that detects certain network connection errors, and after a few retries signals your application to shutdown instead of continue running with a journal or snapshot-store that in all likelihood will never be able to recover, forever stuck in a restart loop while your application is running. An example of what this could look like is this: