Skip to content

Commit 1877a0e

Browse files
Akka.Actor: bounded IStash programmatic configuration (#6661)
* close #6658 Add APIs to track content of Stash * added basic stashing test cases * updated API approvals * Added `StashSize` to `Deploy` * adding support for bounded stash sizes * API approval * adding `Props` and `ActorOf` support for configuring stash size * added API approvals * ensured that `IWithUnboundedStash` can't be accidentally made bounded via HOCON or config * added `stashSize` to protobuf wire format needed for remote deployments * rename `StashSize` to `StashCapacity` * renamed wire format from stashSize to stashCapacity * added serialization support for `StashCapacity * API approvals * added documentation * added bounded stashing api docs to untyped actor page * added comment explaining backwards compat handling for StashCapacity * fixed `DeploySurrogate` API * fix `Deployer` * fix `AbstractStash` * fixed docs
1 parent f79c26d commit 1877a0e

18 files changed

+405
-132
lines changed

docs/articles/actors/receive-actor-api.md

+32
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,38 @@ The result of this is that when an actor is restarted, any stashed messages will
843843
> [!NOTE]
844844
> If you want to enforce that your actor can only work with an unbounded stash, then you should use the `IWithUnboundedStash` interface instead.
845845

846+
### Bounded Stashes
847+
848+
In certain scenarios, it might be helpful to put a limit on the size of the `IStash` inside your actor. You can configure a bounded stash via the following actor definition:
849+
850+
```csharp
851+
public class StashingActorWithOverflow : UntypedActor, IWithStash
852+
```
853+
854+
The `IWithStash` interface will default to *unbounded* stash behavior, but the the `Props` class or via `akka.actor.deployment` we can easily configure this actor to impose a limit on its stash capacity:
855+
856+
```csharp
857+
// create an actor with a stash size of 2
858+
IActorRef stasher = Sys.ActorOf(Props.Create<StashingActorWithOverflow>().WithStashCapacity(2));
859+
```
860+
861+
Or via HOCON:
862+
863+
```hocon
864+
akka.actor.deployment{{
865+
/configStashingActor {{
866+
stash-capacity = 2
867+
}}
868+
}}
869+
```
870+
871+
Either of these settings will configure the `IStash` to only have a maximum capacity of 2 items. If a third item is attempted to be stashed the `IStash` will throw a `StashOverflowException`.
872+
873+
> [!TIP]
874+
> You can always check to see if your `IStash` is approaching its capacity by checking the `IStash.IsFull`, `IStash.Capacity`, or `IStash.Count` properties.
875+
876+
If you attempt to apply a maximum stash capacity to an `IWithUnboundedStash` actor then the setting will be ignored.
877+
846878
## Killing an Actor
847879

848880
You can kill an actor by sending a `Kill` message. This will cause the actor to throw a `ActorKilledException`, triggering a failure. The actor will suspend operation and its supervisor will be asked how to handle the failure, which may mean resuming the actor, restarting it or terminating it completely. See [What Supervision Means](xref:supervision#what-supervision-means) for more information.

docs/articles/actors/untyped-actor-api.md

+32
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,38 @@ Note that the stash is part of the ephemeral actor state, unlike the mailbox. Th
758758
> [!NOTE]
759759
> If you want to enforce that your actor can only work with an unbounded stash, then you should use the `UntypedActorWithUnboundedStash` class instead.
760760
761+
### Bounded Stashes
762+
763+
In certain scenarios, it might be helpful to put a limit on the size of the `IStash` inside your actor. You can configure a bounded stash via the following actor definition:
764+
765+
```csharp
766+
public class StashingActorWithOverflow : UntypedActor, IWithStash
767+
```
768+
769+
The `IWithStash` interface will default to *unbounded* stash behavior, but the the `Props` class or via `akka.actor.deployment` we can easily configure this actor to impose a limit on its stash capacity:
770+
771+
```csharp
772+
// create an actor with a stash size of 2
773+
IActorRef stasher = Sys.ActorOf(Props.Create<StashingActorWithOverflow>().WithStashCapacity(2));
774+
```
775+
776+
Or via HOCON:
777+
778+
```hocon
779+
akka.actor.deployment{{
780+
/configStashingActor {{
781+
stash-capacity = 2
782+
}}
783+
}}
784+
```
785+
786+
Either of these settings will configure the `IStash` to only have a maximum capacity of 2 items. If a third item is attempted to be stashed the `IStash` will throw a `StashOverflowException`.
787+
788+
> [!TIP]
789+
> You can always check to see if your `IStash` is approaching its capacity by checking the `IStash.IsFull`, `IStash.Capacity`, or `IStash.Count` properties.
790+
791+
If you attempt to apply a maximum stash capacity to an `IWithUnboundedStash` actor then the setting will be ignored.
792+
761793
## Killing an Actor
762794

763795
You can kill an actor by sending a `Kill` message. This will cause the actor to throw a `ActorKilledException`, triggering a failure. The actor will suspend operation and its supervisor will be asked how to handle the failure, which may mean resuming the actor, restarting it or terminating it completely. See [What Supervision Means](xref:supervision#what-supervision-means) for more information.

src/common.props

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<Copyright>Copyright © 2013-2023 Akka.NET Team</Copyright>
44
<Authors>Akka.NET Team</Authors>
5-
<VersionPrefix>1.5.3</VersionPrefix>
5+
<VersionPrefix>1.5.4</VersionPrefix>
66
<PackageIcon>akkalogo.png</PackageIcon>
77
<PackageProjectUrl>https://github.com/akkadotnet/akka.net</PackageProjectUrl>
88
<PackageLicenseUrl>https://github.com/akkadotnet/akka.net/blob/master/LICENSE</PackageLicenseUrl>
@@ -39,7 +39,7 @@
3939
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
4040
</PropertyGroup>
4141
<PropertyGroup>
42-
<PackageReleaseNotes>Placeholder for nightly builds*</PackageReleaseNotes>
42+
<PackageReleaseNotes>placeholder for nightlies*</PackageReleaseNotes>
4343
</PropertyGroup>
4444
<!-- SourceLink support for all Akka.NET projects -->
4545
<ItemGroup>

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.DotNet.verified.txt

+8-1
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ namespace Akka.Actor
621621
public static readonly string NoDispatcherGiven;
622622
public static readonly string NoMailboxGiven;
623623
public static readonly Akka.Actor.Scope NoScopeGiven;
624+
public const int NoStashSize = -1;
624625
public static readonly Akka.Actor.Deploy None;
625626
public Deploy() { }
626627
public Deploy(string path, Akka.Actor.Scope scope) { }
@@ -629,19 +630,22 @@ namespace Akka.Actor
629630
public Deploy(Akka.Routing.RouterConfig routerConfig) { }
630631
public Deploy(string path, Akka.Configuration.Config config, Akka.Routing.RouterConfig routerConfig, Akka.Actor.Scope scope, string dispatcher) { }
631632
public Deploy(string path, Akka.Configuration.Config config, Akka.Routing.RouterConfig routerConfig, Akka.Actor.Scope scope, string dispatcher, string mailbox) { }
633+
public Deploy(string path, Akka.Configuration.Config config, Akka.Routing.RouterConfig routerConfig, Akka.Actor.Scope scope, string dispatcher, string mailbox, int stashCapacity) { }
632634
public Akka.Configuration.Config Config { get; }
633635
public string Dispatcher { get; }
634636
public string Mailbox { get; }
635637
public string Path { get; }
636638
public Akka.Routing.RouterConfig RouterConfig { get; }
637639
public Akka.Actor.Scope Scope { get; }
640+
public int StashCapacity { get; }
638641
public bool Equals(Akka.Actor.Deploy other) { }
639642
public Akka.Util.ISurrogate ToSurrogate(Akka.Actor.ActorSystem system) { }
640643
public virtual Akka.Actor.Deploy WithDispatcher(string dispatcher) { }
641644
public virtual Akka.Actor.Deploy WithFallback(Akka.Actor.Deploy other) { }
642645
public virtual Akka.Actor.Deploy WithMailbox(string mailbox) { }
643646
public virtual Akka.Actor.Deploy WithRouterConfig(Akka.Routing.RouterConfig routerConfig) { }
644647
public virtual Akka.Actor.Deploy WithScope(Akka.Actor.Scope scope) { }
648+
public virtual Akka.Actor.Deploy WithStashCapacity(int stashSize) { }
645649
public class DeploySurrogate : Akka.Util.ISurrogate
646650
{
647651
public DeploySurrogate() { }
@@ -651,6 +655,7 @@ namespace Akka.Actor
651655
public string Path { get; set; }
652656
public Akka.Routing.RouterConfig RouterConfig { get; set; }
653657
public Akka.Actor.Scope Scope { get; set; }
658+
public int StashCapacity { get; set; }
654659
public Akka.Util.ISurrogated FromSurrogate(Akka.Actor.ActorSystem system) { }
655660
}
656661
}
@@ -1474,6 +1479,7 @@ namespace Akka.Actor
14741479
public Akka.Actor.Props WithDispatcher(string dispatcher) { }
14751480
public Akka.Actor.Props WithMailbox(string mailbox) { }
14761481
public Akka.Actor.Props WithRouter(Akka.Routing.RouterConfig routerConfig) { }
1482+
public Akka.Actor.Props WithStashCapacity(int stashCapacity) { }
14771483
public Akka.Actor.Props WithSupervisorStrategy(Akka.Actor.SupervisorStrategy supervisorStrategy) { }
14781484
public class PropsSurrogate : Akka.Util.ISurrogate
14791485
{
@@ -1938,7 +1944,7 @@ namespace Akka.Actor.Internal
19381944
public abstract class AbstractStash : Akka.Actor.IStash
19391945
{
19401946
protected AbstractStash(Akka.Actor.IActorContext context) { }
1941-
public int Capacity { get; }
1947+
public virtual int Capacity { get; }
19421948
public int Count { get; }
19431949
public bool IsEmpty { get; }
19441950
public bool IsFull { get; }
@@ -2144,6 +2150,7 @@ namespace Akka.Actor.Internal
21442150
public class UnboundedStashImpl : Akka.Actor.Internal.AbstractStash
21452151
{
21462152
public UnboundedStashImpl(Akka.Actor.IActorContext context) { }
2153+
public override int Capacity { get; }
21472154
}
21482155
}
21492156
namespace Akka.Actor.Setup

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Net.verified.txt

+8-1
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ namespace Akka.Actor
621621
public static readonly string NoDispatcherGiven;
622622
public static readonly string NoMailboxGiven;
623623
public static readonly Akka.Actor.Scope NoScopeGiven;
624+
public const int NoStashSize = -1;
624625
public static readonly Akka.Actor.Deploy None;
625626
public Deploy() { }
626627
public Deploy(string path, Akka.Actor.Scope scope) { }
@@ -629,19 +630,22 @@ namespace Akka.Actor
629630
public Deploy(Akka.Routing.RouterConfig routerConfig) { }
630631
public Deploy(string path, Akka.Configuration.Config config, Akka.Routing.RouterConfig routerConfig, Akka.Actor.Scope scope, string dispatcher) { }
631632
public Deploy(string path, Akka.Configuration.Config config, Akka.Routing.RouterConfig routerConfig, Akka.Actor.Scope scope, string dispatcher, string mailbox) { }
633+
public Deploy(string path, Akka.Configuration.Config config, Akka.Routing.RouterConfig routerConfig, Akka.Actor.Scope scope, string dispatcher, string mailbox, int stashCapacity) { }
632634
public Akka.Configuration.Config Config { get; }
633635
public string Dispatcher { get; }
634636
public string Mailbox { get; }
635637
public string Path { get; }
636638
public Akka.Routing.RouterConfig RouterConfig { get; }
637639
public Akka.Actor.Scope Scope { get; }
640+
public int StashCapacity { get; }
638641
public bool Equals(Akka.Actor.Deploy other) { }
639642
public Akka.Util.ISurrogate ToSurrogate(Akka.Actor.ActorSystem system) { }
640643
public virtual Akka.Actor.Deploy WithDispatcher(string dispatcher) { }
641644
public virtual Akka.Actor.Deploy WithFallback(Akka.Actor.Deploy other) { }
642645
public virtual Akka.Actor.Deploy WithMailbox(string mailbox) { }
643646
public virtual Akka.Actor.Deploy WithRouterConfig(Akka.Routing.RouterConfig routerConfig) { }
644647
public virtual Akka.Actor.Deploy WithScope(Akka.Actor.Scope scope) { }
648+
public virtual Akka.Actor.Deploy WithStashCapacity(int stashSize) { }
645649
public class DeploySurrogate : Akka.Util.ISurrogate
646650
{
647651
public DeploySurrogate() { }
@@ -651,6 +655,7 @@ namespace Akka.Actor
651655
public string Path { get; set; }
652656
public Akka.Routing.RouterConfig RouterConfig { get; set; }
653657
public Akka.Actor.Scope Scope { get; set; }
658+
public int StashCapacity { get; set; }
654659
public Akka.Util.ISurrogated FromSurrogate(Akka.Actor.ActorSystem system) { }
655660
}
656661
}
@@ -1472,6 +1477,7 @@ namespace Akka.Actor
14721477
public Akka.Actor.Props WithDispatcher(string dispatcher) { }
14731478
public Akka.Actor.Props WithMailbox(string mailbox) { }
14741479
public Akka.Actor.Props WithRouter(Akka.Routing.RouterConfig routerConfig) { }
1480+
public Akka.Actor.Props WithStashCapacity(int stashCapacity) { }
14751481
public Akka.Actor.Props WithSupervisorStrategy(Akka.Actor.SupervisorStrategy supervisorStrategy) { }
14761482
public class PropsSurrogate : Akka.Util.ISurrogate
14771483
{
@@ -1936,7 +1942,7 @@ namespace Akka.Actor.Internal
19361942
public abstract class AbstractStash : Akka.Actor.IStash
19371943
{
19381944
protected AbstractStash(Akka.Actor.IActorContext context) { }
1939-
public int Capacity { get; }
1945+
public virtual int Capacity { get; }
19401946
public int Count { get; }
19411947
public bool IsEmpty { get; }
19421948
public bool IsFull { get; }
@@ -2142,6 +2148,7 @@ namespace Akka.Actor.Internal
21422148
public class UnboundedStashImpl : Akka.Actor.Internal.AbstractStash
21432149
{
21442150
public UnboundedStashImpl(Akka.Actor.IActorContext context) { }
2151+
public override int Capacity { get; }
21452152
}
21462153
}
21472154
namespace Akka.Actor.Setup

src/core/Akka.Remote.Tests/Serialization/DaemonMsgCreateSerializerSpec.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public void Serialization_must_serialize_and_deserialize_DaemonMsgCreate_with_De
7272
ConfigurationFactory.ParseString("a=1"),
7373
new RoundRobinPool(5, null, supervisorStrategy, null),
7474
new RemoteScope(new Address("akka", "Test", "host1", 1921)),
75-
"mydispatcher");
75+
"mydispatcher").WithStashCapacity(10);
7676
var deploy2 = new Deploy("path2",
7777
ConfigurationFactory.ParseString("a=2"),
7878
FromConfig.Instance,

src/core/Akka.Remote/Serialization/DaemonMsgCreateSerializer.cs

+20-13
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ public override byte[] ToBinary(object obj)
4646
return message.ToByteArray();
4747
}
4848

49-
throw new ArgumentException($"Can't serialize a non-DaemonMsgCreate message using DaemonMsgCreateSerializer [{obj.GetType()}]");
49+
throw new ArgumentException(
50+
$"Can't serialize a non-DaemonMsgCreate message using DaemonMsgCreateSerializer [{obj.GetType()}]");
5051
}
5152

5253
/// <inheritdoc />
@@ -105,15 +106,15 @@ private Proto.Msg.DeployData DeployToProto(Deploy deploy)
105106
{
106107
var deployBuilder = new Proto.Msg.DeployData();
107108
deployBuilder.Path = deploy.Path;
108-
109+
deployBuilder.StashCapacity = deploy.StashCapacity;
109110
{
110111
var tuple = Serialize(deploy.Config);
111112
deployBuilder.ConfigSerializerId = tuple.Item1;
112113
deployBuilder.ConfigManifest = tuple.Item3;
113114
deployBuilder.Config = ByteString.CopyFrom(tuple.Item4);
114115
}
115116

116-
if (deploy.RouterConfig != NoRouter.Instance)
117+
if (!deploy.RouterConfig.Equals(NoRouter.Instance))
117118
{
118119
var tuple = Serialize(deploy.RouterConfig);
119120
deployBuilder.RouterConfigSerializerId = tuple.Item1;
@@ -140,7 +141,8 @@ private Proto.Msg.DeployData DeployToProto(Deploy deploy)
140141
private Deploy DeployFromProto(Proto.Msg.DeployData protoDeploy)
141142
{
142143
Config config;
143-
if (protoDeploy.ConfigSerializerId > 0) // TODO: should be protoDeploy.Config != null. But it always not null
144+
if (protoDeploy.ConfigSerializerId >
145+
0) // TODO: should be protoDeploy.Config != null. But it always not null
144146
{
145147
config = system.Serialization.Deserialize(
146148
protoDeploy.Config.ToByteArray(),
@@ -152,9 +154,10 @@ private Deploy DeployFromProto(Proto.Msg.DeployData protoDeploy)
152154
config = Config.Empty;
153155
}
154156

155-
157+
156158
RouterConfig routerConfig;
157-
if (protoDeploy.RouterConfigSerializerId > 0) // TODO: should be protoDeploy.RouterConfig != null. But it always not null
159+
if (protoDeploy.RouterConfigSerializerId >
160+
0) // TODO: should be protoDeploy.RouterConfig != null. But it always not null
158161
{
159162
routerConfig = system.Serialization.Deserialize(
160163
protoDeploy.RouterConfig.ToByteArray(),
@@ -183,18 +186,22 @@ private Deploy DeployFromProto(Proto.Msg.DeployData protoDeploy)
183186
? protoDeploy.Dispatcher
184187
: Deploy.NoDispatcherGiven;
185188

186-
return new Deploy(protoDeploy.Path, config, routerConfig, scope, dispatcher);
189+
// automatically covers backwards compat scenarios too - if the stash capacity is 0 (Protobuf default)
190+
// it'll be set to -1 here, which is consistent with how defaults prior to v1.5.4 were handled.
191+
var stashCapacity = protoDeploy.StashCapacity > 0 ? protoDeploy.StashCapacity : Deploy.NoStashSize;
192+
193+
return stashCapacity == Deploy.NoStashSize
194+
? new Deploy(protoDeploy.Path, config, routerConfig, scope, dispatcher)
195+
: new Deploy(protoDeploy.Path, config, routerConfig, scope, dispatcher)
196+
.WithStashCapacity(stashCapacity);
187197
}
188198

189199
//
190200
// IActorRef
191201
//
192-
private Proto.Msg.ActorRefData SerializeActorRef(IActorRef actorRef)
202+
private static Proto.Msg.ActorRefData SerializeActorRef(IActorRef actorRef)
193203
{
194-
return new Proto.Msg.ActorRefData
195-
{
196-
Path = Akka.Serialization.Serialization.SerializedActorPath(actorRef)
197-
};
204+
return new Proto.Msg.ActorRefData { Path = Akka.Serialization.Serialization.SerializedActorPath(actorRef) };
198205
}
199206

200207
private IActorRef DeserializeActorRef(Proto.Msg.ActorRefData actorRefData)
@@ -212,4 +219,4 @@ private IActorRef DeserializeActorRef(Proto.Msg.ActorRefData actorRefData)
212219
return (serializer.Identifier, hasManifest, manifest, serializer.ToBinary(obj));
213220
}
214221
}
215-
}
222+
}

0 commit comments

Comments
 (0)