diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..0383d0202d0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,27 @@ +version: 2 +updates: +- package-ecosystem: nuget + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + ignore: + - dependency-name: NUnit + versions: + - 3.13.0 + - 3.13.1 + - dependency-name: Google.Protobuf + versions: + - 3.15.0 + - 3.15.1 + - 3.15.4 + - dependency-name: FsCheck.Xunit + versions: + - 2.14.5 + - 2.15.0 + - dependency-name: FsCheck + versions: + - 2.14.5 + - dependency-name: FluentAssertions + versions: + - 5.9.0 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 96c58730faa..524e5fd4bfd 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,124 @@ +#### 1.4.19 April 28 2021 #### +**Maintenance Release for Akka.NET 1.4** + +Akka.NET v1.4.19 is a _substantial_ release that includes a number of critical Akka.Cluster fixes, baseline Akka.NET performance improvements, and entirely new dispatcher that has shown to improve performance when used across all of the major actor groups that run both inside the `/user` hierarchy and the `/system` actor hierarchy as well. + +**Akka.Cluster Improvements** +One of the most demanding issues of the v1.4.19 release was "[Akka.Cluster: quarantining / reachability changes appear to be extremely sensitive](https://github.com/akkadotnet/akka.net/issues/4849)" - and this is because debugging this issue touched so many different parts of Akka.Cluster. + +We ultimately solved the problem - it is now quite feasible to rapidly scale an Akka.NET cluster from ~10 nodes to 50+ nodes without having a huge number of quarantines, accidentally downed nodes, and so on. + +Here's the full set of fixes that went into resolving this issue: + +* [Added `PhiAccrualFailureDetector` warning logging for slow heartbeats](https://github.com/akkadotnet/akka.net/pull/4897) +* [measure Akka.Cluster heartbeat timings, hardened Akka.Cluster serialization](https://github.com/akkadotnet/akka.net/pull/4934) +* [`ClusterStressSpec` and Cluster Failure Detector Cleanup](https://github.com/akkadotnet/akka.net/pull/4940) +* [Akka.Cluster: improve `HeartbeatNodeRing` performance](https://github.com/akkadotnet/akka.net/pull/4943) +* [Akka.Cluster: Turned `HeatbeatNodeRing` into `struct`](https://github.com/akkadotnet/akka.net/pull/4944) +* [Akka.Cluster: Configure duration for applying `MemberStatus.WeaklyUp` to joining nodes](https://github.com/akkadotnet/akka.net/pull/4946) +* [Akka.Cluster: Performance optimize `VectorClock`](https://github.com/akkadotnet/akka.net/pull/4952) +* [Akka.Cluster: Refactored `Gossip` into `MembershipState`](https://github.com/akkadotnet/akka.net/pull/4968) +* [Akka.Remote: Clean up bad outbound ACKs in Akka.Remote](https://github.com/akkadotnet/akka.net/pull/4963) + +Akka.Cluster is now much more robust, faster, and capable of scaling up and down much more efficiently than in previous releases. + +**`ChannelExecutor` and Akka Performance Improvements** +In addition to improving Akka.Cluster, we also made substantial improvements to constructs found inside Akka.NET core itself: + +* [Perf optimize `ActorSelection`](https://github.com/akkadotnet/akka.net/pull/4962) - 20% throughput improvement, 25% memory consumption improvement +* [fixed N-1 error inside `Mailbox`](https://github.com/akkadotnet/akka.net/pull/4964) +* [Introduce `ChannelExecutor`](https://github.com/akkadotnet/akka.net/pull/4882) + +In Akka.NET v1.4.19 we introduce an opt-in feature, the `ChannelExecutor` - a new dispatcher type that re-uses the same configuration as a `ForkJoinDispatcher` but runs entirely on top of the .NET `ThreadPool` and is able to take advantage of dynamic thread pool scaling to size / resize workloads on the fly. + +In order to get the most use out of the `ChannelExecutor`, the default actor dispatcher, the internal dispatcher, and the Akka.Remote dispatchers all need to run on it - and you can see the latest configuration settings and guidance for that here in our documentation: https://getakka.net/articles/actors/dispatchers.html#channelexecutor + +But a copy of today's configuration is included below - you can enable this feature inside your Akka.NET applications via the following HOCON: + +``` +akka.actor.default-dispatcher = { + executor = channel-executor + fork-join-executor { #channelexecutor will re-use these settings + parallelism-min = 2 + parallelism-factor = 1 + parallelism-max = 64 + } +} + +akka.actor.internal-dispatcher = { + executor = channel-executor + throughput = 5 + fork-join-executor { + parallelism-min = 4 + parallelism-factor = 1.0 + parallelism-max = 64 + } +} + +akka.remote.default-remote-dispatcher { + type = Dispatcher + executor = channel-executor + fork-join-executor { + parallelism-min = 2 + parallelism-factor = 0.5 + parallelism-max = 16 + } +} + +akka.remote.backoff-remote-dispatcher { + executor = channel-executor + fork-join-executor { + parallelism-min = 2 + parallelism-max = 2 + } +} +``` + +**We are looking for feedback on how well the `ChannelExecutor` works in real world applications here: https://github.com/akkadotnet/akka.net/discussions/4983** + +**Hyperion v0.10 and Improvements** +We also released [Hyperion v0.10.0](https://github.com/akkadotnet/Hyperion/releases/tag/0.10.0) and [v0.10.1](https://github.com/akkadotnet/Hyperion/releases/tag/0.10.1) as part of the Akka.NET v1.4.19 sprint, and this includes some useful changes for Akka.NET users who are trying to build cross-platform (.NET Framework + .NET Core / .NET 5) applications and need to handle all of the idiosyncrasies those platforms introduced by changing the default namespaces on primitive types such as `string` and `int`. + +We have also introduced a [new `Setup` type](https://getakka.net/articles/concepts/configuration.html#programmatic-configuration-with-setup) designed to make it easy to resolve some of these "cross platform" serialization concerns programmatically when configuring Hyperion for use inside Akka.NET: + +```csharp +#if NETFRAMEWORK +var hyperionSetup = HyperionSerializerSetup.Empty + .WithPackageNameOverrides(new Func[] + { + str => str.Contains("System.Private.CoreLib,%core%") + ? str.Replace("System.Private.CoreLib,%core%", "mscorlib,%core%") : str + } +#elif NETCOREAPP +var hyperionSetup = HyperionSerializerSetup.Empty + .WithPackageNameOverrides(new Func[] + { + str => str.Contains("mscorlib,%core%") + ? str.Replace("mscorlib,%core%", "System.Private.CoreLib,%core%") : str + } +#endif + +var bootstrap = BootstrapSetup.Create().And(hyperionSetup); +var system = ActorSystem.Create("actorSystem", bootstrap); +``` + +See the full documentation for this feature here: https://getakka.net/articles/networking/serialization.html#cross-platform-serialization-compatibility-in-hyperion + +To see the [full set of fixes in Akka.NET v1.4.19, please see the milestone on Github](https://github.com/akkadotnet/akka.net/milestone/49). + +| COMMITS | LOC+ | LOC- | AUTHOR | +| --- | --- | --- | --- | +| 38 | 6092 | 4422 | Aaron Stannard | +| 13 | 2231 | 596 | Gregorius Soedharmo | +| 10 | 15 | 14 | dependabot-preview[bot] | +| 3 | 512 | 306 | zbynek001 | +| 3 | 417 | 1 | Ismael Hamed | +| 1 | 5 | 5 | Erik Følstad | +| 1 | 5 | 19 | Arjen Smits | +| 1 | 27 | 1 | Anton V. Ilyin | +| 1 | 21 | 33 | Igor | +| 1 | 1 | 1 | Cagatay YILDIZOGLU | + #### 1.4.18 March 23 2021 #### **Maintenance Release for Akka.NET 1.4** diff --git a/build.fsx b/build.fsx index 5eb5cd5f314..f75551d0664 100644 --- a/build.fsx +++ b/build.fsx @@ -348,6 +348,7 @@ Target "MultiNodeTests" (fun _ -> Target "MultiNodeTestsNetCore" (fun _ -> if not skipBuild.Value then + setEnvironVar "AKKA_CLUSTER_ASSERT" "on" // needed to enable assert invariants for Akka.Cluster let multiNodeTestPath = findToolInSubPath "Akka.MultiNodeTestRunner.dll" (currentDirectory @@ "src" @@ "core" @@ "Akka.MultiNodeTestRunner" @@ "bin" @@ "Release" @@ testNetCoreVersion @@ "win10-x64" @@ "publish") let projects = @@ -388,6 +389,7 @@ Target "MultiNodeTestsNetCore" (fun _ -> ) Target "MultiNodeTestsNet" (fun _ -> if not skipBuild.Value then + setEnvironVar "AKKA_CLUSTER_ASSERT" "on" // needed to enable assert invariants for Akka.Cluster let multiNodeTestPath = findToolInSubPath "Akka.MultiNodeTestRunner.dll" (currentDirectory @@ "src" @@ "core" @@ "Akka.MultiNodeTestRunner" @@ "bin" @@ "Release" @@ testNetVersion @@ "win10-x64" @@ "publish") let projects = @@ -626,7 +628,6 @@ Target "Protobuf" <| fun _ -> |> append (sprintf "-I=%s" (__SOURCE_DIRECTORY__ @@ "/src/protobuf/common") ) |> append (sprintf "--csharp_out=internal_access:%s" (__SOURCE_DIRECTORY__ @@ destinationPath)) |> append "--csharp_opt=file_extension=.g.cs" - |> append "--experimental_allow_proto3_optional" |> append (__SOURCE_DIRECTORY__ @@ "/src/protobuf" @@ protoName) |> toText diff --git a/docs/articles/actors/dispatchers.md b/docs/articles/actors/dispatchers.md index 73ee10f96b9..47bf76e5b8e 100644 --- a/docs/articles/actors/dispatchers.md +++ b/docs/articles/actors/dispatchers.md @@ -74,6 +74,7 @@ Some dispatcher configurations are available out-of-the-box for convenience. You * **task-dispatcher** - A configuration that uses the [TaskDispatcher](#taskdispatcher). * **default-fork-join-dispatcher** - A configuration that uses the [ForkJoinDispatcher](#forkjoindispatcher). * **synchronized-dispatcher** - A configuration that uses the [SynchronizedDispatcher](#synchronizeddispatcher). +* **channel-executor** - new as of v1.4.19, the [`ChannelExecutor`](#channelexecutor) is used to run on top of the .NET `ThreadPool` and allow Akka.NET to dynamically scale thread usage up and down with demand in exchange for better CPU and throughput performance. ## Built-in Dispatchers @@ -165,6 +166,63 @@ private void Form1_Load(object sender, System.EventArgs e) } ``` +### `ChannelExecutor` +In Akka.NET v1.4.19 we will be introducing an opt-in feature, the `ChannelExecutor` - a new dispatcher type that re-uses the same configuration as a `ForkJoinDispatcher` but runs entirely on top of the .NET `ThreadPool` and is able to take advantage of dynamic thread pool scaling to size / resize workloads on the fly. + +During its initial development and benchmarks, we observed the following: + +1. The `ChannelExecutor` tremendously reduced idle CPU and max busy CPU even during peak message throughput, primarily as a result of dynamically shrinking the total `ThreadPool` to only the necessary size. This resolves one of the largest complaints large users of Akka.NET have today. However, **in order for this setting to be effective `ThreadPool.SetMin(0,0)` must also be set**. We are considering doing this inside the `ActorSystem.Create` method, those settings don't work for you you can easily override them by simply calling `ThreadPool.SetMin(yourValue, yourValue)` again after `ActorSystem.Create` has exited. +2. The `ChannelExecutor` actually beat the `ForkJoinDispatcher` and others on performance even in environments like Docker and bare metal on Windows. + +> [!NOTE] +> We are in the process of gathering data from users on how well `ChannelExecutor` performs in the real world. If you are interested in trying out the `ChannelExecutor`, please read the directions in this document and then comment on [the "Akka.NET v1.4.19: ChannelExecutor performance data" discussion thread](https://github.com/akkadotnet/akka.net/discussions/4983). + +The `ChannelExectuor` re-uses the same threading settings as the `ForkJoinExecutor` to determine its effective upper and lower parallelism limits, and you can configure the `ChannelExecutor` to run inside your `ActorSystem` via the following HOCON configuration: + +``` +akka.actor.default-dispatcher = { + executor = channel-executor + fork-join-executor { #channelexecutor will re-use these settings + parallelism-min = 2 + parallelism-factor = 1 + parallelism-max = 64 + } +} + +akka.actor.internal-dispatcher = { + executor = channel-executor + throughput = 5 + fork-join-executor { + parallelism-min = 4 + parallelism-factor = 1.0 + parallelism-max = 64 + } +} + +akka.remote.default-remote-dispatcher { + type = Dispatcher + executor = channel-executor + fork-join-executor { + parallelism-min = 2 + parallelism-factor = 0.5 + parallelism-max = 16 + } +} + +akka.remote.backoff-remote-dispatcher { + executor = channel-executor + fork-join-executor { + parallelism-min = 2 + parallelism-max = 2 + } +} +``` + +This will enable the `ChannelExecutor` to run everywhere and all Akka.NET loads, with the exception of anything you manually allocate onto a `ForkJoinDispatcher` or `PinnedDispatcher`, will be managed by the `ThreadPool`. + +> [!IMPORTANT] +> As of Akka.NET v1.4.19, we call `ThreadPool.SetMinThreads(0,0)` inside the `ActorSystem.Create` method as we've found that the default `ThreadPool` minimum values have a negative impact on performance. However, if this causes undesireable side effects for you inside your application you can always override those settings by calling `ThreadPool.SetMinThreads(yourValue, yourValue)` again after you've created your `ActorSystem`. + #### Common Dispatcher Configuration The following configuration keys are available for any dispatcher configuration: diff --git a/docs/articles/actors/receive-actor-api.md b/docs/articles/actors/receive-actor-api.md index 82b258e9556..553d4194f30 100644 --- a/docs/articles/actors/receive-actor-api.md +++ b/docs/articles/actors/receive-actor-api.md @@ -68,33 +68,28 @@ system.ActorOf(DemoActor.Props(42), "demo"); Another good practice is to declare local messages (messages that are sent in process) within the Actor, which makes it easier to know what messages are generally being sent over the wire vs in process.: ```csharp -public class DemoActor : UntypedActor +public class DemoActor : ReceiveActor { - protected override void OnReceive(object message) + public DemoActor() { - switch (message) + Receive(x => { - case DemoActorLocalMessages.DemoActorLocalMessage1 msg1: - // Handle message here... - break; - case DemoActorLocalMessages.DemoActorLocalMessage2 msg2: - // Handle message here... - break; - default: - break; - } - } + // Handle message here... + }); - class DemoActorLocalMessages + Receive(x => { - public class DemoActorLocalMessage1 - { - } + // Handle message here... + }); + } - public class DemoActorLocalMessage2 - { - } - } + public class DemoActorLocalMessage1 + { + } + + public class DemoActorLocalMessage2 + { + } } ``` @@ -502,32 +497,6 @@ ReceiveAny(o => Console.WriteLine("Received object: " + o); Receive(0 => Console.WriteLine("Received object: " + o); ``` -### Non generic overloads -Receive has non generic overloads: -```csharp -Receive(typeof(string), obj => Console.WriteLine(obj.ToString()) ); -``` -Predicates can go before or after the handler: - -```csharp -Receive(typeof(string), obj => ((string) obj).Length > 5, obj => Console.WriteLine(obj.ToString()) ); -Receive(typeof(string), obj => Console.WriteLine(obj.ToString()), obj => ((string) obj).Length > 5 ); -``` -And the non generic Func - -```csharp -Receive(typeof(string), obj => - { - var s = (string)obj; - if (s.Length > 5) - { - Console.WriteLine("1: " + s); - return true; - } - return false; - }); -``` - ## Reply to messages If you want to have a handle for replying to a message, you can use `Sender`, which gives you an `IActorRef`. You can reply by sending to that `IActorRef` with `Sender.Tell(replyMsg, Self)`. You can also store the `IActorRef` for replying later, or passing on to other actors. If there is no sender (a message was sent without an actor or task context) then the sender defaults to a 'dead-letter' actor ref. @@ -703,7 +672,7 @@ public class HotSwapActor : ReceiveActor }); } - private void Angry(object message) + private void Angry() { Receive(s => s.Equals("foo"), msg => { @@ -716,7 +685,7 @@ public class HotSwapActor : ReceiveActor }); } - private void Happy(object message) + private void Happy() { Receive(s => s.Equals("foo"), msg => { @@ -834,8 +803,10 @@ Use `Kill` like this: victim.Tell(Akka.Actor.Kill.Instance, ActorRefs.NoSender); ``` -## Actors and exceptions -It can happen that while a message is being processed by an actor, that some kind of exception is thrown, e.g. a database exception. +## Actors and Exceptions +An exception can be thrown while a message is being processed by an actor, e.g. a database exception or some other type of runtime exception. + +When this occurs and the exception is not handled via a `try` / `catch` block, the actor's parent will be notified that its child failed with a specific exception type and will use its [supervision strategy](xref:supervision#what-supervision-means) to restart that child. ### What happens to the Message If an exception is thrown while a message is being processed (i.e. taken out of its mailbox and handed over to the current behavior), then this message will be lost. It is important to understand that it is not put back on the mailbox. So if you want to retry processing of a message, you need to deal with it yourself by catching the exception and retry your flow. Make sure that you put a bound on the number of retries since you don't want a system to livelock (so consuming a lot of cpu cycles without making progress). diff --git a/docs/articles/clustering/cluster-sharding.md b/docs/articles/clustering/cluster-sharding.md index e8b9b337a7f..7e6c4b424da 100644 --- a/docs/articles/clustering/cluster-sharding.md +++ b/docs/articles/clustering/cluster-sharding.md @@ -115,6 +115,43 @@ public sealed class MessageExtractor : HashCodeMessageExtractor Using `ShardRegion.StartEntity` implies, that you're able to infer a shard id given an entity id alone. For this reason, in example above we modified a cluster sharding routing logic to make use of `HashCodeMessageExtractor` - in this variant, shard id doesn't have to be provided explicitly, as it will be computed from the hash of entity id itself. Notice a `maxNumberOfShards`, which is the maximum available number of shards allowed for this type of an actor - this value must never change during a single lifetime of a cluster. +### Terminating remembered entities +One complication that `akka.cluster.sharding.remember-entities = true` introduces is that your sharded entity actors can no longer be terminated through the normal Akka.NET channels, i.e. `Context.Stop(Self)`, `PoisonPill.Instance`, and the like. This is because as part of the `remember-entities` contract - the sharding system is going to insist on keeping all remembered entities alive until explictily told to stop. + +To terminate a remembered entity, the sharded entity actor needs to send a [`Passivate` command](xref:Akka.Cluster.Sharding.Passivate) _to its parent actor_ in order to signal to the sharding system that we no longer need to remember this particular entity. + +```csharp +protected override bool ReceiveCommand(object message) +{ + switch (message) + { + case Increment _: + Persist(new CounterChanged(1), UpdateState); + return true; + case Decrement _: + Persist(new CounterChanged(-1), UpdateState); + return true; + case Get _: + Sender.Tell(_count); + return true; + case ReceiveTimeout _: + // send Passivate to parent (shard actor) to stop remembering this entity. + // shard actor will send us back a `Stop.Instance` message + // as our "shutdown" signal - at which point we can terminate normally. + Context.Parent.Tell(new Passivate(Stop.Instance)); + return true; + case Stop _: + Context.Stop(Self); + return true; + } + return false; +} +``` + +It is common to simply use `Context.Parent.Tell(new Passivate(PoisonPill.Instance));` to passivate and shutdown remembered-entity actors. + +To recreate a remembered entity actor after it has been passivated all you have to do is message the `ShardRegion` actor with a message containing the entity's `EntityId` again just like how you instantiated the actor the first time. + ## Retrieving sharding state You can inspect current sharding stats by using following messages: diff --git a/docs/articles/clustering/cluster-singleton.md b/docs/articles/clustering/cluster-singleton.md index 7b31fcdd8a6..37c3e60a267 100644 --- a/docs/articles/clustering/cluster-singleton.md +++ b/docs/articles/clustering/cluster-singleton.md @@ -34,9 +34,6 @@ This pattern may seem to be very tempting to use at first, but it has several dr Especially the last point is something you should be aware of — in general when using the Cluster Singleton pattern you should take care of downing nodes yourself and not rely on the timing based auto-down feature. -> [!WARNING] -> Be very careful when using Cluster Singleton together with Automatic Downing, since it allows the cluster to split up into two separate clusters, which in turn will result in `multiple Singletons` being started, one in each separate cluster! - ## An Example Assume that we need one single entry point to an external system. An actor that receives messages from a JMS queue with the strict requirement that only one JMS consumer must exist to be make sure that the messages are processed in order. That is perhaps not how one would like to design things, but a typical real-world scenario when integrating with external systems. @@ -111,4 +108,4 @@ akka.cluster.singleton-proxy { # Maximum allowed buffer size is 10000. buffer-size = 1000 } -``` \ No newline at end of file +``` diff --git a/docs/articles/concepts/supervision.md b/docs/articles/concepts/supervision.md index 5e7b7ed72fc..563088c7a3e 100644 --- a/docs/articles/concepts/supervision.md +++ b/docs/articles/concepts/supervision.md @@ -7,6 +7,8 @@ title: Supervision This document outlines the concept behind supervision and what that means for your Akka.NET actors at run-time. + + ## What Supervision Means As described in [Actor Systems](xref:actor-systems) supervision describes a dependency relationship between actors: the supervisor delegates tasks to subordinates and therefore must respond to their failures. When a subordinate detects a failure (i.e. throws an exception), it suspends itself and all its subordinates and sends a message to its supervisor, signaling failure. Depending on the nature of the work to be supervised and the nature of the failure, the supervisor has a choice of the following four options: diff --git a/docs/articles/networking/serialization.md b/docs/articles/networking/serialization.md index 6ea60eea99c..a78cc4f1f9c 100644 --- a/docs/articles/networking/serialization.md +++ b/docs/articles/networking/serialization.md @@ -179,6 +179,19 @@ The only thing left to do for this class would be to fill in the serialization l Afterwards the configuration would need to be updated to reflect which name to bind to and the classes that use this serializer. +### Programatically change NewtonSoft JSON serializer settings +You can change the JSON serializer behaviour by using the `NewtonSoftJsonSerializerSetup` class to programatically +change the settings used inside the Json serializer by passing it into the an `ActorSystemSetup`. + +[!code-csharp[Main](../../../src/core/Akka.Docs.Tests/Networking/Serialization/ProgrammaticJsonSerializerSetup.cs?name=CustomJsonSetup)] + +Note that, while we try to keep everything to be compatible, there are no guarantee that your specific serializer settings use case is compatible with the rest of +Akka.NET serialization schemes; please test your system in a development environment before deploying it into production. + +There are a couple limitation with this method, in that you can not change the `ObjectCreationHandling` and the `ContractResolver` settings +in the Json settings object. Those settings, by default, will always be overriden with `ObjectCreationHandling.Replace` and the [`AkkaContractResolver`](xref:Akka.Serialization.NewtonSoftJsonSerializer.AkkaContractResolver) +object respectively. + ### Serializer with String Manifest The `Serializer` illustrated above supports a class-based manifest (type hint). For serialization of data that need to evolve over time, the [`SerializerWithStringManifest`](xref:Akka.Serialization.SerializerWithStringManifest) is recommended instead of `Serializer` because the manifest (type hint) is a `String` instead of a `Type`. @@ -290,3 +303,80 @@ akka { } } ``` + +## Cross platform serialization compatibility in Hyperion +There are problems that can arise when migrating from old .NET Framework to the new .NET Core standard, mainly because of breaking namespace and assembly name changes between these platforms. +Hyperion implements a generic way of addressing this issue by transforming the names of these incompatible names during deserialization. + +There are two ways to set this up, one through the HOCON configuration file, and the other by using the `HyperionSerializerSetup` class. + +> [!NOTE] +> Only the first successful name transformation is applied, the rest are ignored. +> If you are matching several similar names, make sure that you order them from the most specific match to the least specific one. + +### HOCON +HOCON example: +``` +akka.actor.serialization-settings.hyperion.cross-platform-package-name-overrides = { + netfx = [ + { + fingerprint = "System.Private.CoreLib,%core%", + rename-from = "System.Private.CoreLib,%core%", + rename-to = "mscorlib,%core%" + }] + netcore = [ + { + fingerprint = "mscorlib,%core%", + rename-from = "mscorlib,%core%", + rename-to = "System.Private.CoreLib,%core%" + }] + net = [ + { + fingerprint = "mscorlib,%core%", + rename-from = "mscorlib,%core%", + rename-to = "System.Private.CoreLib,%core%" + }] +} +``` + +In the example above, we're addressing the classic case where the core library name was changed between `mscorlib` in .NET Framework to `System.Private.CoreLib` in .NET Core. +This transform is already included inside Hyperion as the default cross platform support, and used here as an illustration only. + +The HOCON configuration section is composed of three object arrays named `netfx`, `netcore`, and `net`, each corresponds, respectively, to .NET Framework, .NET Core, and the new .NET 5.0 and beyond. +The Hyperion serializer will automatically detects the platform it is running on currently and uses the correct array to use inside its deserializer. For example, if Hyperion detects +that it is running under .NET framework, then it will use the `netfx` array to do its deserialization transformation. + +The way it works that when the serializer detects that the type name contains the `fingerprint` string, it will replace the string declared in the `rename-from` +property into the string declared in the `rename-to`. + +In code, we can write this behaviour as: +```csharp +if(packageName.Contains(fingerprint)) packageName = packageName.Replace(rename-from, rename-to); +``` + +### HyperionSerializerSetup + +This behaviour can also be implemented programatically by providing a `HyperionSerializerSetup` instance during `ActorSystem` creation. + +```csharp +#if NETFRAMEWORK +var hyperionSetup = HyperionSerializerSetup.Empty + .WithPackageNameOverrides(new Func[] + { + str => str.Contains("System.Private.CoreLib,%core%") + ? str.Replace("System.Private.CoreLib,%core%", "mscorlib,%core%") : str + } +#elif NETCOREAPP +var hyperionSetup = HyperionSerializerSetup.Empty + .WithPackageNameOverrides(new Func[] + { + str => str.Contains("mscorlib,%core%") + ? str.Replace("mscorlib,%core%", "System.Private.CoreLib,%core%") : str + } +#endif + +var bootstrap = BootstrapSetup.Create().And(hyperionSetup); +var system = ActorSystem.Create("actorSystem", bootstrap); +``` + +In the example above, we're using compiler directives to make sure that the correct name transform are used during compilation. diff --git a/docs/docfx.json b/docs/docfx.json index 8ad6c7c8e3f..0e55c065863 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -68,7 +68,7 @@ "_appTitle": "Akka.NET Documentation", "_appLogoPath": "/images/akkalogo.png", "_appFaviconPath": "/images/favicon.ico", - "_appFooter": "Copyright © 2013-2019 Akka.NET project
Generated by DocFX", + "_appFooter": "Copyright © 2013-2021 Akka.NET project
Generated by DocFX", "_enableSearch": "true" }, "dest": "_site", diff --git a/src/benchmark/Akka.Benchmarks/Actor/ActorSelectionBenchmark.cs b/src/benchmark/Akka.Benchmarks/Actor/ActorSelectionBenchmark.cs new file mode 100644 index 00000000000..9ae388e1752 --- /dev/null +++ b/src/benchmark/Akka.Benchmarks/Actor/ActorSelectionBenchmark.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; +using Akka.Actor; +using Akka.Benchmarks.Configurations; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; + +namespace Akka.Benchmarks.Actor +{ + [Config(typeof(MicroBenchmarkConfig))] // need memory diagnosis + public class ActorSelectionBenchmark + { + [Params(10000)] + public int Iterations { get; set; } + private TimeSpan _timeout; + private ActorSystem _system; + private IActorRef _echo; + + // cached selection for measuring .Tell / .Ask performance + private ActorSelection _actorSelection; + + [GlobalSetup] + public void Setup() + { + _timeout = TimeSpan.FromMinutes(1); + _system = ActorSystem.Create("system"); + _echo = _system.ActorOf(Props.Create(() => new EchoActor()), "echo"); + _actorSelection = _system.ActorSelection("/user/echo"); + } + + [Benchmark] + public async Task RequestResponseActorSelection() + { + for(var i = 0; i < Iterations; i++) + await _actorSelection.Ask("foo", _timeout); + } + + [Benchmark] + public void CreateActorSelection() + { + for (var i = 0; i < Iterations; i++) + _system.ActorSelection("/user/echo"); + } + + [GlobalCleanup] + public void Cleanup() + { + _system.Terminate().Wait(); + } + + public class EchoActor : UntypedActor + { + protected override void OnReceive(object message) + { + Sender.Tell(message); + } + } + } +} diff --git a/src/benchmark/Akka.Benchmarks/Actor/PingPongBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Actor/PingPongBenchmarks.cs index 96c8c1ffc0e..3b6ca37a3b8 100644 --- a/src/benchmark/Akka.Benchmarks/Actor/PingPongBenchmarks.cs +++ b/src/benchmark/Akka.Benchmarks/Actor/PingPongBenchmarks.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Akka.Actor; using Akka.Benchmarks.Configurations; +using Akka.Configuration; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; diff --git a/src/benchmark/Akka.Benchmarks/Actor/SpawnActorBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Actor/SpawnActorBenchmarks.cs index 58f23235774..c6084cf26fe 100644 --- a/src/benchmark/Akka.Benchmarks/Actor/SpawnActorBenchmarks.cs +++ b/src/benchmark/Akka.Benchmarks/Actor/SpawnActorBenchmarks.cs @@ -15,38 +15,41 @@ namespace Akka.Benchmarks.Actor { [Config(typeof(MicroBenchmarkConfig))] - [SimpleJob(RunStrategy.Throughput, targetCount:10, warmupCount:5, invocationCount: ActorCount)] + [SimpleJob(RunStrategy.Throughput, targetCount:10, warmupCount:5)] public class SpawnActorBenchmarks { - public const int ActorCount = 100_000; - private TimeSpan timeout; + [Params(100_000)] + public int ActorCount { get;set; } private ActorSystem system; - [GlobalSetup] + [IterationSetup] public void Setup() { - timeout = TimeSpan.FromMinutes(1); system = ActorSystem.Create("system"); } - [GlobalCleanup] + [IterationCleanup] public void Cleanup() { - system.Dispose(); + system.Terminate().Wait(); } [Benchmark] - public void Actor_spawn() + public async Task Actor_spawn() { var parent = system.ActorOf(Parent.Props); + await parent.Ask(new StartTest(ActorCount), TimeSpan.FromMinutes(2)); } #region actors sealed class StartTest { - public static readonly StartTest Instance = new StartTest(); - private StartTest() { } + public StartTest(int actorCount) { + ActorCount = actorCount; + } + + public int ActorCount { get; } } sealed class ChildReady @@ -64,12 +67,13 @@ private TestDone() { } sealed class Parent : ReceiveActor { public static readonly Props Props = Props.Create(); - private int count = ActorCount - 1; // -1 because we also create the parent + private int count; private IActorRef replyTo; public Parent() { Receive(_ => { + count = _.ActorCount - 1; // -1 because we also create the parent replyTo = Sender; for (int i = 0; i < count; i++) { diff --git a/src/benchmark/Akka.Benchmarks/Akka.Benchmarks.csproj b/src/benchmark/Akka.Benchmarks/Akka.Benchmarks.csproj index cc004a045bc..36bfa3fcd9a 100644 --- a/src/benchmark/Akka.Benchmarks/Akka.Benchmarks.csproj +++ b/src/benchmark/Akka.Benchmarks/Akka.Benchmarks.csproj @@ -10,6 +10,8 @@ + + diff --git a/src/benchmark/Akka.Benchmarks/Cluster/HeartbeatNodeRingBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Cluster/HeartbeatNodeRingBenchmarks.cs new file mode 100644 index 00000000000..3577ed05def --- /dev/null +++ b/src/benchmark/Akka.Benchmarks/Cluster/HeartbeatNodeRingBenchmarks.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using Akka.Actor; +using Akka.Benchmarks.Configurations; +using Akka.Cluster; +using BenchmarkDotNet.Attributes; +using FluentAssertions; + +namespace Akka.Benchmarks.Cluster +{ + [Config(typeof(MicroBenchmarkConfig))] + public class HeartbeatNodeRingBenchmarks + { + [Params(10, 100, 250)] + public int NodesSize; + + + internal static HeartbeatNodeRing CreateHearbeatNodeRingOfSize(int size) + { + var nodes = Enumerable.Range(1, size) + .Select(x => new UniqueAddress(new Address("akka", "sys", "node-" + x, 2552), x)) + .ToList(); + var selfAddress = nodes[size / 2]; + return new HeartbeatNodeRing(selfAddress, nodes.ToImmutableHashSet(), ImmutableHashSet.Empty, 5); + } + + private HeartbeatNodeRing _ring; + + [GlobalSetup] + public void Setup() + { + _ring = CreateHearbeatNodeRingOfSize(NodesSize); + } + + private static void MyReceivers(HeartbeatNodeRing ring) + { + var r = new HeartbeatNodeRing(ring.SelfAddress, ring.Nodes, ImmutableHashSet.Empty, ring.MonitoredByNumberOfNodes); + r.MyReceivers.Value.Count.Should().BeGreaterThan(0); + } + + [Benchmark] + [Arguments(1000)] + public void HeartbeatNodeRing_should_produce_MyReceivers(int iterations) + { + for(var i = 0; i < iterations; i++) + MyReceivers(_ring); + } + } +} diff --git a/src/benchmark/Akka.Benchmarks/Cluster/ReachabilityBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Cluster/ReachabilityBenchmarks.cs new file mode 100644 index 00000000000..fc8362a2bd8 --- /dev/null +++ b/src/benchmark/Akka.Benchmarks/Cluster/ReachabilityBenchmarks.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Akka.Util; +using Akka.Actor; +using Akka.Benchmarks.Configurations; +using Akka.Cluster; +using BenchmarkDotNet.Attributes; +using FluentAssertions; + +namespace Akka.Benchmarks.Cluster +{ + [Config(typeof(MicroBenchmarkConfig))] + public class ReachabilityBenchmarks + { + [Params(100)] + public int NodesSize; + + [Params(100)] + public int Iterations; + + public Address Address = new Address("akka", "sys", "a", 2552); + public Address Node = new Address("akka", "sys", "a", 2552); + + private Reachability CreateReachabilityOfSize(Reachability baseReachability, int size) + { + return Enumerable.Range(1, size).Aggregate(baseReachability, (r, i) => + { + var obs = new UniqueAddress(Address.WithHost("node-" + i), i); + var j = i == size ? 1 : i + 1; + var subject = new UniqueAddress(Address.WithHost("node-" + j), j); + return r.Unreachable(obs, subject).Reachable(obs, subject); + }); + } + + private Reachability AddUnreachable(Reachability baseReachability, int count) + { + var observers = baseReachability.Versions.Keys.Take(count); + // the Keys HashSet IEnumerator does not support Reset, hence why we have to convert it to a list + using var subjects = baseReachability.Versions.Keys.ToList().GetContinuousEnumerator(); + return observers.Aggregate(baseReachability, (r, o) => + { + return Enumerable.Range(1, 5).Aggregate(r, (r2, i) => + { + subjects.MoveNext(); + return r2.Unreachable(o, subjects.Current); + }); + }); + } + + internal Reachability Reachability1; + internal Reachability Reachability2; + internal Reachability Reachability3; + internal ImmutableHashSet Allowed; + + [GlobalSetup] + public void Setup() + { + Reachability1 = CreateReachabilityOfSize(Reachability.Empty, NodesSize); + Reachability2 = CreateReachabilityOfSize(Reachability1, NodesSize); + Reachability3 = AddUnreachable(Reachability1, NodesSize / 2); + Allowed = Reachability1.Versions.Keys.ToImmutableHashSet(); + } + + private void CheckThunkFor(Reachability r1, Reachability r2, Action thunk, + int times) + { + for (var i = 0; i < times; i++) + thunk(new Reachability(r1.Records, r1.Versions), new Reachability(r2.Records, r2.Versions)); + } + + private void CheckThunkFor(Reachability r1, Action thunk, int times) + { + for (var i = 0; i < times; i++) + thunk(new Reachability(r1.Records, r1.Versions)); + } + + private void Merge(Reachability r1, Reachability r2, int expectedRecords) + { + r1.Merge(Allowed, r2).Records.Count.Should().Be(expectedRecords); + } + + private void CheckStatus(Reachability r1) + { + var record = r1.Records.First(); + r1.Status(record.Observer, record.Subject).Should().Be(record.Status); + } + + private void CheckAggregatedStatus(Reachability r1) + { + var record = r1.Records.First(); + r1.Status(record.Subject).Should().Be(record.Status); + } + + private void AllUnreachableOrTerminated(Reachability r1) + { + r1.AllUnreachableOrTerminated.IsEmpty.Should().BeFalse(); + } + + private void AllUnreachable(Reachability r1) + { + r1.AllUnreachable.IsEmpty.Should().BeFalse(); + } + + private void RecordsFrom(Reachability r1) + { + foreach (var o in r1.AllObservers) + { + r1.RecordsFrom(o).Should().NotBeNull(); + } + } + + [Benchmark] + public void Reachability_must_merge_with_same_versions() + { + CheckThunkFor(Reachability1, Reachability1, (r1, r2) => Merge(r1, r2, 0), Iterations); + } + + [Benchmark] + public void Reachability_must_merge_with_all_older_versions() + { + CheckThunkFor(Reachability2, Reachability1, (r1, r2) => Merge(r1, r2, 0), Iterations); + } + + [Benchmark] + public void Reachability_must_merge_with_all_newer_versions() + { + CheckThunkFor(Reachability1, Reachability2, (r1, r2) => Merge(r1, r2, 0), Iterations); + } + + [Benchmark] + public void Reachability_must_merge_with_half_nodes_unreachable() + { + CheckThunkFor(Reachability1, Reachability3, (r1, r2) => Merge(r1, r2, 5* NodesSize/2), Iterations); + } + + [Benchmark] + public void Reachability_must_merge_with_half_nodes_unreachable_opposite() + { + CheckThunkFor(Reachability3, Reachability1, (r1, r2) => Merge(r1, r2, 5 * NodesSize / 2), Iterations); + } + + [Benchmark] + public void Reachability_must_check_status_with_half_nodes_unreachable() + { + CheckThunkFor(Reachability3, CheckAggregatedStatus, Iterations); + } + + [Benchmark] + public void Reachability_must_check_AllUnreachableOrTerminated_with_half_nodes_unreachable() + { + CheckThunkFor(Reachability3, AllUnreachableOrTerminated, Iterations); + } + + [Benchmark] + public void Reachability_must_check_AllUnreachable_with_half_nodes_unreachable() + { + CheckThunkFor(Reachability3, AllUnreachable, Iterations); + } + + [Benchmark] + public void Reachability_must_check_RecordsFrom_with_half_nodes_unreachable() + { + CheckThunkFor(Reachability3, RecordsFrom, Iterations); + } + } +} diff --git a/src/benchmark/Akka.Benchmarks/Cluster/VectorClockBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Cluster/VectorClockBenchmarks.cs new file mode 100644 index 00000000000..65c2a3b29f6 --- /dev/null +++ b/src/benchmark/Akka.Benchmarks/Cluster/VectorClockBenchmarks.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using Akka.Benchmarks.Configurations; +using Akka.Cluster; +using BenchmarkDotNet.Attributes; +using FluentAssertions; +using static Akka.Cluster.VectorClock; + +namespace Akka.Benchmarks.Cluster +{ + [Config(typeof(MicroBenchmarkConfig))] + public class VectorClockBenchmarks + { + [Params(100)] + public int ClockSize; + + [Params(1000)] + public int Iterations; + + internal (VectorClock clock, ImmutableSortedSet nodes) CreateVectorClockOfSize(int size) + { + return Enumerable.Range(1, size) + .Aggregate((VectorClock.Create(), ImmutableSortedSet.Empty), + (tuple, i) => + { + var (vc, nodes) = tuple; + var node = Node.Create(i.ToString()); + return (vc.Increment(node), nodes.Add(node)); + }); + } + + internal VectorClock CopyVectorClock(VectorClock vc) + { + var versions = vc.Versions.Aggregate(ImmutableSortedDictionary.Empty, + (newVersions, pair) => + { + return newVersions.SetItem(Node.FromHash(pair.Key.ToString()), pair.Value); + }); + + return VectorClock.Create(versions); + } + + private Node _firstNode; + private Node _lastNode; + private Node _middleNode; + private ImmutableSortedSet _nodes; + private VectorClock _vcBefore; + private VectorClock _vcBaseLast; + private VectorClock _vcAfterLast; + private VectorClock _vcConcurrentLast; + private VectorClock _vcBaseMiddle; + private VectorClock _vcAfterMiddle; + private VectorClock _vcConcurrentMiddle; + + [GlobalSetup] + public void Setup() + { + var (vcBefore, nodes) = CreateVectorClockOfSize(ClockSize); + _vcBefore = vcBefore; + _nodes = nodes; + + _firstNode = nodes.First(); + _lastNode = nodes.Last(); + _middleNode = nodes[ClockSize / 2]; + + _vcBaseLast = vcBefore.Increment(_lastNode); + _vcAfterLast = _vcBaseLast.Increment(_firstNode); + _vcConcurrentLast = _vcBaseLast.Increment(_lastNode); + _vcBaseMiddle = _vcBefore.Increment(_middleNode); + _vcAfterMiddle = _vcBaseMiddle.Increment(_firstNode); + _vcConcurrentMiddle = _vcBaseMiddle.Increment(_middleNode); + } + + private void CheckThunkFor(VectorClock vc1, VectorClock vc2, Action thunk, int times) + { + var vcc1 = CopyVectorClock(vc1); + var vcc2 = CopyVectorClock(vc2); + for (var i = 0; i < times; i++) + { + thunk(vcc1, vcc2); + } + } + + private void CompareTo(VectorClock vc1, VectorClock vc2, Ordering ordering) + { + vc1.CompareTo(vc2).Should().Be(ordering); + } + + private void NotEqual(VectorClock vc1, VectorClock vc2) + { + (vc1 == vc2).Should().BeFalse(); + } + + [Benchmark] + public void VectorClock_comparisons_should_compare_same() + { + CheckThunkFor(_vcBaseLast, _vcBaseLast, (clock, vectorClock) => CompareTo(clock, vectorClock, Ordering.Same), Iterations); + } + + [Benchmark] + public void VectorClock_comparisons_should_compare_Before_last() + { + CheckThunkFor(_vcBefore, _vcBaseLast, (clock, vectorClock) => CompareTo(clock, vectorClock, Ordering.Before), Iterations); + } + + [Benchmark] + public void VectorClock_comparisons_should_compare_After_last() + { + CheckThunkFor(_vcAfterLast, _vcBaseLast, (clock, vectorClock) => CompareTo(clock, vectorClock, Ordering.After), Iterations); + } + + [Benchmark] + public void VectorClock_comparisons_should_compare_Concurrent_last() + { + CheckThunkFor(_vcAfterLast, _vcConcurrentLast, (clock, vectorClock) => CompareTo(clock, vectorClock, Ordering.Concurrent), Iterations); + } + + [Benchmark] + public void VectorClock_comparisons_should_compare_Before_middle() + { + CheckThunkFor(_vcBefore, _vcBaseMiddle, (clock, vectorClock) => CompareTo(clock, vectorClock, Ordering.Before), Iterations); + } + + [Benchmark] + public void VectorClock_comparisons_should_compare_After_middle() + { + CheckThunkFor(_vcAfterMiddle, _vcBaseMiddle, (clock, vectorClock) => CompareTo(clock, vectorClock, Ordering.After), Iterations); + } + + [Benchmark] + public void VectorClock_comparisons_should_compare_Concurrent_middle() + { + CheckThunkFor(_vcAfterMiddle, _vcConcurrentMiddle, (clock, vectorClock) => CompareTo(clock, vectorClock, Ordering.Concurrent), Iterations); + } + + [Benchmark] + public void VectorClock_comparisons_should_compare_notEquals_Before_Middle() + { + CheckThunkFor(_vcBefore, _vcBaseMiddle, (clock, vectorClock) => NotEqual(clock, vectorClock), Iterations); + } + + [Benchmark] + public void VectorClock_comparisons_should_compare_notEquals_After_Middle() + { + CheckThunkFor(_vcAfterMiddle, _vcBaseMiddle, (clock, vectorClock) => NotEqual(clock, vectorClock), Iterations); + } + + [Benchmark] + public void VectorClock_comparisons_should_compare_notEquals_Concurrent_Middle() + { + CheckThunkFor(_vcAfterMiddle, _vcConcurrentMiddle, (clock, vectorClock) => NotEqual(clock, vectorClock), Iterations); + } + } +} diff --git a/src/benchmark/PingPong/Program.cs b/src/benchmark/PingPong/Program.cs index 9ddecd3b52a..75d48e4a746 100644 --- a/src/benchmark/PingPong/Program.cs +++ b/src/benchmark/PingPong/Program.cs @@ -67,23 +67,23 @@ private static async void Start(uint timesToRun, bool testAsync) return; } -#if THREADS - int workerThreads; - int completionPortThreads; - ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads); + Console.WriteLine("Warming up..."); + //Warm up + await ActorSystem.Create("WarmupSystem").Terminate(); - Console.WriteLine("Worker threads: {0}", workerThreads); + // print statistics AFTER warm up to observe ActorSystem impact on ThreadCount + + Console.WriteLine("OSVersion: {0}", Environment.OSVersion); -#endif Console.WriteLine("ProcessorCount: {0}", processorCount); Console.WriteLine("ClockSpeed: {0} MHZ", CpuSpeed()); Console.WriteLine("Actor Count: {0}", processorCount * 2); Console.WriteLine("Messages sent/received: {0} ({0:0e0})", GetTotalMessagesReceived(repeat)); Console.WriteLine("Is Server GC: {0}", GCSettings.IsServerGC); + Console.WriteLine("Thread count: {0}", Process.GetCurrentProcess().Threads.Count); Console.WriteLine(); - //Warm up - ActorSystem.Create("WarmupSystem").Terminate(); + Console.Write("ActorBase first start time: "); await Benchmark(1, 1, 1, PrintStats.StartTimeOnly, -1, -1); Console.WriteLine(" ms"); @@ -206,9 +206,9 @@ public static IEnumerable GetThroughputSettings() await Task.WhenAll(tasks.ToArray()); sw.Stop(); - - system.Terminate(); totalWatch.Stop(); + await system.Terminate(); // force full ActorSystem termination + var elapsedMilliseconds = sw.ElapsedMilliseconds; long throughput = elapsedMilliseconds == 0 ? -1 : totalMessagesReceived / elapsedMilliseconds * 1000; diff --git a/src/benchmark/RemotePingPong/Program.cs b/src/benchmark/RemotePingPong/Program.cs index 0fa48305602..969e48315e5 100644 --- a/src/benchmark/RemotePingPong/Program.cs +++ b/src/benchmark/RemotePingPong/Program.cs @@ -44,7 +44,7 @@ public static uint CpuSpeed() public static Config CreateActorSystemConfig(string actorSystemName, string ipOrHostname, int port) { var baseConfig = ConfigurationFactory.ParseString(@" - akka { + akka { actor.provider = remote loglevel = ERROR suppress-json-serializer-warning = on @@ -57,6 +57,7 @@ public static Config CreateActorSystemConfig(string actorSystemName, string ipOr port = 0 hostname = ""localhost"" } + } }"); @@ -67,7 +68,7 @@ public static Config CreateActorSystemConfig(string actorSystemName, string ipOr return bindingConfig.WithFallback(baseConfig); } - private static void Main(params string[] args) + private static async Task Main(params string[] args) { Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; uint timesToRun; @@ -76,14 +77,12 @@ private static void Main(params string[] args) timesToRun = 1u; } - Start(timesToRun); - Console.ReadKey(); + await Start(timesToRun); } - private static async void Start(uint timesToRun) - { - const long repeat = 100000L; + private static bool _firstRun = true; + private static void PrintSysInfo(){ var processorCount = Environment.ProcessorCount; if (processorCount == 0) { @@ -92,24 +91,25 @@ private static async void Start(uint timesToRun) return; } -#if THREADS - int workerThreads; - int completionPortThreads; - ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads); - - Console.WriteLine("Worker threads: {0}", workerThreads); Console.WriteLine("OSVersion: {0}", Environment.OSVersion); -#endif Console.WriteLine("ProcessorCount: {0}", processorCount); Console.WriteLine("ClockSpeed: {0} MHZ", CpuSpeed()); Console.WriteLine("Actor Count: {0}", processorCount * 2); Console.WriteLine("Messages sent/received per client: {0} ({0:0e0})", repeat*2); Console.WriteLine("Is Server GC: {0}", GCSettings.IsServerGC); + Console.WriteLine("Thread count: {0}", Process.GetCurrentProcess().Threads.Count); Console.WriteLine(); //Print tables Console.WriteLine("Num clients, Total [msg], Msgs/sec, Total [ms]"); + _firstRun = false; + } + + const long repeat = 100000L; + + private static async Task Start(uint timesToRun) + { for (var i = 0; i < timesToRun; i++) { var redCount = 0; @@ -182,6 +182,12 @@ private static long GetTotalMessagesReceived(int numberOfClients, long numberOfR throw new Exception("Received report that 1 or more remote actor is unable to begin the test. Aborting run."); } + // now that the dispatchers in both ActorSystems are started, we want to measure thread count and other system + // metrics here - but only the very first benchmark + if(_firstRun){ + PrintSysInfo(); + } + var sw = Stopwatch.StartNew(); receivers.ForEach(c => { @@ -193,7 +199,7 @@ private static long GetTotalMessagesReceived(int numberOfClients, long numberOfR sw.Stop(); // force clean termination - var termination = Task.WhenAll(new[] { system1.Terminate(), system2.Terminate() }).Wait(TimeSpan.FromSeconds(10)); + await Task.WhenAll(new[] { system1.Terminate(), system2.Terminate() }); var elapsedMilliseconds = sw.ElapsedMilliseconds; long throughput = elapsedMilliseconds == 0 ? -1 : (long)Math.Ceiling((double)totalMessagesReceived / elapsedMilliseconds * 1000); diff --git a/src/benchmark/SerializationBenchmarks/Program.cs b/src/benchmark/SerializationBenchmarks/Program.cs index e0deb152f90..bf322edb785 100644 --- a/src/benchmark/SerializationBenchmarks/Program.cs +++ b/src/benchmark/SerializationBenchmarks/Program.cs @@ -1,4 +1,11 @@ -using System; +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; using System.Runtime.CompilerServices; using Akka.Actor; using Akka.Configuration; @@ -83,4 +90,4 @@ public class MyType public string SomeStr { get; set; } public int SomeInt { get; set; } } -} \ No newline at end of file +} diff --git a/src/common.props b/src/common.props index 3a3e4f72bd0..bcf07da8e3f 100644 --- a/src/common.props +++ b/src/common.props @@ -2,7 +2,7 @@ Copyright © 2013-2021 Akka.NET Team Akka.NET Team - 1.4.18 + 1.4.19 https://getakka.net/images/akkalogo.png https://github.com/akkadotnet/akka.net https://github.com/akkadotnet/akka.net/blob/master/LICENSE @@ -10,17 +10,17 @@ 2.4.1 - 16.9.1 - 0.9.16 - 12.0.3 + 16.9.4 + 0.10.1 + 13.0.1 2.0.1 - 3.15.6 + 3.15.8 netcoreapp3.1 net5.0 net471 netstandard2.0 5.10.3 - 2.15.1 + 2.15.2 2.0.3 4.7.0 akka;actors;actor model;Akka;concurrency @@ -29,7 +29,7 @@ true - Placeholder for Nightlies** + Placeholder for nightlies** diff --git a/src/contrib/cluster/Akka.Cluster.Metrics.Tests/ClusterMetricsExtensionSpec.cs b/src/contrib/cluster/Akka.Cluster.Metrics.Tests/ClusterMetricsExtensionSpec.cs index 9bde8987216..12c2243dc4a 100644 --- a/src/contrib/cluster/Akka.Cluster.Metrics.Tests/ClusterMetricsExtensionSpec.cs +++ b/src/contrib/cluster/Akka.Cluster.Metrics.Tests/ClusterMetricsExtensionSpec.cs @@ -21,7 +21,7 @@ public class ClusterMetricsExtensionSpec : AkkaSpec { private readonly ClusterMetrics _extension; private readonly ClusterMetricsView _metricsView; - private TimeSpan _sampleInterval; + private readonly TimeSpan _sampleInterval; private int MetricsNodeCount => _metricsView.ClusterMetrics.Count; private int MetricsHistorySize => _metricsView.MetricsHistory.Count; @@ -65,43 +65,31 @@ public async Task Metrics_extension_Should_collect_metrics_after_start_command() MetricsHistorySize.Should().BeGreaterOrEqualTo(beforeStop); } - [Fact(Skip = "Racy")] + [Fact] public async Task Metrics_extension_Should_control_collector_on_off_state() { - int size2 = 0, size3 = 0, size4 = 0; - for (var i = 0; i < 3; ++i) + // store initial size + var sizeBefore = MetricsHistorySize; + + // start collecting + _extension.Supervisor.Tell(ClusterMetricsSupervisorMetadata.CollectionStartMessage.Instance); + + // some metrics should be collected + await AwaitAssertAsync(() => { - var size1 = MetricsHistorySize; - await AwaitSampleAsync(); - await AwaitAssertAsync(() => - { - size2 = MetricsHistorySize; - size1.Should().Be(size2); - }, SampleCollectTimeout); + MetricsHistorySize.Should().BeGreaterThan(sizeBefore); + }, TimeSpan.FromSeconds(30)); - _extension.Supervisor.Tell(ClusterMetricsSupervisorMetadata.CollectionStartMessage.Instance); - await AwaitSampleAsync(); - await AwaitAssertAsync(() => - { - size3 = MetricsHistorySize; - size3.Should().BeGreaterThan(size2); - }, SampleCollectTimeout); + // stop collection + _extension.Supervisor.Tell(ClusterMetricsSupervisorMetadata.CollectionStopMessage.Instance); - _extension.Supervisor.Tell(ClusterMetricsSupervisorMetadata.CollectionStopMessage.Instance); - await AwaitSampleAsync(); - await AwaitAssertAsync(() => - { - size4 = MetricsHistorySize; - size4.Should().BeGreaterOrEqualTo(size3); - }, SampleCollectTimeout); - - await AwaitSampleAsync(); - await AwaitAssertAsync(() => - { - var size5 = MetricsHistorySize; - size5.Should().Be(size4); - }, SampleCollectTimeout); - } + // wait for collection to be stopped + await AwaitSampleAsync(); + + // make sure collection does not proceed after sampling period + var sizeAfter = MetricsHistorySize; + await AwaitSampleAsync(); + MetricsHistorySize.Should().Be(sizeAfter); } private Task AwaitSampleAsync(double? timeMs = null) diff --git a/src/contrib/cluster/Akka.Cluster.Sharding/Serialization/Proto/ClusterShardingMessages.g.cs b/src/contrib/cluster/Akka.Cluster.Sharding/Serialization/Proto/ClusterShardingMessages.g.cs index e459688f633..b8a7a8d24d7 100644 --- a/src/contrib/cluster/Akka.Cluster.Sharding/Serialization/Proto/ClusterShardingMessages.g.cs +++ b/src/contrib/cluster/Akka.Cluster.Sharding/Serialization/Proto/ClusterShardingMessages.g.cs @@ -1,10 +1,3 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2021 Lightbend Inc. -// Copyright (C) 2013-2021 .NET Foundation -// -//----------------------------------------------------------------------- - // Generated by the protocol buffer compiler. DO NOT EDIT! // source: ClusterShardingMessages.proto #pragma warning disable 1591, 0612, 3021 diff --git a/src/contrib/cluster/Akka.Cluster.Sharding/ShardedDaemonProcess.cs b/src/contrib/cluster/Akka.Cluster.Sharding/ShardedDaemonProcess.cs index 3609c6b36ba..01d9983ac94 100644 --- a/src/contrib/cluster/Akka.Cluster.Sharding/ShardedDaemonProcess.cs +++ b/src/contrib/cluster/Akka.Cluster.Sharding/ShardedDaemonProcess.cs @@ -78,7 +78,7 @@ public MessageExtractor(int maxNumberOfShards) /// and have the message types themselves carry identifiers. /// /// - public sealed class ShardingEnvelope + public sealed class ShardingEnvelope: IWrappedMessage { public string EntityId { get; } public object Message { get; } diff --git a/src/contrib/cluster/Akka.Cluster.Sharding/ShardingMessages.cs b/src/contrib/cluster/Akka.Cluster.Sharding/ShardingMessages.cs index d331da02aa9..d2ab017c2dc 100644 --- a/src/contrib/cluster/Akka.Cluster.Sharding/ShardingMessages.cs +++ b/src/contrib/cluster/Akka.Cluster.Sharding/ShardingMessages.cs @@ -40,14 +40,14 @@ private Terminate() /// reduce memory consumption. This is done by the application specific implementation of /// the entity actors for example by defining receive timeout (). /// If a message is already enqueued to the entity when it stops itself the enqueued message - /// in the mailbox will be dropped. To support graceful passivation without loosing such + /// in the mailbox will be dropped. To support graceful passivation without losing such /// messages the entity actor can send this message to its parent . /// The specified wrapped will be sent back to the entity, which is /// then supposed to stop itself. Incoming messages will be buffered by the `ShardRegion` /// between reception of and termination of the entity. Such buffered messages /// are thereafter delivered to a new incarnation of the entity. /// - /// is a perfectly fine . + /// is a perfectly fine . /// [Serializable] public sealed class Passivate : IShardRegionCommand diff --git a/src/contrib/cluster/Akka.Cluster.Tools/Client/Serialization/Proto/ClusterClientMessages.g.cs b/src/contrib/cluster/Akka.Cluster.Tools/Client/Serialization/Proto/ClusterClientMessages.g.cs index 1a813d971ff..890e7fa9009 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/Client/Serialization/Proto/ClusterClientMessages.g.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/Client/Serialization/Proto/ClusterClientMessages.g.cs @@ -1,10 +1,3 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2021 Lightbend Inc. -// Copyright (C) 2013-2021 .NET Foundation -// -//----------------------------------------------------------------------- - // Generated by the protocol buffer compiler. DO NOT EDIT! // source: ClusterClientMessages.proto #pragma warning disable 1591, 0612, 3021 diff --git a/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/DistributedMessages.cs b/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/DistributedMessages.cs index 988eec784da..4d4ec65efbc 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/DistributedMessages.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/DistributedMessages.cs @@ -353,7 +353,7 @@ public override string ToString() /// TBD /// [Serializable] - public sealed class Publish : IDistributedPubSubMessage, IEquatable + public sealed class Publish : IDistributedPubSubMessage, IEquatable, IWrappedMessage { /// /// TBD @@ -420,7 +420,7 @@ public override string ToString() /// TBD /// [Serializable] - public sealed class Send : IDistributedPubSubMessage, IEquatable + public sealed class Send : IDistributedPubSubMessage, IEquatable, IWrappedMessage { /// /// TBD @@ -487,7 +487,7 @@ public override string ToString() /// TBD /// [Serializable] - public sealed class SendToAll : IDistributedPubSubMessage, IEquatable + public sealed class SendToAll : IDistributedPubSubMessage, IEquatable, IWrappedMessage { /// /// TBD diff --git a/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/Serialization/Proto/DistributedPubSubMessages.g.cs b/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/Serialization/Proto/DistributedPubSubMessages.g.cs index 3a78fdcd9bd..033c7a1a689 100644 --- a/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/Serialization/Proto/DistributedPubSubMessages.g.cs +++ b/src/contrib/cluster/Akka.Cluster.Tools/PublishSubscribe/Serialization/Proto/DistributedPubSubMessages.g.cs @@ -1,10 +1,3 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2021 Lightbend Inc. -// Copyright (C) 2013-2021 .NET Foundation -// -//----------------------------------------------------------------------- - // Generated by the protocol buffer compiler. DO NOT EDIT! // source: DistributedPubSubMessages.proto #pragma warning disable 1591, 0612, 3021 diff --git a/src/contrib/cluster/Akka.DistributedData.Tests.MultiNode/DurableDataPocoSpec.cs b/src/contrib/cluster/Akka.DistributedData.Tests.MultiNode/DurableDataPocoSpec.cs new file mode 100644 index 00000000000..9c24d4818f9 --- /dev/null +++ b/src/contrib/cluster/Akka.DistributedData.Tests.MultiNode/DurableDataPocoSpec.cs @@ -0,0 +1,418 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Collections.Immutable; +using System.IO; +using Akka.Actor; +using Akka.Cluster; +using Akka.Cluster.TestKit; +using Akka.Configuration; +using Akka.DistributedData.Durable; +using Akka.Remote.TestKit; +using Akka.TestKit; +using FluentAssertions; +using FluentAssertions.Extensions; + +namespace Akka.DistributedData.Tests.MultiNode +{ + public class DurableDataPocoSpecConfig : MultiNodeConfig + { + public RoleName First { get; } + public RoleName Second { get; } + public RoleName Third { get; } + + public DurableDataPocoSpecConfig(bool writeBehind) + { + First = Role("first"); + Second = Role("second"); + Third = Role("third"); + + var tempDir = Path.Combine(Path.GetTempPath(), "target", "DurableDataPocoSpec", $"Spec-{DateTime.UtcNow.Ticks}"); + CommonConfig = ConfigurationFactory.ParseString($@" + akka.loglevel = INFO + akka.log-config-on-start = off + akka.actor.provider = ""Akka.Cluster.ClusterActorRefProvider, Akka.Cluster"" + akka.log-dead-letters-during-shutdown = off + akka.cluster.distributed-data.durable.keys = [""durable*""] + akka.cluster.distributed-data.durable.lmdb {{ + map-size = 10 MiB + write-behind-interval = {(writeBehind ? "200ms" : "off")} + }} + akka.cluster.distributed-data.durable.store-actor-class = ""Akka.DistributedData.LightningDB.LmdbDurableStore, Akka.DistributedData.LightningDB"" + # initialization of lmdb can be very slow in CI environment + akka.test.single-expect-default = 15s") + .WithFallback(DistributedData.DefaultConfig()); + + NodeConfig(new[] { First }, new[] { ConfigurationFactory.ParseString($@" + akka.cluster.distributed-data.durable.lmdb {{ + dir = ""target/DurableDataPocoSpec/first-ddata"" + }} + ") }); + + NodeConfig(new[] { Second }, new[] { ConfigurationFactory.ParseString($@" + akka.cluster.distributed-data.durable.lmdb {{ + dir = ""target/DurableDataPocoSpec/second-ddata"" + }} + ") }); + + NodeConfig(new[] { Third }, new[] { ConfigurationFactory.ParseString($@" + akka.cluster.distributed-data.durable.lmdb {{ + dir = ""target/DurableDataPocoSpec/third-ddata"" + }} + ") }); + } + } + + internal sealed class PocoObject : IReplicatedData + { + public PocoObject(string transactionId, int created) + { + TransactionId = transactionId; + Created = created; + } + + public string TransactionId { get; } + public int Created { get; } + + public PocoObject Merge(PocoObject other) + => other != null && other.Created > Created ? other : this; + + public IReplicatedData Merge(IReplicatedData other) + => other is PocoObject state ? Merge(state) : this; + } + + public abstract class DurableDataPocoSpecBase : MultiNodeClusterSpec + { + private readonly RoleName _first; + private readonly RoleName _second; + private readonly RoleName _third; + private readonly Cluster.Cluster _cluster; + private readonly IWriteConsistency _writeThree; + private readonly IReadConsistency _readThree; + + private readonly ORDictionaryKey _keyA = new ORDictionaryKey("durable-A"); + private readonly ORDictionaryKey _keyB = new ORDictionaryKey("durable-B"); + private readonly ORSetKey _keyC = new ORSetKey("durable-C"); + + private int _testStepCounter = 0; + + protected DurableDataPocoSpecBase(DurableDataPocoSpecConfig config, Type type) : base(config, type) + { + _cluster = Akka.Cluster.Cluster.Get(Sys); + var timeout = Dilated(14.Seconds()); // initialization of lmdb can be very slow in CI environment + _writeThree = new WriteTo(3, timeout); + _readThree = new ReadFrom(3, timeout); + + _first = config.First; + _second = config.Second; + _third = config.Third; + } + + [MultiNodeFact] + public void DurableDataPocoSpec_Tests() + { + Durable_CRDT_should_work_in_a_single_node_cluster(); + Durable_CRDT_should_work_in_a_multi_node_cluster(); + Durable_CRDT_should_be_durable_after_gossip_update(); + Durable_CRDT_should_handle_Update_before_Load(); + } + + private void TellUpdate(IActorRef replicator, ORDictionaryKey key, IWriteConsistency consistency, PocoObject message) + { + var value = new PocoObject(message.TransactionId, message.Created); + replicator.Tell(Dsl.Update( + key, + ORDictionary.Empty, + consistency, + oldValue => + { + return oldValue.AddOrUpdate(_cluster, value.TransactionId, null, + oldTrxState => value.Merge(oldTrxState)); + } + )); + } + + public void Durable_CRDT_should_work_in_a_single_node_cluster() + { + Join(_first, _first); + + RunOn(() => + { + var r = NewReplicator(); + Within(TimeSpan.FromSeconds(10), () => + { + AwaitAssert(() => + { + r.Tell(Dsl.GetReplicaCount); + ExpectMsg(new ReplicaCount(1)); + }); + }); + + r.Tell(Dsl.Get(_keyA, ReadLocal.Instance)); + ExpectMsg(new NotFound(_keyA, null)); + + TellUpdate(r, _keyA, WriteLocal.Instance, new PocoObject("Id_1", 1)); + TellUpdate(r, _keyA, WriteLocal.Instance, new PocoObject("Id_1", 2)); + TellUpdate(r, _keyA, WriteLocal.Instance, new PocoObject("Id_1", 3)); + + ExpectMsg(new UpdateSuccess(_keyA, null)); + ExpectMsg(new UpdateSuccess(_keyA, null)); + ExpectMsg(new UpdateSuccess(_keyA, null)); + + Watch(r); + Sys.Stop(r); + ExpectTerminated(r); + + var r2 = default(IActorRef); + AwaitAssert(() => r2 = NewReplicator()); // try until name is free + + // note that it will stash the commands until loading completed + r2.Tell(Dsl.Get(_keyA, ReadLocal.Instance)); + var success = ExpectMsg().Get(_keyA); + success.TryGetValue("Id_1", out var value).Should().BeTrue(); + value.TransactionId.Should().Be("Id_1"); + value.Created.Should().Be(3); + + Watch(r2); + Sys.Stop(r2); + ExpectTerminated(r2); + + }, _first); + + EnterBarrierAfterTestStep(); + } + + public void Durable_CRDT_should_work_in_a_multi_node_cluster() + { + Join(_first, _first); + Join(_second, _first); + Join(_third, _first); + + var r = NewReplicator(); + Within(TimeSpan.FromSeconds(10), () => + { + AwaitAssert(() => + { + r.Tell(Dsl.GetReplicaCount); + ExpectMsg(new ReplicaCount(3)); + }); + }); + + EnterBarrier("both-initialized"); + + TellUpdate(r, _keyA, _writeThree, new PocoObject("Id_1", 4)); + ExpectMsg(new UpdateSuccess(_keyA, null)); + + r.Tell(Dsl.Update(_keyC, ORSet.Empty, _writeThree, c => c.Add(_cluster, Myself.Name))); + ExpectMsg(new UpdateSuccess(_keyC, null)); + + EnterBarrier("update-done-" + _testStepCounter); + + r.Tell(Dsl.Get(_keyA, _readThree)); + var success = ExpectMsg().Get(_keyA); + success.TryGetValue("Id_1", out var value).Should().BeTrue(); + value.TransactionId.Should().Be("Id_1"); + value.Created.Should().Be(4); + + r.Tell(Dsl.Get(_keyC, _readThree)); + ExpectMsg().Get(_keyC).Elements.ShouldBe(ImmutableHashSet.CreateRange(new[] { _first.Name, _second.Name, _third.Name })); + + EnterBarrier("values-verified-" + _testStepCounter); + + Watch(r); + Sys.Stop(r); + ExpectTerminated(r); + + var r2 = default(IActorRef); + AwaitAssert(() => r2 = NewReplicator()); // try until name is free + AwaitAssert(() => + { + r2.Tell(Dsl.GetKeyIds); + ExpectMsg().Keys.ShouldNotBe(ImmutableHashSet.Empty); + }); + + r2.Tell(Dsl.Get(_keyA, ReadLocal.Instance)); + success = ExpectMsg().Get(_keyA); + success.TryGetValue("Id_1", out value).Should().BeTrue(); + value.TransactionId.Should().Be("Id_1"); + value.Created.Should().Be(4); + + r2.Tell(Dsl.Get(_keyC, ReadLocal.Instance)); + ExpectMsg().Get(_keyC).Elements.ShouldBe(ImmutableHashSet.CreateRange(new[] { _first.Name, _second.Name, _third.Name })); + + EnterBarrierAfterTestStep(); + } + + public void Durable_CRDT_should_be_durable_after_gossip_update() + { + var r = NewReplicator(); + + RunOn(() => + { + Log.Debug("sending message with sender: {}", ActorCell.GetCurrentSelfOrNoSender()); + r.Tell(Dsl.Update(_keyC, ORSet.Empty, WriteLocal.Instance, c => c.Add(_cluster, Myself.Name))); + ExpectMsg(new UpdateSuccess(_keyC, null)); + }, _first); + + RunOn(() => + { + r.Tell(Dsl.Subscribe(_keyC, TestActor)); + ExpectMsg().Get(_keyC).Elements.ShouldBe(ImmutableHashSet.Create(_first.Name)); + + // must do one more roundtrip to be sure that it keyB is stored, since Changed might have + // been sent out before storage + TellUpdate(r, _keyA, WriteLocal.Instance, new PocoObject("Id_1", 3)); + ExpectMsg(new UpdateSuccess(_keyA, null)); + + Watch(r); + Sys.Stop(r); + ExpectTerminated(r); + + var r2 = default(IActorRef); + AwaitAssert(() => r2 = NewReplicator()); // try until name is free + AwaitAssert(() => + { + r2.Tell(Dsl.GetKeyIds); + ExpectMsg().Keys.ShouldNotBe(ImmutableHashSet.Empty); + }); + + r2.Tell(Dsl.Get(_keyC, ReadLocal.Instance)); + ExpectMsg().Get(_keyC).Elements.ShouldBe(ImmutableHashSet.Create(_first.Name)); + + }, _second); + + EnterBarrierAfterTestStep(); + } + + public void Durable_CRDT_should_handle_Update_before_Load() + { + RunOn(() => + { + var sys1 = ActorSystem.Create("AdditionalSys", Sys.Settings.Config); + var cluster1 = Akka.Cluster.Cluster.Get(sys1); + var address = cluster1.SelfAddress; + try + { + cluster1.Join(address); + /* new TestKit(sys1) with ImplicitSender */ + { + var r = NewReplicator(sys1); + Within(TimeSpan.FromSeconds(10), () => + { + AwaitAssert(() => + { + r.Tell(Dsl.GetReplicaCount); + ExpectMsg(new ReplicaCount(1)); + }); + }); + + r.Tell(Dsl.Get(_keyA, ReadLocal.Instance)); + ExpectMsg(new NotFound(_keyA, null)); + + TellUpdate(r, _keyA, WriteLocal.Instance, new PocoObject("Id_1", 5)); + TellUpdate(r, _keyA, WriteLocal.Instance, new PocoObject("Id_1", 6)); + TellUpdate(r, _keyA, WriteLocal.Instance, new PocoObject("Id_1", 7)); + TellUpdate(r, _keyB, WriteLocal.Instance, new PocoObject("Id_1", 1)); + + ExpectMsg(new UpdateSuccess(_keyA, null)); + ExpectMsg(new UpdateSuccess(_keyA, null)); + ExpectMsg(new UpdateSuccess(_keyA, null)); + ExpectMsg(new UpdateSuccess(_keyB, null)); + + Watch(r); + sys1.Stop(r); + ExpectTerminated(r); + } + } + finally + { + sys1.Terminate().Wait(TimeSpan.FromSeconds(10)); + } + + var sys2 = ActorSystem.Create( + "AdditionalSys", + ConfigurationFactory.ParseString($"akka.remote.dot-netty.tcp.port = {address.Port}") + .WithFallback(Sys.Settings.Config)); + try + { + var cluster2 = Akka.Cluster.Cluster.Get(sys2); + cluster2.Join(address); + /* new TestKit(sys1) with ImplicitSender */ + { + var r2 = NewReplicator(sys2); + + // it should be possible to update while loading is in progress + TellUpdate(r2, _keyB, WriteLocal.Instance, new PocoObject("Id_1", 2)); + ExpectMsg(new UpdateSuccess(_keyB, null)); + + // wait until all loaded + AwaitAssert(() => + { + r2.Tell(Dsl.GetKeyIds); + ExpectMsg().Keys.ShouldBe(ImmutableHashSet.CreateRange(new [] { _keyA.Id, _keyB.Id })); + }); + + r2.Tell(Dsl.Get(_keyA, ReadLocal.Instance)); + var success = ExpectMsg().Get(_keyA); + success.TryGetValue("Id_1", out var value).Should().BeTrue(); + value.TransactionId.Should().Be("Id_1"); + value.Created.Should().Be(7); + + r2.Tell(Dsl.Get(_keyB, ReadLocal.Instance)); + success = ExpectMsg().Get(_keyB); + success.TryGetValue("Id_1", out value).Should().BeTrue(); + value.TransactionId.Should().Be("Id_1"); + value.Created.Should().Be(2); + } + } + finally + { + sys2.Terminate().Wait(TimeSpan.FromSeconds(10)); + } + + }, _first); + Log.Info("Setup complete"); + EnterBarrierAfterTestStep(); + Log.Info("All setup complete"); + } + + private void EnterBarrierAfterTestStep() + { + _testStepCounter++; + EnterBarrier("after-" + _testStepCounter); + } + + private IActorRef NewReplicator(ActorSystem system = null) + { + if (system == null) system = Sys; + + return system.ActorOf(Replicator.Props( + ReplicatorSettings.Create(system).WithGossipInterval(TimeSpan.FromSeconds(1))), + "replicator-" + _testStepCounter); + } + + private void Join(RoleName from, RoleName to) + { + RunOn(() => + { + _cluster.Join(Node(to).Address); + }, from); + EnterBarrier(from.Name + "-joined"); + } + } + + public class DurableDataPocoSpec : DurableDataPocoSpecBase + { + public DurableDataPocoSpec() : base(new DurableDataPocoSpecConfig(writeBehind: false), typeof(DurableDataPocoSpec)) { } + } + + public class DurableDataWriteBehindPocoSpec : DurableDataPocoSpecBase + { + public DurableDataWriteBehindPocoSpec() : base(new DurableDataPocoSpecConfig(writeBehind: true), typeof(DurableDataWriteBehindPocoSpec)) { } + } +} diff --git a/src/contrib/cluster/Akka.DistributedData.Tests.MultiNode/DurablePruningSpec.cs b/src/contrib/cluster/Akka.DistributedData.Tests.MultiNode/DurablePruningSpec.cs index 32612323ba8..be36d749aad 100644 --- a/src/contrib/cluster/Akka.DistributedData.Tests.MultiNode/DurablePruningSpec.cs +++ b/src/contrib/cluster/Akka.DistributedData.Tests.MultiNode/DurablePruningSpec.cs @@ -7,7 +7,9 @@ using System; using System.Collections.Immutable; +using System.Linq; using Akka.Actor; +using Akka.Cluster; using Akka.Cluster.TestKit; using Akka.Configuration; using Akka.Remote.TestKit; @@ -48,20 +50,20 @@ public class DurablePruningSpec : MultiNodeClusterSpec private readonly GCounterKey keyA = new GCounterKey("A"); private readonly IActorRef replicator; - protected DurablePruningSpec() : this(new DurablePruningSpecConfig()) + public DurablePruningSpec() : this(new DurablePruningSpecConfig()) { } protected DurablePruningSpec(DurablePruningSpecConfig config) : base(config, typeof(DurablePruningSpec)) { - InitialParticipantsValueFactory = Roles.Count; cluster = Akka.Cluster.Cluster.Get(Sys); + replicator = StartReplicator(Sys); timeout = Dilated(TimeSpan.FromSeconds(5)); } - protected override int InitialParticipantsValueFactory { get; } + protected override int InitialParticipantsValueFactory => Roles.Count; - [MultiNodeFact(Skip = "FIXME")] + [MultiNodeFact] public void Pruning_of_durable_CRDT_should_move_data_from_removed_node() { Join(first, first); @@ -69,10 +71,20 @@ public void Pruning_of_durable_CRDT_should_move_data_from_removed_node() var sys2 = ActorSystem.Create(Sys.Name, Sys.Settings.Config); var cluster2 = Akka.Cluster.Cluster.Get(sys2); + var distributedData2 = DistributedData.Get(sys2); var replicator2 = StartReplicator(sys2); var probe2 = new TestProbe(sys2, new XunitAssertions()); cluster2.Join(Node(first).Address); + AwaitAssert(() => + { + cluster.State.Members.Count.ShouldBe(4); + cluster.State.Members.All(m => m.Status == MemberStatus.Up).ShouldBe(true); + cluster2.State.Members.Count.ShouldBe(4); + cluster2.State.Members.All(m => m.Status == MemberStatus.Up).ShouldBe(true); + }, TimeSpan.FromSeconds(10)); + EnterBarrier("joined"); + Within(TimeSpan.FromSeconds(5), () => AwaitAssert(() => { replicator.Tell(Dsl.GetReplicaCount); @@ -81,10 +93,10 @@ public void Pruning_of_durable_CRDT_should_move_data_from_removed_node() probe2.ExpectMsg(new ReplicaCount(4)); })); - replicator.Tell(Dsl.Update(keyA, GCounter.Empty, WriteLocal.Instance, c => c.Increment(cluster))); + replicator.Tell(Dsl.Update(keyA, GCounter.Empty, WriteLocal.Instance, c => c.Increment(cluster, 3))); ExpectMsg(new UpdateSuccess(keyA, null)); - replicator2.Tell(Dsl.Update(keyA, GCounter.Empty, WriteLocal.Instance, c => c.Increment(cluster2, 2)), probe2.Ref); + replicator2.Tell(Dsl.Update(keyA, GCounter.Empty, WriteLocal.Instance, c => c.Increment(cluster2.SelfUniqueAddress, 2)), probe2.Ref); probe2.ExpectMsg(new UpdateSuccess(keyA, null)); EnterBarrier("updates-done"); @@ -135,8 +147,9 @@ public void Pruning_of_durable_CRDT_should_move_data_from_removed_node() RunOn(() => { - var addr = cluster2.SelfAddress; - var sys3 = ActorSystem.Create(Sys.Name, ConfigurationFactory.ParseString(@" + var address = cluster2.SelfAddress; + var sys3 = ActorSystem.Create(Sys.Name, ConfigurationFactory.ParseString($@" + akka.remote.dot-netty.tcp.port = {address.Port} ").WithFallback(Sys.Settings.Config)); var cluster3 = Akka.Cluster.Cluster.Get(sys3); var replicator3 = StartReplicator(sys3); @@ -151,13 +164,20 @@ public void Pruning_of_durable_CRDT_should_move_data_from_removed_node() replicator3.Tell(Dsl.Get(keyA, ReadLocal.Instance), probe3.Ref); var counter4 = probe3.ExpectMsg().Get(keyA); var value = counter4.Value; - values.Add((int) value); + values = values.Add((int) value); value.ShouldBe(10UL); counter4.State.Count.ShouldBe(3); }); values.ShouldBe(ImmutableHashSet.Create(10)); }); + // all must at least have seen it as joining + AwaitAssert(() => + { + cluster3.State.Members.Count.ShouldBe(4); + cluster3.State.Members.All(m => m.Status == MemberStatus.Up).ShouldBeTrue(); + }, TimeSpan.FromSeconds(10)); + // after merging with others replicator3.Tell(Dsl.Get(keyA, new ReadAll(RemainingOrDefault))); var counter5 = ExpectMsg().Get(keyA); @@ -165,6 +185,7 @@ public void Pruning_of_durable_CRDT_should_move_data_from_removed_node() counter5.State.Count.ShouldBe(3); }, first); + EnterBarrier("sys3-started"); replicator.Tell(Dsl.Get(keyA, new ReadAll(RemainingOrDefault))); diff --git a/src/contrib/cluster/Akka.DistributedData/Internal/Internal.cs b/src/contrib/cluster/Akka.DistributedData/Internal/Internal.cs index 62bf4f27433..6a6bc50a0d6 100644 --- a/src/contrib/cluster/Akka.DistributedData/Internal/Internal.cs +++ b/src/contrib/cluster/Akka.DistributedData/Internal/Internal.cs @@ -85,11 +85,21 @@ private ClockTick() { } public override string ToString() => "ClockTick"; } + internal interface ISendingSystemUid + { + UniqueAddress FromNode { get; } + } + + internal interface IDestinationSystemUid + { + long? ToSystemUid { get; } + } + /// /// TBD /// [Serializable] - internal sealed class Write : IReplicatorMessage, IEquatable + internal sealed class Write : IReplicatorMessage, IEquatable, ISendingSystemUid { /// /// TBD @@ -205,7 +215,7 @@ private WriteNack() { } /// TBD /// [Serializable] - internal sealed class Read : IReplicatorMessage, IEquatable + internal sealed class Read : IReplicatorMessage, IEquatable, ISendingSystemUid { /// /// TBD @@ -651,7 +661,7 @@ private DeletedData() { } /// TBD /// [Serializable] - internal sealed class Status : IReplicatorMessage, IEquatable + internal sealed class Status : IReplicatorMessage, IEquatable, IDestinationSystemUid { /// /// TBD @@ -738,7 +748,7 @@ public override string ToString() /// TBD /// [Serializable] - internal sealed class Gossip : IReplicatorMessage, IEquatable + internal sealed class Gossip : IReplicatorMessage, IEquatable, IDestinationSystemUid { /// /// TBD @@ -813,9 +823,9 @@ public override string ToString() public sealed class Delta : IEquatable { - public readonly DataEnvelope DataEnvelope; - public readonly long FromSeqNr; - public readonly long ToSeqNr; + public DataEnvelope DataEnvelope { get; } + public long FromSeqNr { get; } + public long ToSeqNr { get; } public Delta(DataEnvelope dataEnvelope, long fromSeqNr, long toSeqNr) { @@ -850,7 +860,7 @@ public override int GetHashCode() } } - public sealed class DeltaPropagation : IReplicatorMessage, IEquatable + public sealed class DeltaPropagation : IReplicatorMessage, IEquatable, ISendingSystemUid { private sealed class NoDelta : IDeltaReplicatedData, IRequireCausualDeliveryOfDeltas { @@ -878,9 +888,9 @@ private NoDelta() { } /// public static readonly IReplicatedDelta NoDeltaPlaceholder = NoDelta.Instance; - public readonly UniqueAddress FromNode; - public readonly bool ShouldReply; - public readonly ImmutableDictionary Deltas; + public UniqueAddress FromNode { get; } + public bool ShouldReply { get; } + public ImmutableDictionary Deltas { get; } public DeltaPropagation(UniqueAddress fromNode, bool shouldReply, ImmutableDictionary deltas) { diff --git a/src/contrib/cluster/Akka.DistributedData/Replicator.cs b/src/contrib/cluster/Akka.DistributedData/Replicator.cs index fef2f41a519..6d20f6893ae 100644 --- a/src/contrib/cluster/Akka.DistributedData/Replicator.cs +++ b/src/contrib/cluster/Akka.DistributedData/Replicator.cs @@ -261,7 +261,7 @@ namespace Akka.DistributedData /// /// /// - internal sealed class Replicator : ReceiveActor + internal sealed class Replicator : UntypedActor, IWithUnboundedStash { public static Props Props(ReplicatorSettings settings) => Actor.Props.Create(() => new Replicator(settings)).WithDeploy(Deploy.Local).WithDispatcher(settings.Dispatcher); @@ -294,10 +294,15 @@ public static Props Props(ReplicatorSettings settings) => private ImmutableHashSet
AllNodes => _nodes.Union(_weaklyUpNodes); /// - /// Cluster weaklyUp nodes, doesn't contain selfAddress + /// Cluster weaklyUp nodes, doesn't contain joining and not selfAddress /// private ImmutableHashSet
_weaklyUpNodes = ImmutableHashSet
.Empty; + /// + /// cluster joining nodes, doesn't contain selfAddress + /// + private ImmutableHashSet
_joiningNodes = ImmutableHashSet
.Empty; + private ImmutableDictionary _removedNodes = ImmutableDictionary.Empty; private ImmutableDictionary _pruningPerformed = ImmutableDictionary.Empty; private ImmutableHashSet _tombstonedNodes = ImmutableHashSet.Empty; @@ -308,6 +313,9 @@ public static Props Props(ReplicatorSettings settings) => private ImmutableSortedSet _leader = ImmutableSortedSet.Empty.WithComparer(Member.LeaderStatusOrdering); private bool IsLeader => !_leader.IsEmpty && _leader.First().Address == _selfAddress; + private bool IsKnownNode(Address node) => _nodes.Contains(node) || _weaklyUpNodes.Contains(node) || + _joiningNodes.Contains(node) || _selfAddress == node; + /// /// For pruning timeouts are based on clock that is only increased when all nodes are reachable. /// @@ -346,6 +354,9 @@ public static Props Props(ReplicatorSettings settings) => private readonly ICancelable _deltaPropagationTask; private readonly int _maxDeltaSize; + private int _count; + private DateTime _startTime; + public Replicator(ReplicatorSettings settings) { _settings = settings; @@ -386,7 +397,7 @@ public Replicator(ReplicatorSettings settings) _durableWildcards = durableWildcardsBuilder.ToImmutable(); _durableStore = _hasDurableKeys - ? Context.Watch(Context.ActorOf(_settings.DurableStoreProps, "durableStore")) + ? Context.Watch(Context.ActorOf(_settings.DurableStoreProps.WithDeploy(Deploy.Local), "durableStore")) : Context.System.DeadLetters; _deltaPropagationSelector = new ReplicatorDeltaPropagationSelector(this); @@ -399,8 +410,13 @@ public Replicator(ReplicatorSettings settings) TimeSpan.TicksPerMillisecond * 200)); _deltaPropagationTask = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(deltaPropagationInterval, deltaPropagationInterval, Self, DeltaPropagationTick.Instance, Self); - if (_hasDurableKeys) Load(); - else NormalReceive(); + if (_hasDurableKeys) + { + _count = 0; + _startTime = DateTime.UtcNow; + Become(Load); + } + else Become(NormalReceive); } protected override void PreStart() @@ -436,86 +452,141 @@ protected override void PostStop() else return Actor.SupervisorStrategy.DefaultDecider.Decide(e); }); - private void Load() + private bool Load(object message) { - var startTime = DateTime.UtcNow; - var count = 0; - - NormalReceive(); - - Receive(load => + switch (message) { - count += load.Data.Count; - foreach (var entry in load.Data) - { - var envelope = entry.Value.DataEnvelope; - var newEnvelope = Write(entry.Key, envelope); - if (!ReferenceEquals(newEnvelope, envelope)) + case LoadData load: + _count += load.Data.Count; + foreach (var entry in load.Data) { - _durableStore.Tell(new Store(entry.Key, new DurableDataEnvelope(newEnvelope), null)); + var envelope = entry.Value.DataEnvelope; + var newEnvelope = Write(entry.Key, envelope); + if (!ReferenceEquals(newEnvelope, envelope)) + { + _durableStore.Tell(new Store(entry.Key, new DurableDataEnvelope(newEnvelope), null)); + } } - } - }); - Receive(_ => - { - _log.Debug("Loading {0} entries from durable store took {1} ms", count, - (DateTime.UtcNow - startTime).TotalMilliseconds); - Become(NormalReceive); - Self.Tell(FlushChanges.Instance); - }); - Receive(_ => - { - // 0 until durable data has been loaded, used by test - Sender.Tell(new ReplicaCount(0)); - }); - - // ignore scheduled ticks when loading durable data - Receive(new Action(Ignore)); - Receive(new Action(Ignore)); - Receive(new Action(Ignore)); - - // ignore gossip and replication when loading durable data - Receive(new Action(IgnoreDebug)); - Receive(new Action(IgnoreDebug)); - Receive(new Action(IgnoreDebug)); - Receive(new Action(IgnoreDebug)); + return true; + + case LoadAllCompleted _: + _log.Debug("Loading {0} entries from durable store took {1} ms", _count, + (DateTime.UtcNow - _startTime).TotalMilliseconds); + Become(NormalReceive); + Stash.UnstashAll(); + Self.Tell(FlushChanges.Instance); + return true; + + case GetReplicaCount _: + // 0 until durable data has been loaded, used by test + Sender.Tell(new ReplicaCount(0)); + return true; + + // ignore scheduled ticks when loading durable data + case RemovedNodePruningTick _: + case FlushChanges _: + case GossipTick _: + // Ignored + return true; + + // ignore gossip and replication when loading durable data + case Read _: + case Write _: + case Status _: + case Gossip _: + _log.Debug("ignoring message [{0}] when loading durable data", message.GetType()); + return true; + + case ClusterEvent.IClusterDomainEvent msg: + return NormalReceive(msg); + + default: + Stash.Stash(); + return true; + } } - private void NormalReceive() + protected override void OnReceive(object message) { - Receive(g => ReceiveGet(g.Key, g.Consistency, g.Request)); - Receive(msg => ReceiveUpdate(msg.Key, msg.Modify, msg.Consistency, msg.Request)); - Receive(r => ReceiveRead(r.Key)); - Receive(w => ReceiveWrite(w.Key, w.Envelope)); - Receive(rr => ReceiveReadRepair(rr.Key, rr.Envelope)); - Receive(msg => ReceiveDeltaPropagation(msg.FromNode, msg.ShouldReply, msg.Deltas)); - Receive(_ => ReceiveFlushChanges()); - Receive(_ => ReceiveDeltaPropagationTick()); - Receive(_ => ReceiveGossipTick()); - Receive(c => ReceiveClockTick()); - Receive(s => ReceiveStatus(s.Digests, s.Chunk, s.TotalChunks)); - Receive(g => ReceiveGossip(g.UpdatedData, g.SendBack)); - Receive(s => ReceiveSubscribe(s.Key, s.Subscriber)); - Receive(u => ReceiveUnsubscribe(u.Key, u.Subscriber)); - Receive(t => ReceiveTerminated(t.ActorRef)); - Receive(m => ReceiveMemberWeaklyUp(m.Member)); - Receive(m => ReceiveMemberUp(m.Member)); - Receive(m => ReceiveMemberRemoved(m.Member)); - Receive(m => ReceiveOtherMemberEvent(m.Member)); - Receive(u => ReceiveUnreachable(u.Member)); - Receive(r => ReceiveReachable(r.Member)); - Receive(_ => ReceiveGetKeyIds()); - Receive(d => ReceiveDelete(d.Key, d.Consistency, d.Request)); - Receive(r => ReceiveRemovedNodePruningTick()); - Receive(_ => ReceiveGetReplicaCount()); + throw new NotImplementedException(); } - private void IgnoreDebug(T msg) + private bool NormalReceive(object message) { - _log.Debug("ignoring message [{0}] when loading durable data", typeof(T)); - } + switch (message) + { + case IDestinationSystemUid msg: + if (msg.ToSystemUid.HasValue && msg.ToSystemUid != _selfUniqueAddress.Uid) + { + // When restarting a node with same host:port it is possible that a Replicator on another node + // is sending messages to the restarted node even if it hasn't joined the same cluster. + // Therefore we check that the message was intended for this incarnation and otherwise + // it is discarded. + _log.Info("Ignoring message [{0}] from [{1}] intended for system uid [{2}], self uid is [{3}]", + Logging.SimpleName(msg), + Sender, + msg.ToSystemUid, + _selfUniqueAddress.Uid); + } + else + { + switch (msg) + { + case Status s: + ReceiveStatus(s.Digests, s.Chunk, s.TotalChunks); return true; + case Gossip g : + ReceiveGossip(g.UpdatedData, g.SendBack); return true; + } + } + return true; - private static void Ignore(T msg) { } + case ISendingSystemUid msg: + if (msg.FromNode != null && !IsKnownNode(msg.FromNode.Address)) + { + // When restarting a node with same host:port it is possible that a Replicator on another node + // is sending messages to the restarted node even if it hasn't joined the same cluster. + // Therefore we check that the message was from a known cluster member + _log.Info("Ignoring message [{0}] from [{1}] unknown node [{2}]", Logging.SimpleName(msg), Sender, msg.FromNode); + } + else + { + switch (msg) + { + case Read r : ReceiveRead(r.Key); return true; + case Write w : ReceiveWrite(w.Key, w.Envelope); return true; + case DeltaPropagation d : ReceiveDeltaPropagation(msg.FromNode, d.ShouldReply, d.Deltas); return true; + } + } + return true; + + case Get g : ReceiveGet(g.Key, g.Consistency, g.Request); return true; + case Update msg : ReceiveUpdate(msg.Key, msg.Modify, msg.Consistency, msg.Request); return true; + case ReadRepair rr : ReceiveReadRepair(rr.Key, rr.Envelope); return true; + case FlushChanges _ : ReceiveFlushChanges(); return true; + case DeltaPropagationTick _ : ReceiveDeltaPropagationTick(); return true; + case GossipTick _ : ReceiveGossipTick(); return true; + case ClockTick c : ReceiveClockTick(); return true; + case Subscribe s : ReceiveSubscribe(s.Key, s.Subscriber); return true; + case Unsubscribe u : ReceiveUnsubscribe(u.Key, u.Subscriber); return true; + case Terminated t : ReceiveTerminated(t.ActorRef); return true; + + case ClusterEvent.MemberJoined m : ReceiveMemberJoining(m.Member); return true; + case ClusterEvent.MemberWeaklyUp m : ReceiveMemberWeaklyUp(m.Member); return true; + case ClusterEvent.MemberUp m : ReceiveMemberUp(m.Member); return true; + case ClusterEvent.MemberRemoved m : ReceiveMemberRemoved(m.Member); return true; + + case ClusterEvent.IMemberEvent m : ReceiveOtherMemberEvent(m.Member); return true; + case ClusterEvent.UnreachableMember u : ReceiveUnreachable(u.Member); return true; + case ClusterEvent.ReachableMember r : ReceiveReachable(r.Member); return true; + + case GetKeyIds _ : ReceiveGetKeyIds(); return true; + case Delete d : ReceiveDelete(d.Key, d.Consistency, d.Request); return true; + case RemovedNodePruningTick r : ReceiveRemovedNodePruningTick(); return true; + case GetReplicaCount _ : ReceiveGetReplicaCount(); return true; + } + + return false; + } private void ReceiveGet(IKey key, IReadConsistency consistency, object req) { @@ -1185,11 +1256,18 @@ private void ReceiveTerminated(IActorRef terminated) } } + private void ReceiveMemberJoining(Member m) + { + if (MatchingRole(m) && m.Address != _selfAddress) + _joiningNodes = _joiningNodes.Add(m.Address); + } + private void ReceiveMemberWeaklyUp(Member m) { if (MatchingRole(m) && m.Address != _selfAddress) { _weaklyUpNodes = _weaklyUpNodes.Add(m.Address); + _joiningNodes = _joiningNodes.Remove(m.Address); } } @@ -1202,6 +1280,7 @@ private void ReceiveMemberUp(Member m) { _nodes = _nodes.Add(m.Address); _weaklyUpNodes = _weaklyUpNodes.Remove(m.Address); + _joiningNodes = _joiningNodes.Remove(m.Address); } } } @@ -1218,6 +1297,7 @@ private void ReceiveMemberRemoved(Member m) _nodes = _nodes.Remove(m.Address); _weaklyUpNodes = _weaklyUpNodes.Remove(m.Address); + _joiningNodes = _joiningNodes.Remove(m.Address); _removedNodes = _removedNodes.SetItem(m.UniqueAddress, _allReachableClockTime); _unreachable = _unreachable.Remove(m.Address); @@ -1448,5 +1528,7 @@ protected override DeltaPropagation CreateDeltaPropagation(ImmutableDictionary diff --git a/src/contrib/cluster/Akka.DistributedData/Serialization/Proto/ReplicatedDataMessages.g.cs b/src/contrib/cluster/Akka.DistributedData/Serialization/Proto/ReplicatedDataMessages.g.cs index a79671a434d..dea5d25b29e 100644 --- a/src/contrib/cluster/Akka.DistributedData/Serialization/Proto/ReplicatedDataMessages.g.cs +++ b/src/contrib/cluster/Akka.DistributedData/Serialization/Proto/ReplicatedDataMessages.g.cs @@ -1,10 +1,3 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2021 Lightbend Inc. -// Copyright (C) 2013-2021 .NET Foundation -// -//----------------------------------------------------------------------- - // Generated by the protocol buffer compiler. DO NOT EDIT! // source: ReplicatedDataMessages.proto #pragma warning disable 1591, 0612, 3021 diff --git a/src/contrib/cluster/Akka.DistributedData/Serialization/Proto/ReplicatorMessages.g.cs b/src/contrib/cluster/Akka.DistributedData/Serialization/Proto/ReplicatorMessages.g.cs index 6c08f75042c..70427bb8cfd 100644 --- a/src/contrib/cluster/Akka.DistributedData/Serialization/Proto/ReplicatorMessages.g.cs +++ b/src/contrib/cluster/Akka.DistributedData/Serialization/Proto/ReplicatorMessages.g.cs @@ -1,10 +1,3 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2021 Lightbend Inc. -// Copyright (C) 2013-2021 .NET Foundation -// -//----------------------------------------------------------------------- - // Generated by the protocol buffer compiler. DO NOT EDIT! // source: ReplicatorMessages.proto #pragma warning disable 1591, 0612, 3021 @@ -16,7 +9,7 @@ using scg = global::System.Collections.Generic; namespace Akka.DistributedData.Serialization.Proto.Msg { - /// Holder for reflection information generated from replicatormessages.proto + /// Holder for reflection information generated from ReplicatorMessages.proto internal static partial class ReplicatorMessagesReflection { #region Descriptor @@ -29,7 +22,7 @@ internal static partial class ReplicatorMessagesReflection { static ReplicatorMessagesReflection() { byte[] descriptorData = global::System.Convert.FromBase64String( string.Concat( - "ChhyZXBsaWNhdG9ybWVzc2FnZXMucHJvdG8SLEFra2EuRGlzdHJpYnV0ZWRE", + "ChhSZXBsaWNhdG9yTWVzc2FnZXMucHJvdG8SLEFra2EuRGlzdHJpYnV0ZWRE", "YXRhLlNlcmlhbGl6YXRpb24uUHJvdG8uTXNnIp0CCgNHZXQSRwoDa2V5GAEg", "ASgLMjouQWtrYS5EaXN0cmlidXRlZERhdGEuU2VyaWFsaXphdGlvbi5Qcm90", "by5Nc2cuT3RoZXJNZXNzYWdlEhMKC2NvbnNpc3RlbmN5GAIgASgREg8KB3Rp", diff --git a/src/contrib/cluster/Akka.DistributedData/WriteAggregator.cs b/src/contrib/cluster/Akka.DistributedData/WriteAggregator.cs index d068b4b1691..c55c40548a2 100644 --- a/src/contrib/cluster/Akka.DistributedData/WriteAggregator.cs +++ b/src/contrib/cluster/Akka.DistributedData/WriteAggregator.cs @@ -28,6 +28,7 @@ public static Props Props(IKey key, DataEnvelope envelope, Delta delta, IWriteCo private readonly DeltaPropagation _delta; private readonly bool _durable; private readonly UniqueAddress _selfUniqueAddress; + private bool _gotLocalStoreReply; private ImmutableHashSet
_gotNackFrom; @@ -42,13 +43,14 @@ public WriteAggregator(IKey key, DataEnvelope envelope, Delta delta, IWriteConsi _req = req; _replyTo = replyTo; _durable = durable; - _write = new Write(key.Id, envelope); + _write = new Write(key.Id, envelope, _selfUniqueAddress); _delta = delta == null ? null : new DeltaPropagation(_selfUniqueAddress, true, ImmutableDictionary.Empty.Add(key.Id, delta)); _gotLocalStoreReply = !durable; _gotNackFrom = ImmutableHashSet
.Empty; + DoneWhenRemainingSize = GetDoneWhenRemainingSize(); } @@ -87,50 +89,58 @@ protected override void PreStart() if (IsDone) Reply(isTimeout: false); } - protected override bool Receive(object message) => message.Match() - .With(x => - { - Remaining = Remaining.Remove(SenderAddress); - if (IsDone) Reply(isTimeout: false); - }) - .With(x => - { - _gotNackFrom = _gotNackFrom.Add(SenderAddress); - if (IsDone) Reply(isTimeout: false); - }) - .With(_ => - { - // ok, will be retried with full state - }) - .With(x => - { - _gotLocalStoreReply = true; - if (IsDone) Reply(isTimeout: false); - }) - .With(x => - { - _gotLocalStoreReply = true; - _gotNackFrom = _gotNackFrom.Remove(_selfUniqueAddress.Address); - if (IsDone) Reply(isTimeout: false); - }) - .With(x => + protected override bool Receive(object message) + { + switch (message) { - // Deltas must be applied in order and we can't keep track of ordering of - // simultaneous updates so there is a chance that the delta could not be applied. - // Try again with the full state to the primary nodes that have not acked. - if (_delta != null) - { - foreach (var address in PrimaryNodes.Intersect(Remaining)) + case WriteAck _ : + Remaining = Remaining.Remove(SenderAddress); + if (IsDone) Reply(isTimeout: false); + return true; + + case WriteNack _: + _gotNackFrom = _gotNackFrom.Add(SenderAddress); + if (IsDone) Reply(isTimeout: false); + return true; + + case DeltaNack _: + Sender.Tell(_write); + return true; + + case UpdateSuccess _: + _gotLocalStoreReply = true; + if (IsDone) Reply(isTimeout: false); + return true; + + case StoreFailure _: + _gotLocalStoreReply = true; + _gotNackFrom = _gotNackFrom.Add(_selfUniqueAddress.Address); + if (IsDone) Reply(isTimeout: false); + return true; + + case SendToSecondary _: + if (_delta != null) { - Replica(address).Tell(_write); + // Deltas must be applied in order and we can't keep track of ordering of + // simultaneous updates so there is a chance that the delta could not be applied. + // Try again with the full state to the primary nodes that have not acked. + foreach (var address in PrimaryNodes.Intersect(Remaining)) + { + Replica(address).Tell(_write); + } } - } - foreach (var n in SecondaryNodes) - Replica(n).Tell(_write); - }) - .With(x => Reply(isTimeout: true)) - .WasHandled; + foreach (var n in SecondaryNodes) + Replica(n).Tell(_write); + return true; + + case ReceiveTimeout _: + Reply(isTimeout: true); + return true; + } + + return false; + } private void Reply(bool isTimeout) { diff --git a/src/contrib/cluster/Akka.DistributedData/reference.conf b/src/contrib/cluster/Akka.DistributedData/reference.conf index 4652b28bd42..69ddb4459aa 100644 --- a/src/contrib/cluster/Akka.DistributedData/reference.conf +++ b/src/contrib/cluster/Akka.DistributedData/reference.conf @@ -24,7 +24,7 @@ akka.cluster.distributed-data { # Maximum number of entries to transfer in one gossip message when synchronizing # the replicas. Next chunk will be transferred in next round of gossip. - max-delta-elements = 1000 + max-delta-elements = 500 # The id of the dispatcher to use for Replicator actors. # If not specified, the internal dispatcher is used. @@ -33,13 +33,13 @@ akka.cluster.distributed-data { # How often the Replicator checks for pruning of data associated with # removed cluster nodes. - pruning-interval = 30 s + pruning-interval = 120 s # How long time it takes (worst case) to spread the data to all other replica nodes. # This is used when initiating and completing the pruning process of data associated # with removed cluster nodes. The time measurement is stopped when any replica is # unreachable, so it should be configured to worst case in a healthy cluster. - max-pruning-dissemination = 60 s + max-pruning-dissemination = 300 s # The markers of that pruning has been performed for a removed node are kept for this # time and thereafter removed. If and old data entry that was never pruned is somehow @@ -47,18 +47,21 @@ akka.cluster.distributed-data { # This would be possible (although unlikely) in the case of a long network partition. # It should be in the magnitude of hours. For durable data it is configured by # 'akka.cluster.distributed-data.durable.pruning-marker-time-to-live'. - pruning-marker-time-to-live = 6 h + pruning-marker-time-to-live = 6 h # Serialized Write and Read messages are cached when they are sent to # several nodes. If no further activity they are removed from the cache # after this duration. serializer-cache-time-to-live = 10s + # Settings for delta-CRDT delta-crdt { + # enable or disable delta-CRDT replication + enabled = on - # Some complex deltas grow in size for each update and above this - # threshold such deltas are discarded and sent as full state instead. - max-delta-size = 200 + # Some complex deltas grow in size for each update and above this + # threshold such deltas are discarded and sent as full state instead. + max-delta-size = 50 } durable { @@ -90,9 +93,39 @@ akka.cluster.distributed-data { type = PinnedDispatcher } + # Config for the LmdbDurableStore + lmdb { + # Directory of LMDB file. There are two options: + # 1. A relative or absolute path to a directory that ends with 'ddata' + # the full name of the directory will contain name of the ActorSystem + # and its remote port. + # 2. Otherwise the path is used as is, as a relative or absolute path to + # a directory. + # + # When running in production you may want to configure this to a specific + # path (alt 2), since the default directory contains the remote port of the + # actor system to make the name unique. If using a dynamically assigned + # port (0) it will be different each time and the previously stored data + # will not be loaded. + dir = "ddata" + + # Size in bytes of the memory mapped file. + map-size = 100 MiB + + # Accumulate changes before storing improves performance with the + # risk of losing the last writes if the JVM crashes. + # The interval is by default set to 'off' to write each update immediately. + # Enabling write behind by specifying a duration, e.g. 200ms, is especially + # efficient when performing many writes to the same key, because it is only + # the last value for each key that will be serialized and stored. + # write-behind-interval = 200 ms + write-behind-interval = off + } } } +#//#distributed-data +# Serializer for cluster DistributedData messages akka.actor { serializers { akka-data-replication = "Akka.DistributedData.Serialization.ReplicatorMessageSerializer, Akka.DistributedData" diff --git a/src/contrib/dependencyinjection/Akka.DependencyInjection.Tests/ActorServiceProviderPropsWithScopesSpecs.cs b/src/contrib/dependencyinjection/Akka.DependencyInjection.Tests/ActorServiceProviderPropsWithScopesSpecs.cs index 506d98c18e1..ef5d2fe0c39 100644 --- a/src/contrib/dependencyinjection/Akka.DependencyInjection.Tests/ActorServiceProviderPropsWithScopesSpecs.cs +++ b/src/contrib/dependencyinjection/Akka.DependencyInjection.Tests/ActorServiceProviderPropsWithScopesSpecs.cs @@ -45,7 +45,11 @@ public void ActorsWithScopedDependenciesShouldDisposeUponStop() ExpectTerminated(scoped1); // all dependencies should be disposed - deps1.Dependencies.All(x => x.Disposed).Should().BeTrue(); + AwaitAssert(() => + { + deps1.Dependencies.All(x => x.Disposed).Should().BeTrue(); + }); + // reuse the same props var scoped2 = Sys.ActorOf(props, "scoped2"); @@ -76,7 +80,10 @@ public void ActorsWithScopedDependenciesShouldDisposeAndRecreateUponRestart() }); // all previous dependencies should be disposed - deps1.Dependencies.All(x => x.Disposed).Should().BeTrue(); + AwaitAssert(() => + { + deps1.Dependencies.All(x => x.Disposed).Should().BeTrue(); + }); // actor should restart with totally new dependencies scoped1.Tell(new FetchDependencies()); @@ -104,7 +111,10 @@ public void ActorsWithMixedDependenciesShouldDisposeAndRecreateScopedUponRestart }); // all previous SCOPED dependencies should be disposed - deps1.Dependencies.Where(x => !(x is AkkaDiFixture.ISingletonDependency)).All(x => x.Disposed).Should().BeTrue(); + AwaitAssert(() => + { + deps1.Dependencies.Where(x => !(x is AkkaDiFixture.ISingletonDependency)).All(x => x.Disposed).Should().BeTrue(); + }); // singletons should not be disposed deps1.Dependencies.Where(x => (x is AkkaDiFixture.ISingletonDependency)).All(x => x.Disposed).Should().BeFalse(); @@ -147,8 +157,13 @@ public void ActorsWithNonDiDependenciesShouldStart() scoped1.Tell(new Crash()); }); - // all previous SCOPED dependencies should be disposed - deps1.Dependencies.Where(x => !(x is AkkaDiFixture.ISingletonDependency)).All(x => x.Disposed).Should().BeTrue(); + AwaitAssert(() => + { + // all previous SCOPED dependencies should eventually be disposed + deps1.Dependencies.Where(x => !(x is AkkaDiFixture.ISingletonDependency)).All(x => x.Disposed).Should().BeTrue(); + }, + duration: TimeSpan.FromMilliseconds(300), + interval: TimeSpan.FromMilliseconds(100)); // singletons should not be disposed deps1.Dependencies.Where(x => (x is AkkaDiFixture.ISingletonDependency)).All(x => x.Disposed).Should().BeFalse(); diff --git a/src/contrib/dependencyinjection/Akka.DependencyInjection.Tests/AkkaDiFixture.cs b/src/contrib/dependencyinjection/Akka.DependencyInjection.Tests/AkkaDiFixture.cs index ef441731833..bb41f55b62b 100644 --- a/src/contrib/dependencyinjection/Akka.DependencyInjection.Tests/AkkaDiFixture.cs +++ b/src/contrib/dependencyinjection/Akka.DependencyInjection.Tests/AkkaDiFixture.cs @@ -95,33 +95,19 @@ public void Dispose() public AkkaDiFixture() { - Host = CreateHostBuilder().Build(); - Host.Start(); - Provider = Host.Services; - } - - public IHostBuilder CreateHostBuilder() - => Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder() - .ConfigureServices((context, services) => - { - // - // register some default services - services.AddSingleton() + var services = new ServiceCollection(); + services.AddSingleton() .AddScoped() .AddTransient(); - // - }); - - private IHost Host { get; set; } + Provider = services.BuildServiceProvider(); + } + public IServiceProvider Provider { get; private set; } public void Dispose() { - Host?.StopAsync().GetAwaiter().GetResult(); - Provider = null; - Host = null; } } } diff --git a/src/contrib/dependencyinjection/Akka.DependencyInjection.Tests/DelegateInjectionSpecs.cs b/src/contrib/dependencyinjection/Akka.DependencyInjection.Tests/DelegateInjectionSpecs.cs new file mode 100644 index 00000000000..bc0a64388ad --- /dev/null +++ b/src/contrib/dependencyinjection/Akka.DependencyInjection.Tests/DelegateInjectionSpecs.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Akka.Actor; +using Akka.TestKit; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; +using Xunit.Abstractions; + +namespace Akka.DependencyInjection.Tests +{ + public class DelegateInjectionSpecs : AkkaSpec + { + public delegate IActorRef EchoActorProvider(); + + private readonly IServiceProvider _serviceProvider; + + public static IServiceProvider CreateProvider() + { + var services = new ServiceCollection() + .AddSingleton() + .AddHostedService() + .AddSingleton(p => + { + var system = p.GetRequiredService(); + var actor = system.ActorSystem.ActorOf("echoActor"); + return () => actor; + }); + + var provider = services.BuildServiceProvider(); + var akkaService = provider.GetRequiredService(); + akkaService.StartAsync(default).Wait(); + + return provider; + } + + public DelegateInjectionSpecs(ITestOutputHelper output) : base(output) + { + _serviceProvider = CreateProvider(); + } + + [Fact] + public async Task DI_should_be_able_to_retrieve_singleton_using_delegate() + { + var actor = _serviceProvider.GetRequiredService()(); + + var task = actor.Ask("echo"); + task.Wait(TimeSpan.FromSeconds(3)); + task.Result.ShouldBe("echo"); + + var sys = _serviceProvider.GetRequiredService().ActorSystem; + await sys.Terminate(); + } + + [Fact] + public async Task DI_should_be_able_to_retrieve_singleton_using_delegate_from_inside_actor() + { + var system = _serviceProvider.GetRequiredService().ActorSystem; + var actor = system.ActorOf(ParentActor.Props(system)); + + var task = actor.Ask("echo"); + task.Wait(TimeSpan.FromSeconds(3)); + task.Result.ShouldBe("echo"); + + var sys = _serviceProvider.GetRequiredService().ActorSystem; + await sys.Terminate(); + } + + internal class ParentActor : UntypedActor + { + public static Props Props(ActorSystem system) => + ServiceProvider.For(system).Props(); + + private readonly IActorRef _echoActor; + + public ParentActor(IServiceProvider provider) + { + _echoActor = provider.GetRequiredService()(); + } + + protected override void OnReceive(object message) + { + _echoActor.Forward(message); + } + } + + internal class EchoActor : ReceiveActor + { + public static Props Props() => Akka.Actor.Props.Create(); + + public EchoActor() + { + Receive(msg => + { + Sender.Tell(msg); + }); + } + } + + internal class AkkaService : IHostedService + { + public ActorSystem ActorSystem { get; private set; } + + private readonly IServiceProvider _serviceProvider; + + public AkkaService(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + var setup = ServiceProviderSetup.Create(_serviceProvider) + .And(BootstrapSetup.Create().WithConfig(TestKitBase.DefaultConfig)); + + ActorSystem = ActorSystem.Create("TestSystem", setup); + return Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + await ActorSystem.Terminate(); + } + } + } +} diff --git a/src/contrib/persistence/Akka.Persistence.Sqlite/Akka.Persistence.Sqlite.csproj b/src/contrib/persistence/Akka.Persistence.Sqlite/Akka.Persistence.Sqlite.csproj index df2e2d75f46..0d52c1c8ac0 100644 --- a/src/contrib/persistence/Akka.Persistence.Sqlite/Akka.Persistence.Sqlite.csproj +++ b/src/contrib/persistence/Akka.Persistence.Sqlite/Akka.Persistence.Sqlite.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/contrib/serializers/Akka.Serialization.Hyperion.Tests/HyperionConfigTests.cs b/src/contrib/serializers/Akka.Serialization.Hyperion.Tests/HyperionConfigTests.cs index fc30441953d..faf91904c13 100644 --- a/src/contrib/serializers/Akka.Serialization.Hyperion.Tests/HyperionConfigTests.cs +++ b/src/contrib/serializers/Akka.Serialization.Hyperion.Tests/HyperionConfigTests.cs @@ -105,6 +105,61 @@ public void Hyperion_serializer_should_allow_to_setup_custom_types_provider_with Assert.Equal(typeof(DummyTypesProvider), serializer.Settings.KnownTypesProvider); } } + + [Fact] + public void Hyperion_serializer_should_read_cross_platform_package_name_override_settings() + { + var config = ConfigurationFactory.ParseString(@" + akka.actor { + serializers.hyperion = ""Akka.Serialization.HyperionSerializer, Akka.Serialization.Hyperion"" + serialization-bindings { + ""System.Object"" = hyperion + } + serialization-settings.hyperion { + cross-platform-package-name-overrides = { + netfx = [ + { + fingerprint = ""a"", + rename-from = ""b"", + rename-to = ""c"" + }] + netcore = [ + { + fingerprint = ""d"", + rename-from = ""e"", + rename-to = ""f"" + }] + net = [ + { + fingerprint = ""g"", + rename-from = ""h"", + rename-to = ""i"" + }] + } + } + } + "); + using (var system = ActorSystem.Create(nameof(HyperionConfigTests), config)) + { + var serializer = (HyperionSerializer)system.Serialization.FindSerializerForType(typeof(object)); + var overrides = serializer.Settings.PackageNameOverrides.ToList(); + Assert.NotEmpty(overrides); + var @override = overrides[0]; + +#if NET471 + Assert.Equal("acc", @override("abc")); + Assert.Equal("bcd", @override("bcd")); +#elif NETCOREAPP3_1 + Assert.Equal("dff", @override("def")); + Assert.Equal("efg", @override("efg")); +#elif NET5_0 + Assert.Equal("gii", @override("ghi")); + Assert.Equal("hij", @override("hij")); +#else + throw new Exception("Test can not be completed because no proper compiler directive is set for this test build"); +#endif + } + } } class DummyTypesProvider : IKnownTypesProvider diff --git a/src/contrib/serializers/Akka.Serialization.Hyperion.Tests/HyperionSerializerSetupSpec.cs b/src/contrib/serializers/Akka.Serialization.Hyperion.Tests/HyperionSerializerSetupSpec.cs new file mode 100644 index 00000000000..0ae01817155 --- /dev/null +++ b/src/contrib/serializers/Akka.Serialization.Hyperion.Tests/HyperionSerializerSetupSpec.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Akka.Actor; +using Akka.Configuration; +using Akka.TestKit; +using Xunit; +using Xunit.Abstractions; +using FluentAssertions; + +namespace Akka.Serialization.Hyperion.Tests +{ + public class HyperionSerializerSetupSpec : AkkaSpec + { + private static Config Config + => ConfigurationFactory.ParseString(@" +akka.actor { + serializers { + hyperion = ""Akka.Serialization.Hyperion, Akka.Serialization.Hyperion"" + } + + serialization-bindings { + ""System.Object"" = hyperion + } +} +"); + + public HyperionSerializerSetupSpec(ITestOutputHelper output) : base (Config, output) + { } + + [Fact] + public void Setup_should_be_converted_to_settings_correctly() + { + var setup = HyperionSerializerSetup.Empty + .WithPreserveObjectReference(true) + .WithKnownTypeProvider(); + var settings = + new HyperionSerializerSettings(false, false, typeof(DummyTypesProvider), new Func[] { s => $"{s}.." }); + var appliedSettings = setup.ApplySettings(settings); + + appliedSettings.PreserveObjectReferences.Should().BeTrue(); // overriden + appliedSettings.VersionTolerance.Should().BeFalse(); // default + appliedSettings.KnownTypesProvider.Should().Be(typeof(NoKnownTypes)); // overriden + appliedSettings.PackageNameOverrides.Count().Should().Be(1); // from settings + appliedSettings.PackageNameOverrides.First()("a").Should().Be("a.."); + } + + [Fact] + public void Setup_package_override_should_work() + { + var setup = HyperionSerializerSetup.Empty + .WithPackageNameOverrides(new Func[] + { + s => s.Contains("Hyperion.Override") + ? s.Replace(".Override", "") + : s + }); + + var settings = HyperionSerializerSettings.Default; + var appliedSettings = setup.ApplySettings(settings); + + var adapter = appliedSettings.PackageNameOverrides.First(); + adapter("My.Hyperion.Override").Should().Be("My.Hyperion"); + } + } +} diff --git a/src/contrib/serializers/Akka.Serialization.Hyperion/HyperionSerializer.cs b/src/contrib/serializers/Akka.Serialization.Hyperion/HyperionSerializer.cs index 64cfbca1aec..ad495294387 100644 --- a/src/contrib/serializers/Akka.Serialization.Hyperion/HyperionSerializer.cs +++ b/src/contrib/serializers/Akka.Serialization.Hyperion/HyperionSerializer.cs @@ -6,14 +6,18 @@ //----------------------------------------------------------------------- using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using Akka.Actor; using Akka.Configuration; +using Akka.Serialization.Hyperion; using Akka.Util; using Hyperion; +using HySerializer = Hyperion.Serializer; // ReSharper disable once CheckNamespace namespace Akka.Serialization @@ -28,7 +32,7 @@ public class HyperionSerializer : Serializer /// public readonly HyperionSerializerSettings Settings; - private readonly Hyperion.Serializer _serializer; + private readonly HySerializer _serializer; /// /// Initializes a new instance of the class. @@ -57,7 +61,7 @@ public HyperionSerializer(ExtendedActorSystem system, Config config) public HyperionSerializer(ExtendedActorSystem system, HyperionSerializerSettings settings) : base(system) { - this.Settings = settings; + Settings = settings; var akkaSurrogate = Surrogate .Create( @@ -66,13 +70,23 @@ public HyperionSerializer(ExtendedActorSystem system, HyperionSerializerSettings var provider = CreateKnownTypesProvider(system, settings.KnownTypesProvider); + if (system != null) + { + var settingsSetup = system.Settings.Setup.Get() + .GetOrElse(HyperionSerializerSetup.Empty); + + settingsSetup.ApplySettings(Settings); + } + _serializer = - new Hyperion.Serializer(new SerializerOptions( - preserveObjectReferences: settings.PreserveObjectReferences, + new HySerializer(new SerializerOptions( versionTolerance: settings.VersionTolerance, + preserveObjectReferences: settings.PreserveObjectReferences, surrogates: new[] { akkaSurrogate }, + serializerFactories: null, knownTypes: provider.GetKnownTypes(), - ignoreISerializable:true)); + ignoreISerializable:true, + packageNameOverrides: settings.PackageNameOverrides)); } /// @@ -156,7 +170,8 @@ public sealed class HyperionSerializerSettings public static readonly HyperionSerializerSettings Default = new HyperionSerializerSettings( preserveObjectReferences: true, versionTolerance: true, - knownTypesProvider: typeof(NoKnownTypes)); + knownTypesProvider: typeof(NoKnownTypes), + packageNameOverrides: new List>()); /// /// Creates a new instance of using provided HOCON config. @@ -174,15 +189,42 @@ public sealed class HyperionSerializerSettings public static HyperionSerializerSettings Create(Config config) { if (config.IsNullOrEmpty()) - throw ConfigurationException.NullOrEmptyConfig("akka.serializers.hyperion"); + throw ConfigurationException.NullOrEmptyConfig("akka.actor.serialization-settings.hyperion"); var typeName = config.GetString("known-types-provider", null); var type = !string.IsNullOrEmpty(typeName) ? Type.GetType(typeName, true) : null; + var framework = RuntimeInformation.FrameworkDescription; + string frameworkKey; + if (framework.Contains(".NET Framework")) + frameworkKey = "netfx"; + else if (framework.Contains(".NET Core")) + frameworkKey = "netcore"; + else + frameworkKey = "net"; + + var packageNameOverrides = new List>(); + var overrideConfigs = config.GetValue($"cross-platform-package-name-overrides.{frameworkKey}"); + if (overrideConfigs != null) + { + var configs = overrideConfigs.GetArray().Select(value => value.GetObject()); + foreach (var obj in configs) + { + var fingerprint = obj.GetKey("fingerprint").GetString(); + var renameFrom = obj.GetKey("rename-from").GetString(); + var renameTo = obj.GetKey("rename-to").GetString(); + packageNameOverrides.Add(packageName => + packageName.Contains(fingerprint) + ? packageName.Replace(renameFrom, renameTo) + : packageName); + } + } + return new HyperionSerializerSettings( preserveObjectReferences: config.GetBoolean("preserve-object-references", true), versionTolerance: config.GetBoolean("version-tolerance", true), - knownTypesProvider: type); + knownTypesProvider: type, + packageNameOverrides: packageNameOverrides); } /// @@ -207,6 +249,12 @@ public static HyperionSerializerSettings Create(Config config) /// public readonly Type KnownTypesProvider; + /// + /// A list of lambda functions, used to transform incoming deserialized + /// package names before they are instantiated + /// + public readonly IEnumerable> PackageNameOverrides; + /// /// Creates a new instance of a . /// @@ -214,7 +262,24 @@ public static HyperionSerializerSettings Create(Config config) /// Flag which determines if field data should be serialized as part of type manifest. /// Type implementing to be used to determine a list of types implicitly known by all cooperating serializer. /// Raised when `known-types-provider` type doesn't implement interface. + [Obsolete] public HyperionSerializerSettings(bool preserveObjectReferences, bool versionTolerance, Type knownTypesProvider) + : this(preserveObjectReferences, versionTolerance, knownTypesProvider, new List>()) + { } + + /// + /// Creates a new instance of a . + /// + /// Flag which determines if serializer should keep track of references in serialized object graph. + /// Flag which determines if field data should be serialized as part of type manifest. + /// Type implementing to be used to determine a list of types implicitly known by all cooperating serializer. + /// TBD + /// Raised when `known-types-provider` type doesn't implement interface. + public HyperionSerializerSettings( + bool preserveObjectReferences, + bool versionTolerance, + Type knownTypesProvider, + IEnumerable> packageNameOverrides) { knownTypesProvider = knownTypesProvider ?? typeof(NoKnownTypes); if (!typeof(IKnownTypesProvider).IsAssignableFrom(knownTypesProvider)) @@ -223,6 +288,7 @@ public HyperionSerializerSettings(bool preserveObjectReferences, bool versionTol PreserveObjectReferences = preserveObjectReferences; VersionTolerance = versionTolerance; KnownTypesProvider = knownTypesProvider; + PackageNameOverrides = packageNameOverrides; } } } diff --git a/src/contrib/serializers/Akka.Serialization.Hyperion/HyperionSerializerSetup.cs b/src/contrib/serializers/Akka.Serialization.Hyperion/HyperionSerializerSetup.cs new file mode 100644 index 00000000000..2bca0452527 --- /dev/null +++ b/src/contrib/serializers/Akka.Serialization.Hyperion/HyperionSerializerSetup.cs @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Akka.Actor; +using Akka.Actor.Setup; +using Akka.Util; + +namespace Akka.Serialization.Hyperion +{ + public class HyperionSerializerSetup : Setup + { + public static readonly HyperionSerializerSetup Empty = + new HyperionSerializerSetup(Option.None, Option.None); + + public static HyperionSerializerSetup Create( + bool preserveObjectReferences, + bool versionTolerance, + Type knownTypesProvider, + IEnumerable> packageNameOverrides) + => new HyperionSerializerSetup(preserveObjectReferences, versionTolerance, knownTypesProvider, packageNameOverrides); + + private HyperionSerializerSetup( + Option preserveObjectReferences, + Option versionTolerance, + Type knownTypesProvider = null, + IEnumerable> packageNameOverrides = null) + { + PreserveObjectReferences = preserveObjectReferences; + VersionTolerance = versionTolerance; + KnownTypesProvider = knownTypesProvider; + PackageNameOverrides = packageNameOverrides; + } + + /// + /// When true, it tells to keep + /// track of references in serialized/deserialized object graph. + /// + public Option PreserveObjectReferences { get; } + + /// + /// When true, it tells to encode + /// a list of currently serialized fields into type manifest. + /// + public Option VersionTolerance { get; } + + /// + /// A type implementing , that will + /// be used when is being constructed + /// to provide a list of message types that are supposed to be known + /// implicitly by all communicating parties. Implementing class must + /// provide either a default constructor or a constructor taking + /// as its only parameter. + /// + public Type KnownTypesProvider { get; } + + /// + /// A list of lambda functions, used to transform incoming deserialized + /// package names before they are instantiated + /// + public IEnumerable> PackageNameOverrides { get; } + + internal HyperionSerializerSettings ApplySettings(HyperionSerializerSettings settings) + => new HyperionSerializerSettings( + PreserveObjectReferences.HasValue ? PreserveObjectReferences.Value : settings.PreserveObjectReferences, + VersionTolerance.HasValue ? VersionTolerance.Value : settings.VersionTolerance, + KnownTypesProvider ?? settings.KnownTypesProvider, + PackageNameOverrides ?? settings.PackageNameOverrides + ); + + public HyperionSerializerSetup WithPreserveObjectReference(bool preserveObjectReference) + => Copy(preserveObjectReferences: preserveObjectReference); + + public HyperionSerializerSetup WithVersionTolerance(bool versionTolerance) + => Copy(versionTolerance: versionTolerance); + + public HyperionSerializerSetup WithKnownTypeProvider() + => Copy(knownTypesProvider: typeof(T)); + + public HyperionSerializerSetup WithKnownTypeProvider(Type knownTypeProvider) + => Copy(knownTypesProvider: knownTypeProvider); + + public HyperionSerializerSetup WithPackageNameOverrides(IEnumerable> packageNameOverrides) + => Copy(packageNameOverrides: packageNameOverrides); + + private HyperionSerializerSetup Copy( + bool? preserveObjectReferences = null, + bool? versionTolerance = null, + Type knownTypesProvider = null, + IEnumerable> packageNameOverrides = null) + => new HyperionSerializerSetup( + preserveObjectReferences ?? PreserveObjectReferences, + versionTolerance ?? VersionTolerance, + knownTypesProvider ?? KnownTypesProvider, + packageNameOverrides ?? PackageNameOverrides + ); + } +} diff --git a/src/contrib/serializers/Akka.Serialization.Hyperion/Properties/AssemblyInfo.cs b/src/contrib/serializers/Akka.Serialization.Hyperion/Properties/AssemblyInfo.cs index c43ab52c46c..620b28cb98c 100644 --- a/src/contrib/serializers/Akka.Serialization.Hyperion/Properties/AssemblyInfo.cs +++ b/src/contrib/serializers/Akka.Serialization.Hyperion/Properties/AssemblyInfo.cs @@ -21,6 +21,8 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("6c995bf6-1f8d-40ee-b608-68e36ea6a6f8")] +[assembly: InternalsVisibleTo("Akka.Serialization.Hyperion.Tests")] + // Version information for an assembly consists of the following four values: // // Major Version diff --git a/src/contrib/serializers/Akka.Serialization.Hyperion/reference.conf b/src/contrib/serializers/Akka.Serialization.Hyperion/reference.conf index d7d660d84b1..4589fc8480b 100644 --- a/src/contrib/serializers/Akka.Serialization.Hyperion/reference.conf +++ b/src/contrib/serializers/Akka.Serialization.Hyperion/reference.conf @@ -22,6 +22,40 @@ # time. No types are known by default. Implementing class must expose either a default # constructor or constructor accepting an `ExtendedActorSystem` as its only parameter. known-types-provider = "Akka.Serialization.NoKnownTypes, Akka.Serialization.Hyperion" + + # A list of incompatible dll package name for deserializing types + # between NetFx, .NET Core, and the new .NET + # Used to map and rename/correct different dll names between different platforms + # in the .NET ecosystem. + # + # How it works is that when the serializer detects that the type name contains + # the fingerprint string, it will replace the string declared in the rename-from + # property into the string declared in the rename-to property. + # + # if(packageName.Contains(fingerprint)) packageName = packageName.Replace(rename-from, rename-to); + # + # The commented values below are the default values used in the serializer, + # provided as an example on how to use this feature. + cross-platform-package-name-overrides = { + # netfx = [ + # { + # fingerprint = "System.Private.CoreLib,%core%", + # rename-from = "System.Private.CoreLib,%core%", + # rename-to = "mscorlib,%core%" + # }] + # netcore = [ + # { + # fingerprint = "mscorlib,%core%", + # rename-from = "mscorlib,%core%", + # rename-to = "System.Private.CoreLib,%core%" + # }] + # net = [ + # { + # fingerprint = "mscorlib,%core%", + # rename-from = "mscorlib,%core%", + # rename-to = "System.Private.CoreLib,%core%" + # }] + } } } } \ No newline at end of file diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCluster.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCluster.approved.txt index c3f9f370ec0..c8c6f3b7c0e 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCluster.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCluster.approved.txt @@ -1,4 +1,5 @@ [assembly: System.Reflection.AssemblyMetadataAttribute("RepositoryUrl", "https://github.com/akkadotnet/akka.net")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Benchmarks")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Metrics")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Sharding")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Akka.Cluster.Sharding.Tests")] @@ -219,6 +220,7 @@ namespace Akka.Cluster public string UseDispatcher { get; } public bool VerboseGossipReceivedLogging { get; } public bool VerboseHeartbeatLogging { get; } + public System.TimeSpan WeaklyUpAfter { get; } } [Akka.Annotations.InternalApiAttribute()] public interface IClusterActorRefProvider : Akka.Actor.IActorRefProvider, Akka.Remote.IRemoteActorRefProvider { } @@ -437,11 +439,12 @@ namespace Akka.Cluster.SBR } namespace Akka.Cluster.Serialization { - public class ClusterMessageSerializer : Akka.Serialization.Serializer + [Akka.Annotations.InternalApiAttribute()] + public class ClusterMessageSerializer : Akka.Serialization.SerializerWithStringManifest { public ClusterMessageSerializer(Akka.Actor.ExtendedActorSystem system) { } - public override bool IncludeManifest { get; } - public override object FromBinary(byte[] bytes, System.Type type) { } + public override object FromBinary(byte[] bytes, string manifest) { } + public override string Manifest(object o) { } public override byte[] ToBinary(object obj) { } } } \ No newline at end of file diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveClusterSharding.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveClusterSharding.approved.txt index dc5aac9abde..03714456bf2 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveClusterSharding.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveClusterSharding.approved.txt @@ -253,7 +253,7 @@ namespace Akka.Cluster.Sharding public Akka.Cluster.Sharding.ShardedDaemonProcessSettings WithRole(string role) { } public Akka.Cluster.Sharding.ShardedDaemonProcessSettings WithShardingSettings(Akka.Cluster.Sharding.ClusterShardingSettings shardingSettings) { } } - public sealed class ShardingEnvelope + public sealed class ShardingEnvelope : Akka.Actor.IWrappedMessage { public ShardingEnvelope(string entityId, object message) { } public string EntityId { get; } diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveClusterTools.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveClusterTools.approved.txt index 60b0840e284..df676d7749b 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveClusterTools.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveClusterTools.approved.txt @@ -229,7 +229,7 @@ namespace Akka.Cluster.Tools.PublishSubscribe public static Akka.Cluster.Tools.PublishSubscribe.GetTopics Instance { get; } } public interface IDistributedPubSubMessage { } - public sealed class Publish : Akka.Cluster.Tools.PublishSubscribe.IDistributedPubSubMessage, System.IEquatable + public sealed class Publish : Akka.Actor.IWrappedMessage, Akka.Cluster.Tools.PublishSubscribe.IDistributedPubSubMessage, System.IEquatable { public Publish(string topic, object message, bool sendOneMessageToEachGroup = False) { } public object Message { get; } @@ -258,7 +258,7 @@ namespace Akka.Cluster.Tools.PublishSubscribe public override int GetHashCode() { } public override string ToString() { } } - public sealed class Send : Akka.Cluster.Tools.PublishSubscribe.IDistributedPubSubMessage, System.IEquatable + public sealed class Send : Akka.Actor.IWrappedMessage, Akka.Cluster.Tools.PublishSubscribe.IDistributedPubSubMessage, System.IEquatable { public Send(string path, object message, bool localAffinity = False) { } public bool LocalAffinity { get; } @@ -269,7 +269,7 @@ namespace Akka.Cluster.Tools.PublishSubscribe public override int GetHashCode() { } public override string ToString() { } } - public sealed class SendToAll : Akka.Cluster.Tools.PublishSubscribe.IDistributedPubSubMessage, System.IEquatable + public sealed class SendToAll : Akka.Actor.IWrappedMessage, Akka.Cluster.Tools.PublishSubscribe.IDistributedPubSubMessage, System.IEquatable { public SendToAll(string path, object message, bool excludeSelf = False) { } public bool ExcludeSelf { get; } diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt index 7d9a54420db..2def445471a 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt @@ -320,7 +320,7 @@ namespace Akka.Actor public void Tell(object message, Akka.Actor.IActorRef sender = null) { } public override string ToString() { } } - public class ActorSelectionMessage : Akka.Actor.IAutoReceivedMessage, Akka.Actor.IPossiblyHarmful + public class ActorSelectionMessage : Akka.Actor.IAutoReceivedMessage, Akka.Actor.IPossiblyHarmful, Akka.Actor.IWrappedMessage { public ActorSelectionMessage(object message, Akka.Actor.SelectionPathElement[] elements, bool wildCardFanOut = False) { } public Akka.Actor.SelectionPathElement[] Elements { get; } @@ -1145,6 +1145,10 @@ namespace Akka.Actor Akka.Actor.ITimerScheduler Timers { get; set; } } public interface IWithUnboundedStash : Akka.Actor.IActorStash, Akka.Dispatch.IRequiresMessageQueue { } + public interface IWrappedMessage + { + object Message { get; } + } public sealed class Identify : Akka.Actor.IAutoReceivedMessage, Akka.Actor.INotInfluenceReceiveTimeout { public Identify(object messageId) { } @@ -1617,6 +1621,7 @@ namespace Akka.Actor } public class SelectChildRecursive : Akka.Actor.SelectionPathElement { + public static readonly Akka.Actor.SelectChildRecursive Instance; public SelectChildRecursive() { } public override bool Equals(object obj) { } public override int GetHashCode() { } @@ -1624,6 +1629,7 @@ namespace Akka.Actor } public class SelectParent : Akka.Actor.SelectionPathElement { + public static readonly Akka.Actor.SelectParent Instance; public SelectParent() { } public override bool Equals(object obj) { } public override int GetHashCode() { } @@ -1797,6 +1803,10 @@ namespace Akka.Actor protected void RunTask(System.Func action) { } } public delegate void UntypedReceive(object message); + public class static WrappedMessage + { + public static object Unwrap(object message) { } + } } namespace Akka.Actor.Dsl { @@ -2091,6 +2101,11 @@ namespace Akka.Annotations { public ApiMayChangeAttribute() { } } + [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Interface | System.AttributeTargets.All, AllowMultiple=false, Inherited=true)] + public sealed class DoNotInheritAttribute : System.Attribute + { + public DoNotInheritAttribute() { } + } [System.AttributeUsageAttribute(System.AttributeTargets.Module | System.AttributeTargets.Class | System.AttributeTargets.Struct | System.AttributeTargets.Enum | System.AttributeTargets.Constructor | System.AttributeTargets.Method | System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Interface | System.AttributeTargets.All, AllowMultiple=false, Inherited=true)] public sealed class InternalApiAttribute : System.Attribute { @@ -2879,7 +2894,7 @@ namespace Akka.Event { protected ActorEventBus() { } } - public abstract class AllDeadLetters + public abstract class AllDeadLetters : Akka.Actor.IWrappedMessage { protected AllDeadLetters(object message, Akka.Actor.IActorRef sender, Akka.Actor.IActorRef recipient) { } public object Message { get; } @@ -2933,6 +2948,12 @@ namespace Akka.Event protected virtual void Print(Akka.Event.LogEvent logEvent) { } protected override bool Receive(object message) { } } + public sealed class Dropped : Akka.Event.AllDeadLetters + { + public Dropped(object message, string reason, Akka.Actor.IActorRef sender, Akka.Actor.IActorRef recipient) { } + public Dropped(object message, string reason, Akka.Actor.IActorRef recipient) { } + public string Reason { get; } + } public class DummyClassForStringSources { public DummyClassForStringSources() { } @@ -3155,12 +3176,9 @@ namespace Akka.Event public TraceLogger() { } protected override void OnReceive(object message) { } } - public sealed class UnhandledMessage + public sealed class UnhandledMessage : Akka.Event.AllDeadLetters, Akka.Actor.IWrappedMessage { public UnhandledMessage(object message, Akka.Actor.IActorRef sender, Akka.Actor.IActorRef recipient) { } - public object Message { get; } - public Akka.Actor.IActorRef Recipient { get; } - public Akka.Actor.IActorRef Sender { get; } } public class Warning : Akka.Event.LogEvent { @@ -3238,7 +3256,7 @@ namespace Akka.IO public static Akka.IO.Dns.Resolved Cached(string name, Akka.Actor.ActorSystem system) { } public override Akka.IO.DnsExt CreateExtension(Akka.Actor.ExtendedActorSystem system) { } public static Akka.IO.Dns.Resolved ResolveName(string name, Akka.Actor.ActorSystem system, Akka.Actor.IActorRef sender) { } - public abstract class Command + public abstract class Command : Akka.Actor.INoSerializationVerificationNeeded { protected Command() { } } @@ -3667,7 +3685,7 @@ namespace Akka.IO { protected Event() { } } - public abstract class Message + public abstract class Message : Akka.Actor.INoSerializationVerificationNeeded { protected Message() { } } @@ -3774,7 +3792,7 @@ namespace Akka.IO { protected Event() { } } - public abstract class Message + public abstract class Message : Akka.Actor.INoSerializationVerificationNeeded { protected Message() { } } @@ -3809,7 +3827,7 @@ namespace Akka.IO public static readonly Akka.IO.UdpConnected.SuspendReading Instance; } } - public class UdpConnectedExt : Akka.IO.IOExtension + public class UdpConnectedExt : Akka.IO.IOExtension, Akka.Actor.INoSerializationVerificationNeeded { public UdpConnectedExt(Akka.Actor.ExtendedActorSystem system) { } public UdpConnectedExt(Akka.Actor.ExtendedActorSystem system, Akka.IO.UdpSettings settings) { } @@ -3967,6 +3985,13 @@ namespace Akka.Pattern public OpenCircuitException(System.Exception cause, System.TimeSpan remainingDuration) { } public System.TimeSpan RemainingDuration { get; } } + public class static RetrySupport + { + public static System.Threading.Tasks.Task Retry(System.Func> attempt, int attempts) { } + public static System.Threading.Tasks.Task Retry(System.Func> attempt, int attempts, System.TimeSpan minBackoff, System.TimeSpan maxBackoff, int randomFactor, Akka.Actor.IScheduler scheduler) { } + public static System.Threading.Tasks.Task Retry(System.Func> attempt, int attempts, System.TimeSpan delay, Akka.Actor.IScheduler scheduler) { } + public static System.Threading.Tasks.Task Retry(System.Func> attempt, int attempts, System.Func> delayFunction, Akka.Actor.IScheduler scheduler) { } + } public class UserCalledFailException : Akka.Actor.AkkaException { public UserCalledFailException() { } @@ -4088,7 +4113,7 @@ namespace Akka.Routing public Akka.Util.ISurrogated FromSurrogate(Akka.Actor.ActorSystem system) { } } } - public sealed class ConsistentHashableEnvelope : Akka.Routing.RouterEnvelope, Akka.Routing.IConsistentHashable + public sealed class ConsistentHashableEnvelope : Akka.Routing.RouterEnvelope, Akka.Actor.IWrappedMessage, Akka.Routing.IConsistentHashable { public ConsistentHashableEnvelope(object message, object hashKey) { } public object ConsistentHashKey { get; } @@ -4643,6 +4668,11 @@ namespace Akka.Serialization public bool PreserveObjectReferences { get; } public static Akka.Serialization.NewtonSoftJsonSerializerSettings Create(Akka.Configuration.Config config) { } } + public sealed class NewtonSoftJsonSerializerSetup : Akka.Actor.Setup.Setup + { + public System.Action ApplySettings { get; } + public static Akka.Serialization.NewtonSoftJsonSerializerSetup Create(System.Action settings) { } + } public class NullSerializer : Akka.Serialization.Serializer { public NullSerializer(Akka.Actor.ExtendedActorSystem system) { } diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveDistributedData.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveDistributedData.approved.txt index 2c018f0f93d..fa45f6893cd 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveDistributedData.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveDistributedData.approved.txt @@ -1012,10 +1012,10 @@ namespace Akka.DistributedData.Internal } public sealed class Delta : System.IEquatable { - public readonly Akka.DistributedData.Internal.DataEnvelope DataEnvelope; - public readonly long FromSeqNr; - public readonly long ToSeqNr; public Delta(Akka.DistributedData.Internal.DataEnvelope dataEnvelope, long fromSeqNr, long toSeqNr) { } + public Akka.DistributedData.Internal.DataEnvelope DataEnvelope { get; } + public long FromSeqNr { get; } + public long ToSeqNr { get; } public bool Equals(Akka.DistributedData.Internal.Delta other) { } public override bool Equals(object obj) { } public override int GetHashCode() { } @@ -1029,11 +1029,11 @@ namespace Akka.DistributedData.Internal } public sealed class DeltaPropagation : Akka.DistributedData.IReplicatorMessage, System.IEquatable { - public readonly System.Collections.Immutable.ImmutableDictionary Deltas; - public readonly Akka.Cluster.UniqueAddress FromNode; public static readonly Akka.DistributedData.IReplicatedDelta NoDeltaPlaceholder; - public readonly bool ShouldReply; public DeltaPropagation(Akka.Cluster.UniqueAddress fromNode, bool shouldReply, System.Collections.Immutable.ImmutableDictionary deltas) { } + public System.Collections.Immutable.ImmutableDictionary Deltas { get; } + public Akka.Cluster.UniqueAddress FromNode { get; } + public bool ShouldReply { get; } public bool Equals(Akka.DistributedData.Internal.DeltaPropagation other) { } public override bool Equals(object obj) { } public override int GetHashCode() { } diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApprovePersistence.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApprovePersistence.approved.txt index c36330c5341..11c5c2a144d 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApprovePersistence.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApprovePersistence.approved.txt @@ -113,7 +113,7 @@ namespace Akka.Persistence public override int GetHashCode() { } public override string ToString() { } } - public sealed class DeleteMessagesFailure : System.IEquatable + public sealed class DeleteMessagesFailure : Akka.Actor.INoSerializationVerificationNeeded, System.IEquatable { public DeleteMessagesFailure(System.Exception cause, long toSequenceNr) { } public System.Exception Cause { get; } diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveRemote.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveRemote.approved.txt index e6cd624f47e..f3fc63faff7 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveRemote.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveRemote.approved.txt @@ -147,6 +147,7 @@ namespace Akka.Remote public PhiAccrualFailureDetector(double threshold, int maxSampleSize, System.TimeSpan minStdDeviation, System.TimeSpan acceptableHeartbeatPause, System.TimeSpan firstHeartbeatEstimate, Akka.Remote.Clock clock = null) { } public PhiAccrualFailureDetector(Akka.Configuration.Config config, Akka.Event.EventStream ev) { } protected PhiAccrualFailureDetector(Akka.Remote.Clock clock) { } + public string Address { get; set; } public override bool IsAvailable { get; } public override bool IsMonitoring { get; } public override void HeartBeat() { } diff --git a/src/core/Akka.Cluster.TestKit/MultiNodeClusterSpec.cs b/src/core/Akka.Cluster.TestKit/MultiNodeClusterSpec.cs index e9a6f8e931c..ec7fd773968 100644 --- a/src/core/Akka.Cluster.TestKit/MultiNodeClusterSpec.cs +++ b/src/core/Akka.Cluster.TestKit/MultiNodeClusterSpec.cs @@ -175,7 +175,7 @@ protected override void AfterTermination() //TODO: ExpectedTestDuration? - void MuteLog(ActorSystem sys = null) + public virtual void MuteLog(ActorSystem sys = null) { if (sys == null) sys = Sys; if (!sys.Log.IsDebugEnabled) diff --git a/src/core/Akka.Cluster.Tests.MultiNode/Akka.Cluster.Tests.MultiNode.csproj b/src/core/Akka.Cluster.Tests.MultiNode/Akka.Cluster.Tests.MultiNode.csproj index ffc6127b3c5..e1488e9f55b 100644 --- a/src/core/Akka.Cluster.Tests.MultiNode/Akka.Cluster.Tests.MultiNode.csproj +++ b/src/core/Akka.Cluster.Tests.MultiNode/Akka.Cluster.Tests.MultiNode.csproj @@ -4,6 +4,7 @@ Akka.Cluster.Tests.MultiNode $(NetFrameworkTestVersion);$(NetTestVersion);$(NetCoreTestVersion) + latest diff --git a/src/core/Akka.Cluster.Tests.MultiNode/Bugfix4353Specs.cs b/src/core/Akka.Cluster.Tests.MultiNode/Bugfix4353Specs.cs index 63b41428a2e..347fd141ac1 100644 --- a/src/core/Akka.Cluster.Tests.MultiNode/Bugfix4353Specs.cs +++ b/src/core/Akka.Cluster.Tests.MultiNode/Bugfix4353Specs.cs @@ -36,7 +36,7 @@ protected Bugfix4353Spec(Bugfix4353SpecsConfig config) : base(config, typeof(Bug } [MultiNodeFact] - public void Bugfix4353Spec_Cluster_of_3_must_reach_cnovergence() + public void Bugfix4353Spec_Cluster_of_3_must_reach_convergence() { AwaitClusterUp(First, Second, Third); EnterBarrier("after-1"); diff --git a/src/core/Akka.Cluster.Tests.MultiNode/MemberWeaklyUpSpec.cs b/src/core/Akka.Cluster.Tests.MultiNode/MemberWeaklyUpSpec.cs index ae7998d7afd..1d3a143a7a1 100644 --- a/src/core/Akka.Cluster.Tests.MultiNode/MemberWeaklyUpSpec.cs +++ b/src/core/Akka.Cluster.Tests.MultiNode/MemberWeaklyUpSpec.cs @@ -35,7 +35,7 @@ public MemberWeaklyUpConfig() CommonConfig = DebugConfig(on: false) .WithFallback(ConfigurationFactory.ParseString(@" akka.remote.retry-gate-closed-for = 3s - akka.cluster.allow-weakly-up-members = on")) + akka.cluster.allow-weakly-up-members = 3s")) .WithFallback(MultiNodeClusterSpec.ClusterConfig()); TestTransport = true; diff --git a/src/core/Akka.Cluster.Tests.MultiNode/MinMembersBeforeUpSpec.cs b/src/core/Akka.Cluster.Tests.MultiNode/MinMembersBeforeUpSpec.cs index 5f3e0d4642f..f7381a18cc8 100644 --- a/src/core/Akka.Cluster.Tests.MultiNode/MinMembersBeforeUpSpec.cs +++ b/src/core/Akka.Cluster.Tests.MultiNode/MinMembersBeforeUpSpec.cs @@ -103,7 +103,7 @@ public MinMembersBeforeUpWithWeaklyUpSpecConfig() CommonConfig = ConfigurationFactory.ParseString(@" akka.cluster.min-nr-of-members = 3 - akka.cluster.allow-weakly-up-members = on + akka.cluster.allow-weakly-up-members = 3s ").WithFallback(MultiNodeClusterSpec.ClusterConfigWithFailureDetectorPuppet()); } } diff --git a/src/core/Akka.Cluster.Tests.MultiNode/RestartFirstSeedNodeSpec.cs b/src/core/Akka.Cluster.Tests.MultiNode/RestartFirstSeedNodeSpec.cs index 0b4944b5ccf..fbffa29b4dd 100644 --- a/src/core/Akka.Cluster.Tests.MultiNode/RestartFirstSeedNodeSpec.cs +++ b/src/core/Akka.Cluster.Tests.MultiNode/RestartFirstSeedNodeSpec.cs @@ -43,7 +43,7 @@ public class RestartFirstSeedNodeSpec : MultiNodeClusterSpec { private readonly RestartFirstSeedNodeSpecConfig _config; private Address _missedSeed; - private static Address _seedNode1Address; + private static volatile Address _seedNode1Address; private Lazy seed1System; private Lazy restartedSeed1System; @@ -104,7 +104,9 @@ public void Cluster_seed_nodes__must_be_able_to_restart_first_seed_node_and_join // now we can join seed1System, seed2, seed3 together RunOn(() => { - Cluster.Get(seed1System.Value).JoinSeedNodes(GetSeedNodes()); + var seeds = GetSeedNodes(); + seeds.Count.Should().Be(4); // validate that we have complete seed node list + Cluster.Get(seed1System.Value).JoinSeedNodes(seeds); AwaitAssert(() => { Cluster.Get(seed1System.Value) @@ -122,7 +124,9 @@ public void Cluster_seed_nodes__must_be_able_to_restart_first_seed_node_and_join }, _config.Seed1); RunOn(() => { - Cluster.JoinSeedNodes(GetSeedNodes()); + var seeds = GetSeedNodes(); + seeds.Count.Should().Be(4); // validate that we have complete seed node list + Cluster.JoinSeedNodes(seeds); AwaitMembersUp(3); }, _config.Seed2, _config.Seed3); EnterBarrier("started"); diff --git a/src/core/Akka.Cluster.Tests.MultiNode/StressSpec.cs b/src/core/Akka.Cluster.Tests.MultiNode/StressSpec.cs new file mode 100644 index 00000000000..444dfbf8987 --- /dev/null +++ b/src/core/Akka.Cluster.Tests.MultiNode/StressSpec.cs @@ -0,0 +1,1420 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using Akka.Actor; +using Akka.Cluster.TestKit; +using Akka.Configuration; +using Akka.Event; +using Akka.Remote; +using Akka.Remote.TestKit; +using Akka.Remote.Transport; +using Akka.TestKit; +using Akka.TestKit.Internal; +using Akka.TestKit.Internal.StringMatcher; +using Akka.TestKit.TestEvent; +using Akka.Util; +using FluentAssertions; +using Google.Protobuf.WellKnownTypes; +using Environment = System.Environment; + +namespace Akka.Cluster.Tests.MultiNode +{ + public class StressSpecConfig : MultiNodeConfig + { + public int TotalNumberOfNodes => Environment.GetEnvironmentVariable("MNTR_STRESSSPEC_NODECOUNT") switch + { + string e when string.IsNullOrEmpty(e) => 13, + string val => int.Parse(val), + _ => 13 + }; + + public StressSpecConfig() + { + foreach (var i in Enumerable.Range(1, TotalNumberOfNodes)) + Role("node-" + i); + + CommonConfig = ConfigurationFactory.ParseString(@" + akka.test.cluster-stress-spec { + infolog = on + # scale the nr-of-nodes* settings with this factor + nr-of-nodes-factor = 1 + # not scaled + nr-of-seed-nodes = 3 + nr-of-nodes-joining-to-seed-initially = 2 + nr-of-nodes-joining-one-by-one-small = 2 + nr-of-nodes-joining-one-by-one-large = 2 + nr-of-nodes-joining-to-one = 2 + nr-of-nodes-leaving-one-by-one-small = 1 + nr-of-nodes-leaving-one-by-one-large = 1 + nr-of-nodes-leaving = 2 + nr-of-nodes-shutdown-one-by-one-small = 1 + nr-of-nodes-shutdown-one-by-one-large = 1 + nr-of-nodes-partition = 2 + nr-of-nodes-shutdown = 2 + nr-of-nodes-join-remove = 2 + # not scaled + # scale the *-duration settings with this factor + duration-factor = 1 + join-remove-duration = 90s + idle-gossip-duration = 10s + expected-test-duration = 600s + # scale convergence within timeouts with this factor + convergence-within-factor = 1.0 + } + akka.actor.provider = cluster + + akka.cluster { + failure-detector.acceptable-heartbeat-pause = 3s + downing-provider-class = ""Akka.Cluster.SplitBrainResolver, Akka.Cluster"" + split-brain-resolver { + active-strategy = keep-majority #TODO: remove this once it's been made default + stable-after = 10s + } + publish-stats-interval = 1s + } + akka.loggers = [""Akka.TestKit.TestEventListener, Akka.TestKit""] + akka.loglevel = INFO + akka.remote.log-remote-lifecycle-events = off + akka.actor.default-dispatcher = { + executor = channel-executor + fork-join-executor { + parallelism-min = 2 + parallelism-factor = 1 + parallelism-max = 64 + } + } + akka.actor.internal-dispatcher = { + executor = channel-executor + fork-join-executor { + parallelism-min = 2 + parallelism-factor = 1 + parallelism-max = 64 + } + } +akka.remote.default-remote-dispatcher { + executor = channel-executor + fork-join-executor { + parallelism-min = 2 + parallelism-factor = 0.5 + parallelism-max = 16 + } + "); + + TestTransport = true; + } + + public class Settings + { + private readonly Config _testConfig; + + public Settings(Config config, int totalNumberOfNodes) + { + TotalNumberOfNodes = totalNumberOfNodes; + _testConfig = config.GetConfig("akka.test.cluster-stress-spec"); + Infolog = _testConfig.GetBoolean("infolog"); + NFactor = _testConfig.GetInt("nr-of-nodes-factor"); + NumberOfSeedNodes = _testConfig.GetInt("nr-of-seed-nodes"); + NumberOfNodesJoiningToSeedNodesInitially = + _testConfig.GetInt("nr-of-nodes-joining-to-seed-initially") * NFactor; + NumberOfNodesJoiningOneByOneSmall = _testConfig.GetInt("nr-of-nodes-joining-one-by-one-small") * NFactor; + NumberOfNodesJoiningOneByOneLarge = _testConfig.GetInt("nr-of-nodes-joining-one-by-one-large") * NFactor; + NumberOfNodesJoiningToOneNode = _testConfig.GetInt("nr-of-nodes-joining-to-one") * NFactor; + // remaining will join to seed nodes + NumberOfNodesJoiningToSeedNodes = (totalNumberOfNodes - NumberOfSeedNodes - + NumberOfNodesJoiningToSeedNodesInitially - + NumberOfNodesJoiningOneByOneSmall - + NumberOfNodesJoiningOneByOneLarge - NumberOfNodesJoiningToOneNode); + if (NumberOfNodesJoiningToSeedNodes < 0) + throw new ArgumentOutOfRangeException("nr-of-nodes-joining-*", + $"too many configured nr-of-nodes-joining-*, total should be <= {totalNumberOfNodes}"); + + NumberOfNodesLeavingOneByOneSmall = _testConfig.GetInt("nr-of-nodes-leaving-one-by-one-small") * NFactor; + NumberOfNodesLeavingOneByOneLarge = _testConfig.GetInt("nr-of-nodes-leaving-one-by-one-large") * NFactor; + NumberOfNodesLeaving = _testConfig.GetInt("nr-of-nodes-leaving") * NFactor; + NumberOfNodesShutdownOneByOneSmall = _testConfig.GetInt("nr-of-nodes-shutdown-one-by-one-small") * NFactor; + NumberOfNodesShutdownOneByOneLarge = _testConfig.GetInt("nr-of-nodes-shutdown-one-by-one-large") * NFactor; + NumberOfNodesShutdown = _testConfig.GetInt("nr-of-nodes-shutdown") * NFactor; + NumberOfNodesPartition = _testConfig.GetInt("nr-of-nodes-partition") * NFactor; + NumberOfNodesJoinRemove = _testConfig.GetInt("nr-of-nodes-join-remove"); // not scaled by nodes factor + + DFactor = _testConfig.GetInt("duration-factor"); + JoinRemoveDuration = TimeSpan.FromMilliseconds(_testConfig.GetTimeSpan("join-remove-duration").TotalMilliseconds * DFactor); + IdleGossipDuration = TimeSpan.FromMilliseconds(_testConfig.GetTimeSpan("idle-gossip-duration").TotalMilliseconds * DFactor); + ExpectedTestDuration = TimeSpan.FromMilliseconds(_testConfig.GetTimeSpan("expected-test-duration").TotalMilliseconds * DFactor); + ConvergenceWithinFactor = _testConfig.GetDouble("convergence-within-factor"); + + if (NumberOfSeedNodes + NumberOfNodesJoiningToSeedNodesInitially + NumberOfNodesJoiningOneByOneSmall + + NumberOfNodesJoiningOneByOneLarge + NumberOfNodesJoiningToOneNode + + NumberOfNodesJoiningToSeedNodes > totalNumberOfNodes) + { + throw new ArgumentOutOfRangeException("nr-of-nodes-joining-*", + $"specified number of joining nodes <= {totalNumberOfNodes}"); + } + + // don't shutdown the 3 nodes hosting the master actors + if (NumberOfNodesLeavingOneByOneSmall + NumberOfNodesLeavingOneByOneLarge + NumberOfNodesLeaving + + NumberOfNodesShutdownOneByOneSmall + NumberOfNodesShutdownOneByOneLarge + NumberOfNodesShutdown > + totalNumberOfNodes - 3) + { + throw new ArgumentOutOfRangeException("nr-of-nodes-leaving-*", + $"specified number of leaving/shutdown nodes <= {totalNumberOfNodes - 3}"); + } + + if (NumberOfNodesJoinRemove > totalNumberOfNodes) + { + throw new ArgumentOutOfRangeException("nr-of-nodes-join-remove*", + $"nr-of-nodes-join-remove should be <= {totalNumberOfNodes}"); + } + } + + public int TotalNumberOfNodes { get; } + + public bool Infolog { get; } + public int NFactor { get; } + + public int NumberOfSeedNodes { get; } + + public int NumberOfNodesJoiningToSeedNodesInitially { get; } + + public int NumberOfNodesJoiningOneByOneSmall { get; } + + public int NumberOfNodesJoiningOneByOneLarge { get; } + + public int NumberOfNodesJoiningToOneNode { get; } + + public int NumberOfNodesJoiningToSeedNodes { get; } + + public int NumberOfNodesLeavingOneByOneSmall { get; } + + public int NumberOfNodesLeavingOneByOneLarge { get; } + + public int NumberOfNodesLeaving { get; } + + public int NumberOfNodesShutdownOneByOneSmall { get; } + + public int NumberOfNodesShutdownOneByOneLarge { get; } + + public int NumberOfNodesShutdown { get; } + + public int NumberOfNodesPartition { get; } + + public int NumberOfNodesJoinRemove { get; } + + public int DFactor { get; } + + public TimeSpan JoinRemoveDuration { get; } + + public TimeSpan IdleGossipDuration { get; } + + public TimeSpan ExpectedTestDuration { get; } + + public double ConvergenceWithinFactor { get; } + + public override string ToString() + { + return _testConfig.WithFallback($"nrOfNodes={TotalNumberOfNodes}").Root.ToString(2); + } + } + } + + internal sealed class ClusterResult + { + public ClusterResult(Address address, TimeSpan duration, GossipStats clusterStats) + { + Address = address; + Duration = duration; + ClusterStats = clusterStats; + } + + public Address Address { get; } + public TimeSpan Duration { get; } + public GossipStats ClusterStats { get; } + } + + internal sealed class AggregatedClusterResult + { + public AggregatedClusterResult(string title, TimeSpan duration, GossipStats clusterStats) + { + Title = title; + Duration = duration; + ClusterStats = clusterStats; + } + + public string Title { get; } + + public TimeSpan Duration { get; } + + public GossipStats ClusterStats { get; } + } + + /// + /// Central aggregator of cluster statistics and metrics. + /// + /// Reports the result via log periodically and when all + /// expected results has been collected. It shuts down + /// itself when expected results has been collected. + /// + internal class ClusterResultAggregator : ReceiveActor + { + private readonly string _title; + private readonly int _expectedResults; + private readonly StressSpecConfig.Settings _settings; + + private readonly ILoggingAdapter _log = Context.GetLogger(); + + private Option _reportTo = Option.None; + private ImmutableList _results = ImmutableList.Empty; + private ImmutableSortedDictionary> _phiValuesObservedByNode = + ImmutableSortedDictionary>.Empty.WithComparers(Member.AddressOrdering); + private ImmutableSortedDictionary _clusterStatsObservedByNode = + ImmutableSortedDictionary.Empty.WithComparers(Member.AddressOrdering); + + public static readonly string FormatPhiHeader = "[Monitor]\t[Subject]\t[count]\t[count phi > 1.0]\t[max phi]"; + + public string FormatPhiLine(Address monitor, Address subject, PhiValue phi) + { + return $"{monitor}\t{subject}\t{phi.Count}\t{phi.CountAboveOne}\t{phi.Max:F2}"; + } + + public string FormatPhi() + { + if (_phiValuesObservedByNode.IsEmpty) return string.Empty; + else + { + + var lines = (from mon in _phiValuesObservedByNode from phi in mon.Value select FormatPhiLine(mon.Key, phi.Address, phi)); + return FormatPhiHeader + Environment.NewLine + string.Join(Environment.NewLine, lines); + } + } + + public TimeSpan MaxDuration => _results.Max(x => x.Duration); + + public GossipStats TotalGossipStats => + _results.Aggregate(new GossipStats(), (stats, result) => stats += result.ClusterStats); + + public string FormatStats() + { + string F(ClusterEvent.CurrentInternalStats stats) + { + return + $"CurrentClusterStats({stats.GossipStats?.ReceivedGossipCount}, {stats.GossipStats?.MergeCount}, " + + $"{stats.GossipStats?.SameCount}, {stats.GossipStats?.NewerCount}, {stats.GossipStats?.OlderCount}," + + $"{stats.SeenBy?.VersionSize}, {stats.SeenBy?.SeenLatest})"; + } + + return string.Join(Environment.NewLine, "ClusterStats(gossip, merge, same, newer, older, vclockSize, seenLatest)" + + Environment.NewLine + + string.Join(Environment.NewLine, _clusterStatsObservedByNode.Select(x => $"{x.Key}\t{F(x.Value)}"))); + } + + public ClusterResultAggregator(string title, int expectedResults, StressSpecConfig.Settings settings) + { + _title = title; + _expectedResults = expectedResults; + _settings = settings; + + Receive(phi => + { + _phiValuesObservedByNode = _phiValuesObservedByNode.SetItem(phi.Address, phi.PhiValues); + }); + + Receive(stats => + { + _clusterStatsObservedByNode = _clusterStatsObservedByNode.SetItem(stats.Address, stats.Stats); + }); + + Receive(_ => + { + if (_settings.Infolog) + { + _log.Info("BEGIN CLUSTER OPERATION: [{0}] in progress" + Environment.NewLine + "{1}" + Environment.NewLine + "{2}", _title, + FormatPhi(), FormatStats()); + } + }); + + Receive(r => + { + _results = _results.Add(r); + if (_results.Count == _expectedResults) + { + var aggregated = new AggregatedClusterResult(_title, MaxDuration, TotalGossipStats); + if (_settings.Infolog) + { + _log.Info("END CLUSTER OPERATION: [{0}] completed in [{1}] ms" + Environment.NewLine + "{2}" + + Environment.NewLine + "{3}" + Environment.NewLine + "{4}", _title, aggregated.Duration.TotalMilliseconds, + aggregated.ClusterStats, FormatPhi(), FormatStats()); + } + _reportTo.OnSuccess(r => r.Tell(aggregated)); + Context.Stop(Self); + } + }); + + Receive(_ => { }); + Receive(re => + { + _reportTo = re.Ref; + }); + } + } + + /// + /// Keeps cluster statistics and metrics reported by . + /// + /// Logs the list of historical results when a new is received. + /// + internal class ClusterResultHistory : ReceiveActor + { + private ILoggingAdapter _log = Context.GetLogger(); + private ImmutableList _history = ImmutableList.Empty; + + public ClusterResultHistory() + { + Receive(result => + { + _history = _history.Add(result); + }); + } + + public static readonly string FormatHistoryHeader = "[Title]\t[Duration (ms)]\t[GossipStats(gossip, merge, same, newer, older)]"; + + public string FormatHistoryLine(AggregatedClusterResult result) + { + return $"{result.Title}\t{result.Duration.TotalMilliseconds}\t{result.ClusterStats}"; + } + + public string FormatHistory => FormatHistoryHeader + Environment.NewLine + + string.Join(Environment.NewLine, _history.Select(x => FormatHistoryLine(x))); + } + + /// + /// Collect phi values of the failure detector and report to the central + /// + internal class PhiObserver : ReceiveActor + { + private readonly Cluster _cluster = Cluster.Get(Context.System); + private readonly ILoggingAdapter _log = Context.GetLogger(); + private ImmutableDictionary _phiByNode = ImmutableDictionary.Empty; + + private Option _reportTo = Option.None; + private HashSet
_nodes = new HashSet
(); + + private ICancelable _checkPhiTask = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable( + TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(1), Context.Self, PhiTick.Instance, ActorRefs.NoSender); + + private double Phi(Address address) + { + return _cluster.FailureDetector switch + { + DefaultFailureDetectorRegistry
reg => (reg.GetFailureDetector(address)) switch + { + PhiAccrualFailureDetector fd => fd.CurrentPhi, + _ => 0.0d + }, + _ => 0.0d + }; + } + + private PhiValue PhiByNodeDefault(Address address) + { + if (!_phiByNode.ContainsKey(address)) + { + // populate default value + _phiByNode = _phiByNode.Add(address, new PhiValue(address, 0, 0, 0.0d)); + } + + return _phiByNode[address]; + } + + public PhiObserver() + { + Receive(_ => + { + foreach (var node in _nodes) + { + var previous = PhiByNodeDefault(node); + var p = Phi(node); + + if (p > 0 || _cluster.FailureDetector.IsMonitoring(node)) + { + if (double.IsInfinity(p)) + { + _log.Warning("Detected phi value of infinity for [{0}] - ", node); + var (history, time) = _cluster.FailureDetector.GetFailureDetector(node) switch + { + PhiAccrualFailureDetector fd => (fd.state.History, fd.state.TimeStamp), + _ => (HeartbeatHistory.Apply(1), null) + }; + _log.Warning("PhiValues: (Timestamp={0}, Mean={1}, Variance={2}, StdDeviation={3}, Intervals=[{4}])",time, + history.Mean, history.Variance, history.StdDeviation, + string.Join(",", history.Intervals)); + } + + var aboveOne = !double.IsInfinity(p) && p > 1.0d ? 1 : 0; + _phiByNode = _phiByNode.SetItem(node, new PhiValue(node, + previous.CountAboveOne + aboveOne, + previous.Count + 1, + Math.Max(previous.Max, p))); + } + } + + var phiSet = _phiByNode.Values.ToImmutableSortedSet(); + _reportTo.OnSuccess(r => r.Tell(new PhiResult(_cluster.SelfAddress, phiSet))); + }); + + Receive(state => + { + _nodes = new HashSet
(state.Members.Select(x => x.Address)); + }); + + Receive(m => + { + _nodes.Add(m.Member.Address); + }); + + Receive(r => + { + _reportTo.OnSuccess(o => Context.Unwatch(o)); + _reportTo = r.Ref; + _reportTo.OnSuccess(n => Context.Watch(n)); + }); + + Receive(t => + { + if (_reportTo.HasValue) + _reportTo = Option.None; + }); + + Receive(_ => + { + _phiByNode = ImmutableDictionary.Empty; + _nodes.Clear(); + _cluster.Unsubscribe(Self); + _cluster.Subscribe(Self, typeof(ClusterEvent.IMemberEvent)); + }); + } + + protected override void PreStart() + { + _cluster.Subscribe(Self, typeof(ClusterEvent.IMemberEvent)); + } + + protected override void PostStop() + { + _cluster.Unsubscribe(Self); + _checkPhiTask.Cancel(); + base.PostStop(); + } + } + + internal readonly struct PhiValue : IComparable + { + public PhiValue(Address address, int countAboveOne, int count, double max) + { + Address = address; + CountAboveOne = countAboveOne; + Count = count; + Max = max; + } + + public Address Address { get; } + public int CountAboveOne { get; } + public int Count { get; } + public double Max { get; } + + public int CompareTo(PhiValue other) + { + return Member.AddressOrdering.Compare(Address, other.Address); + } + } + + internal readonly struct PhiResult + { + public PhiResult(Address address, ImmutableSortedSet phiValues) + { + Address = address; + PhiValues = phiValues; + } + + public Address Address { get; } + + public ImmutableSortedSet PhiValues { get; } + } + + internal class StatsObserver : ReceiveActor + { + private readonly Cluster _cluster = Cluster.Get(Context.System); + private Option _reportTo = Option.None; + private Option _startStats = Option.None; + + protected override void PreStart() + { + _cluster.Subscribe(Self, typeof(ClusterEvent.CurrentInternalStats)); + } + + protected override void PostStop() + { + _cluster.Unsubscribe(Self); + } + + public StatsObserver() + { + Receive(stats => + { + var gossipStats = stats.GossipStats; + var vclockStats = stats.SeenBy; + + GossipStats MatchStats() + { + if (!_startStats.HasValue) + { + _startStats = gossipStats; + return gossipStats; + } + + return gossipStats -_startStats.Value; + } + + var diff = MatchStats(); + var res = new StatsResult(_cluster.SelfAddress, new ClusterEvent.CurrentInternalStats(diff, vclockStats)); + _reportTo.OnSuccess(a => a.Tell(res)); + }); + + Receive(r => + { + _reportTo.OnSuccess(o => Context.Unwatch(o)); + _reportTo = r.Ref; + _reportTo.OnSuccess(n => Context.Watch(n)); + }); + + Receive(t => + { + if (_reportTo.HasValue) + _reportTo = Option.None; + }); + + Receive(_ => + { + _startStats = Option.None; + }); + + // nothing interesting here + Receive(_ => { }); + } + } + + /// + /// Used for remote death watch testing + /// + internal class Watchee : ActorBase + { + protected override bool Receive(object message) + { + return true; + } + } + + internal sealed class Begin + { + public static readonly Begin Instance = new Begin(); + private Begin() { } + } + + internal sealed class End + { + public static readonly End Instance = new End(); + private End() { } + } + + internal sealed class RetryTick + { + public static readonly RetryTick Instance = new RetryTick(); + private RetryTick() { } + } + + internal sealed class ReportTick + { + public static readonly ReportTick Instance = new ReportTick(); + private ReportTick() { } + } + + internal sealed class PhiTick + { + public static readonly PhiTick Instance = new PhiTick(); + private PhiTick() { } + } + + internal sealed class ReportTo + { + public ReportTo(Option @ref) + { + Ref = @ref; + } + + public Option Ref { get; } + } + + internal sealed class StatsResult + { + public StatsResult(Address address, ClusterEvent.CurrentInternalStats stats) + { + Address = address; + Stats = stats; + } + + public Address Address { get; } + + public Akka.Cluster.ClusterEvent.CurrentInternalStats Stats { get; } + } + + internal sealed class Reset + { + public static readonly Reset Instance = new Reset(); + private Reset() { } + } + + internal class MeasureDurationUntilDown : ReceiveActor + { + private readonly Cluster _cluster = Cluster.Get(Context.System); + private readonly long _startTime; + private readonly ILoggingAdapter _log = Context.GetLogger(); + public MeasureDurationUntilDown() + { + _startTime = MonotonicClock.GetTicks(); + + Receive(d => + { + var m = d.Member; + if (m.UniqueAddress == _cluster.SelfUniqueAddress) + { + _log.Info("Downed [{0}] after [{1} ms]", _cluster.SelfAddress, TimeSpan.FromTicks(MonotonicClock.GetTicks() - _startTime).TotalMilliseconds); + } + }); + + Receive(_ => { }); + } + + protected override void PreStart() + { + _cluster.Subscribe(Self, ClusterEvent.SubscriptionInitialStateMode.InitialStateAsSnapshot, typeof(ClusterEvent.MemberDowned)); + } + } + + public class StressSpec : MultiNodeClusterSpec + { + public StressSpecConfig.Settings Settings { get; } + public TestProbe IdentifyProbe; + + protected override TimeSpan ShutdownTimeout => Dilated(TimeSpan.FromSeconds(30)); + + public int Step = 0; + public int NbrUsedRoles = 0; + + public override void MuteLog(ActorSystem sys = null) + { + sys ??= Sys; + base.MuteLog(sys); + Sys.EventStream.Publish(new Mute(new ErrorFilter(typeof(ApplicationException), new ContainsString("Simulated exception")))); + MuteDeadLetters(sys, typeof(AggregatedClusterResult), typeof(StatsResult), typeof(PhiResult), typeof(RetryTick)); + } + + public StressSpec() : this(new StressSpecConfig()){ } + + protected StressSpec(StressSpecConfig config) : base(config, typeof(StressSpec)) + { + Settings = new StressSpecConfig.Settings(Sys.Settings.Config, config.TotalNumberOfNodes); + ClusterResultHistory = new Lazy(() => + { + if (Settings.Infolog) + return Sys.ActorOf(Props.Create(() => new ClusterResultHistory()), "resultHistory"); + return Sys.DeadLetters; + }); + + PhiObserver = new Lazy(() => + { + return Sys.ActorOf(Props.Create(() => new PhiObserver()), "phiObserver"); + }); + + StatsObserver = new Lazy(() => + { + return Sys.ActorOf(Props.Create(() => new StatsObserver()), "statsObserver"); + }); + } + + protected override void AtStartup() + { + IdentifyProbe = CreateTestProbe(); + base.AtStartup(); + } + + public string ClrInfo() + { + var sb = new StringBuilder(); + sb.Append("Operating System: ") + .Append(Environment.OSVersion.Platform) + .Append(", ") + .Append(RuntimeInformation.ProcessArchitecture.ToString()) + .Append(", ") + .Append(Environment.OSVersion.VersionString) + .AppendLine(); + + sb.Append("CLR: ") + .Append(RuntimeInformation.FrameworkDescription) + .AppendLine(); + + sb.Append("Processors: ").Append(Environment.ProcessorCount) + .AppendLine() + .Append("Load average: ").Append("can't be easily measured on .NET Core") // TODO: fix + .AppendLine() + .Append("Thread count: ") + .Append(Process.GetCurrentProcess().Threads.Count) + .AppendLine(); + + sb.Append("Memory: ") + .Append(" (") + .Append(Process.GetCurrentProcess().WorkingSet64 / 1024 / 1024) + .Append(" - ") + .Append(Process.GetCurrentProcess().PeakWorkingSet64 / 1024 / 1024) + .Append(") MB [working set / peak working set]"); + + sb.AppendLine("Args: ").Append(string.Join(Environment.NewLine, Environment.GetCommandLineArgs())) + .AppendLine(); + + return sb.ToString(); + } + + public ImmutableList SeedNodes => Roles.Take(Settings.NumberOfSeedNodes).ToImmutableList(); + + internal GossipStats LatestGossipStats => Cluster.ReadView.LatestStats.GossipStats; + + public Lazy ClusterResultHistory { get; } + + public Lazy PhiObserver { get; } + + public Lazy StatsObserver { get; } + + public Option ClusterResultAggregator() + { + Sys.ActorSelection(new RootActorPath(GetAddress(Roles.First())) / "user" / ("result" + Step)) + .Tell(new Identify(Step), IdentifyProbe.Ref); + return new Option(IdentifyProbe.ExpectMsg().Subject); + } + + public void CreateResultAggregator(string title, int expectedResults, bool includeInHistory) + { + RunOn(() => + { + var aggregator = Sys.ActorOf( + Props.Create(() => new ClusterResultAggregator(title, expectedResults, Settings)) + .WithDeploy(Deploy.Local), "result" + Step); + + if (includeInHistory && Settings.Infolog) + { + aggregator.Tell(new ReportTo(new Option(ClusterResultHistory.Value))); + } + else + { + aggregator.Tell(new ReportTo(Option.None)); + } + }, + Roles.First()); + EnterBarrier("result-aggregator-created-" + Step); + + RunOn(() => + { + var resultAggregator = ClusterResultAggregator(); + PhiObserver.Value.Tell(new ReportTo(resultAggregator)); + StatsObserver.Value.Tell(Reset.Instance); + StatsObserver.Value.Tell(new ReportTo(resultAggregator)); + }, Roles.Take(NbrUsedRoles).ToArray()); + + } + + public void AwaitClusterResult() + { + RunOn(() => + { + ClusterResultAggregator().OnSuccess(r => + { + Watch(r); + ExpectMsg(t => t.ActorRef.Path == r.Path); + }); + }, Roles.First()); + EnterBarrier("cluster-result-done-" + Step); + } + + public void JoinOneByOne(int numberOfNodes) + { + foreach (var i in Enumerable.Range(0, numberOfNodes)) + { + JoinOne(); + NbrUsedRoles += 1; + Step += 1; + } + } + + public TimeSpan ConvergenceWithin(TimeSpan baseDuration, int nodes) + { + return TimeSpan.FromMilliseconds(baseDuration.TotalMilliseconds * Settings.ConvergenceWithinFactor * nodes); + } + + public void JoinOne() + { + Within(TimeSpan.FromSeconds(5) + ConvergenceWithin(TimeSpan.FromSeconds(2), NbrUsedRoles + 1), () => + { + var currentRoles = Roles.Take(NbrUsedRoles + 1).ToArray(); + var title = $"join one to {NbrUsedRoles} nodes cluster"; + CreateResultAggregator(title, expectedResults: currentRoles.Length, includeInHistory: true); + RunOn(() => + { + ReportResult(() => + { + RunOn(() => + { + Cluster.Join(GetAddress(Roles.First())); + }, currentRoles.Last()); + AwaitMembersUp(currentRoles.Length, timeout: RemainingOrDefault); + return true; + }); + }, currentRoles); + AwaitClusterResult(); + EnterBarrier("join-one-" + Step); + }); + } + + public void JoinSeveral(int numberOfNodes, bool toSeedNodes) + { + string FormatSeedJoin() + { + return toSeedNodes ? "seed nodes" : "one node"; + } + + Within(TimeSpan.FromSeconds(10) + ConvergenceWithin(TimeSpan.FromSeconds(3), NbrUsedRoles + numberOfNodes), + () => + { + var currentRoles = Roles.Take(NbrUsedRoles + numberOfNodes).ToArray(); + var joiningRoles = currentRoles.Skip(NbrUsedRoles).ToArray(); + var title = $"join {numberOfNodes} to {FormatSeedJoin()}, in {NbrUsedRoles} nodes cluster"; + CreateResultAggregator(title, expectedResults: currentRoles.Length, true); + RunOn(() => + { + ReportResult(() => + { + RunOn(() => + { + if (toSeedNodes) + { + Cluster.JoinSeedNodes(SeedNodes.Select(x => GetAddress(x))); + } + else + { + Cluster.Join(GetAddress(Roles.First())); + } + }, joiningRoles); + AwaitMembersUp(currentRoles.Length, timeout: RemainingOrDefault); + return true; + }); + }, currentRoles); + AwaitClusterResult(); + EnterBarrier("join-several-" + Step); + }); + } + + public void RemoveOneByOne(int numberOfNodes, bool shutdown) + { + foreach (var i in Enumerable.Range(0, numberOfNodes)) + { + RemoveOne(shutdown); + NbrUsedRoles -= 1; + Step += 1; + } + } + + public void RemoveOne(bool shutdown) + { + string FormatNodeLeave() + { + return shutdown ? "shutdown" : "remove"; + } + + Within(TimeSpan.FromSeconds(25) + ConvergenceWithin(TimeSpan.FromSeconds(3), NbrUsedRoles - 1), () + => + { + var currentRoles = Roles.Take(NbrUsedRoles - 1).ToArray(); + var title = $"{FormatNodeLeave()} one from {NbrUsedRoles} nodes cluster"; + CreateResultAggregator(title, expectedResults:currentRoles.Length, true); + + var removeRole = Roles[NbrUsedRoles - 1]; + var removeAddress = GetAddress(removeRole); + Console.WriteLine($"Preparing to {FormatNodeLeave()}[{removeAddress}] role [{removeRole.Name}] out of [{Roles.Count}]"); + RunOn(() => + { + var watchee = Sys.ActorOf(Props.Create(() => new Watchee()), "watchee"); + Console.WriteLine("Created watchee [{0}]", watchee); + }, removeRole); + + EnterBarrier("watchee-created-" + Step); + + RunOn(() => + { + AwaitAssert(() => + { + Sys.ActorSelection(new RootActorPath(removeAddress) / "user" / "watchee").Tell(new Identify("watchee"), IdentifyProbe.Ref); + var watchee = IdentifyProbe.ExpectMsg(TimeSpan.FromSeconds(1)).Subject; + Watch(watchee); + }, interval:TimeSpan.FromSeconds(1.25d)); + + }, Roles.First()); + EnterBarrier("watchee-established-" + Step); + + RunOn(() => + { + if (!shutdown) + Cluster.Leave(GetAddress(Myself)); + }, removeRole); + + RunOn(() => + { + ReportResult(() => + { + RunOn(() => + { + if (shutdown) + { + if (Settings.Infolog) + { + Log.Info("Shutting down [{0}]", removeAddress); + } + + TestConductor.Exit(removeRole, 0).Wait(); + } + }, Roles.First()); + + AwaitMembersUp(currentRoles.Length, timeout: RemainingOrDefault); + AwaitAllReachable(); + return true; + }); + }, currentRoles); + + RunOn(() => + { + var expectedPath = new RootActorPath(removeAddress) / "user" / "watchee"; + ExpectMsg(t => t.ActorRef.Path == expectedPath); + }, Roles.First()); + + EnterBarrier("watch-verified-" + Step); + + AwaitClusterResult(); + EnterBarrier("remove-one-" + Step); + }); + } + + public void RemoveSeveral(int numberOfNodes, bool shutdown) + { + string FormatNodeLeave() + { + return shutdown ? "shutdown" : "remove"; + } + + Within(TimeSpan.FromSeconds(25) + ConvergenceWithin(TimeSpan.FromSeconds(5), NbrUsedRoles - numberOfNodes), + () => + { + var currentRoles = Roles.Take(NbrUsedRoles - numberOfNodes).ToArray(); + var removeRoles = Roles.Skip(currentRoles.Length).Take(numberOfNodes).ToArray(); + var title = $"{FormatNodeLeave()} {numberOfNodes} in {NbrUsedRoles} nodes cluster"; + CreateResultAggregator(title, expectedResults: currentRoles.Length, includeInHistory: true); + + RunOn(() => + { + if (!shutdown) + { + Cluster.Leave(GetAddress(Myself)); + } + }, removeRoles); + + RunOn(() => + { + ReportResult(() => + { + RunOn(() => + { + if (shutdown) + { + foreach (var role in removeRoles) + { + if (Settings.Infolog) + Log.Info("Shutting down [{0}]", GetAddress(role)); + TestConductor.Exit(role, 0).Wait(RemainingOrDefault); + } + } + }, Roles.First()); + AwaitMembersUp(currentRoles.Length, timeout: RemainingOrDefault); + AwaitAllReachable(); + return true; + }); + }, currentRoles); + + AwaitClusterResult(); + EnterBarrier("remove-several-" + Step); + }); + } + + public void PartitionSeveral(int numberOfNodes) + { + Within(TimeSpan.FromSeconds(25) + ConvergenceWithin(TimeSpan.FromSeconds(5), NbrUsedRoles - numberOfNodes), + () => + { + var currentRoles = Roles.Take(NbrUsedRoles - numberOfNodes).ToArray(); + var removeRoles = Roles.Skip(currentRoles.Length).Take(numberOfNodes).ToArray(); + var title = $"partition {numberOfNodes} in {NbrUsedRoles} nodes cluster"; + Console.WriteLine(title); + Console.WriteLine("[{0}] are blackholing [{1}]", string.Join(",", currentRoles.Select(x => x.ToString())), string.Join(",", removeRoles.Select(x => x.ToString()))); + CreateResultAggregator(title, expectedResults: currentRoles.Length, includeInHistory: true); + + RunOn(() => + { + foreach (var x in currentRoles) + { + foreach (var y in removeRoles) + { + TestConductor.Blackhole(x, y, ThrottleTransportAdapter.Direction.Both).Wait(); + } + } + }, Roles.First()); + EnterBarrier("partition-several-blackhole"); + + RunOn(() => + { + ReportResult(() => + { + var startTime = MonotonicClock.GetTicks(); + AwaitMembersUp(currentRoles.Length, timeout:RemainingOrDefault); + Sys.Log.Info("Removed [{0}] members after [{0} ms]", + removeRoles.Length, TimeSpan.FromTicks(MonotonicClock.GetTicks() - startTime).TotalMilliseconds); + AwaitAllReachable(); + return true; + }); + }, currentRoles); + + RunOn(() => + { + Sys.ActorOf(Props.Create()); + AwaitAssert(() => + { + Cluster.IsTerminated.Should().BeTrue(); + }); + }, removeRoles); + AwaitClusterResult(); + EnterBarrier("partition-several-" + Step); + }); + } + + public T ReportResult(Func thunk) + { + var startTime = MonotonicClock.GetTicks(); + var startStats = ClusterView.LatestStats.GossipStats; + + var returnValue = thunk(); + + ClusterResultAggregator().OnSuccess(r => + { + r.Tell(new ClusterResult(Cluster.SelfAddress, TimeSpan.FromTicks(MonotonicClock.GetTicks() - startTime), LatestGossipStats - startStats)); + }); + + return returnValue; + } + + public void ExerciseJoinRemove(string title, TimeSpan duration) + { + var activeRoles = Roles.Take(Settings.NumberOfNodesJoinRemove).ToArray(); + var loopDuration = TimeSpan.FromSeconds(10) + + ConvergenceWithin(TimeSpan.FromSeconds(4), NbrUsedRoles + activeRoles.Length); + var rounds = (int)Math.Max(1.0d, (duration - loopDuration).TotalMilliseconds / loopDuration.TotalMilliseconds); + var usedRoles = Roles.Take(NbrUsedRoles).ToArray(); + var usedAddresses = usedRoles.Select(x => GetAddress(x)).ToImmutableHashSet(); + + Option Loop(int counter, Option previousAs, + ImmutableHashSet
allPreviousAddresses) + { + if (counter > rounds) return previousAs; + + var t = title + " round " + counter; + RunOn(() => + { + PhiObserver.Value.Tell(Reset.Instance); + StatsObserver.Value.Tell(Reset.Instance); + }, usedRoles); + CreateResultAggregator(t, expectedResults:NbrUsedRoles, includeInHistory:true); + + var nextAs = Option.None; + var nextAddresses = ImmutableHashSet
.Empty; + Within(loopDuration, () => + { + var (nextAsy, nextAddr) = ReportResult(() => + { + Option nextAs; + + if (activeRoles.Contains(Myself)) + { + previousAs.OnSuccess(s => + { + Shutdown(s); + }); + + var sys = ActorSystem.Create(Sys.Name, Sys.Settings.Config); + MuteLog(sys); + Akka.Cluster.Cluster.Get(sys).JoinSeedNodes(SeedNodes.Select(x => GetAddress(x))); + nextAs = new Option(sys); + } + else + { + nextAs = previousAs; + } + + RunOn(() => + { + AwaitMembersUp(NbrUsedRoles + activeRoles.Length, + canNotBePartOfMemberRing: allPreviousAddresses, + timeout: RemainingOrDefault); + AwaitAllReachable(); + }, usedRoles); + + nextAddresses = ClusterView.Members.Select(x => x.Address).ToImmutableHashSet() + .Except(usedAddresses); + + RunOn(() => + { + nextAddresses.Count.Should().Be(Settings.NumberOfNodesJoinRemove); + }, usedRoles); + + return (nextAs, nextAddresses); + }); + + nextAs = nextAsy; + nextAddresses = nextAddr; + }); + + AwaitClusterResult(); + Step += 1; + return Loop(counter + 1, nextAs, nextAddresses); + } + + Loop(1, Option.None, ImmutableHashSet
.Empty).OnSuccess(aSys => + { + Shutdown(aSys); + }); + + Within(loopDuration, () => + { + RunOn(() => + { + AwaitMembersUp(NbrUsedRoles, timeout: RemainingOrDefault); + AwaitAllReachable(); + PhiObserver.Value.Tell(Reset.Instance); + StatsObserver.Value.Tell(Reset.Instance); + }, usedRoles); + }); + EnterBarrier("join-remove-shutdown-" + Step); + } + + public void IdleGossip(string title) + { + CreateResultAggregator(title, expectedResults: NbrUsedRoles, includeInHistory: true); + ReportResult(() => + { + ClusterView.Members.Count.Should().Be(NbrUsedRoles); + Thread.Sleep(Settings.IdleGossipDuration); + ClusterView.Members.Count.Should().Be(NbrUsedRoles); + return true; + }); + AwaitClusterResult(); + } + + public void IncrementStep() + { + Step += 1; + } + + [MultiNodeFact] + public void Cluster_under_stress() + { + MustLogSettings(); + IncrementStep(); + MustJoinSeedNodes(); + IncrementStep(); + MustJoinSeedNodesOneByOneToSmallCluster(); + IncrementStep(); + MustJoinSeveralNodesToOneNode(); + IncrementStep(); + MustJoinSeveralNodesToSeedNodes(); + IncrementStep(); + MustJoinNodesOneByOneToLargeCluster(); + IncrementStep(); + MustExerciseJoinRemoveJoinRemove(); + IncrementStep(); + MustGossipWhenIdle(); + IncrementStep(); + MustDownPartitionedNodes(); + IncrementStep(); + MustLeaveNodesOneByOneFromLargeCluster(); + IncrementStep(); + MustShutdownNodesOneByOneFromLargeCluster(); + IncrementStep(); + MustLeaveSeveralNodes(); + IncrementStep(); + MustShutdownSeveralNodes(); + IncrementStep(); + MustShutdownNodesOneByOneFromSmallCluster(); + IncrementStep(); + MustLeaveNodesOneByOneFromSmallCluster(); + IncrementStep(); + MustLogClrInfo(); + } + + public void MustLogSettings() + { + if (Settings.Infolog) + { + Log.Info("StressSpec CLR:" + Environment.NewLine + ClrInfo()); + RunOn(() => + { + Log.Info("StressSpec settings:" + Environment.NewLine + Settings); + }); + } + EnterBarrier("after-" + Step); + } + + public void MustJoinSeedNodes() + { + Within(TimeSpan.FromSeconds(30), () => + { + var otherNodesJoiningSeedNodes = Roles.Skip(Settings.NumberOfSeedNodes) + .Take(Settings.NumberOfNodesJoiningToSeedNodesInitially).ToArray(); + var size = SeedNodes.Count + otherNodesJoiningSeedNodes.Length; + + CreateResultAggregator("join seed nodes", expectedResults: size, includeInHistory: true); + + RunOn(() => + { + ReportResult(() => + { + Cluster.JoinSeedNodes(SeedNodes.Select(x => GetAddress(x))); + AwaitMembersUp(size, timeout: RemainingOrDefault); + return true; + }); + }, SeedNodes.AddRange(otherNodesJoiningSeedNodes).ToArray()); + + AwaitClusterResult(); + NbrUsedRoles += size; + EnterBarrier("after-" + Step); + }); + } + + public void MustJoinSeedNodesOneByOneToSmallCluster() + { + JoinOneByOne(Settings.NumberOfNodesJoiningOneByOneSmall); + EnterBarrier("after-" + Step); + } + + public void MustJoinSeveralNodesToOneNode() + { + JoinSeveral(Settings.NumberOfNodesJoiningToOneNode, false); + NbrUsedRoles += Settings.NumberOfNodesJoiningToOneNode; + EnterBarrier("after-" + Step); + } + + public void MustJoinSeveralNodesToSeedNodes() + { + if (Settings.NumberOfNodesJoiningToSeedNodes > 0) + { + JoinSeveral(Settings.NumberOfNodesJoiningToSeedNodes, true); + NbrUsedRoles += Settings.NumberOfNodesJoiningToSeedNodes; + } + EnterBarrier("after-" + Step); + } + + public void MustJoinNodesOneByOneToLargeCluster() + { + JoinOneByOne(Settings.NumberOfNodesJoiningOneByOneLarge); + EnterBarrier("after-" + Step); + } + + public void MustExerciseJoinRemoveJoinRemove() + { + ExerciseJoinRemove("exercise join/remove", Settings.JoinRemoveDuration); + EnterBarrier("after-" + Step); + } + + public void MustGossipWhenIdle() + { + IdleGossip("idle gossip"); + EnterBarrier("after-" + Step); + } + + public void MustDownPartitionedNodes() + { + PartitionSeveral(Settings.NumberOfNodesPartition); + NbrUsedRoles -= Settings.NumberOfNodesPartition; + EnterBarrier("after-" + Step); + } + + public void MustLeaveNodesOneByOneFromLargeCluster() + { + RemoveOneByOne(Settings.NumberOfNodesLeavingOneByOneLarge, shutdown:false); + EnterBarrier("after-" + Step); + } + + public void MustShutdownNodesOneByOneFromLargeCluster() + { + RemoveOneByOne(Settings.NumberOfNodesShutdownOneByOneLarge, shutdown: true); + EnterBarrier("after-" + Step); + } + + public void MustLeaveSeveralNodes() + { + RemoveSeveral(Settings.NumberOfNodesLeaving, shutdown: false); + NbrUsedRoles -= Settings.NumberOfNodesLeaving; + EnterBarrier("after-" + Step); + } + + public void MustShutdownSeveralNodes() + { + RemoveSeveral(Settings.NumberOfNodesShutdown, shutdown: true); + NbrUsedRoles -= Settings.NumberOfNodesShutdown; + EnterBarrier("after-" + Step); + } + + public void MustShutdownNodesOneByOneFromSmallCluster() + { + RemoveOneByOne(Settings.NumberOfNodesShutdownOneByOneSmall, true); + EnterBarrier("after-" + Step); + } + + public void MustLeaveNodesOneByOneFromSmallCluster() + { + RemoveOneByOne(Settings.NumberOfNodesLeavingOneByOneSmall, false); + EnterBarrier("after-" + Step); + } + + public void MustLogClrInfo() + { + if (Settings.Infolog) + { + Log.Info("StressSpec CLR: " + Environment.NewLine + "{0}", ClrInfo()); + } + EnterBarrier("after-" + Step); + } + } +} diff --git a/src/core/Akka.Cluster.Tests/Akka.Cluster.Tests.csproj b/src/core/Akka.Cluster.Tests/Akka.Cluster.Tests.csproj index 18a9e2568db..450b6e36d88 100644 --- a/src/core/Akka.Cluster.Tests/Akka.Cluster.Tests.csproj +++ b/src/core/Akka.Cluster.Tests/Akka.Cluster.Tests.csproj @@ -19,6 +19,7 @@ + diff --git a/src/core/Akka.Cluster.Tests/ClusterConfigSpec.cs b/src/core/Akka.Cluster.Tests/ClusterConfigSpec.cs index 7dedfbb61b7..6899f5fe93f 100644 --- a/src/core/Akka.Cluster.Tests/ClusterConfigSpec.cs +++ b/src/core/Akka.Cluster.Tests/ClusterConfigSpec.cs @@ -42,6 +42,7 @@ public void Clustering_must_be_able_to_parse_generic_cluster_config_elements() settings.LeaderActionsInterval.Should().Be(1.Seconds()); settings.UnreachableNodesReaperInterval.Should().Be(1.Seconds()); settings.AllowWeaklyUpMembers.Should().BeTrue(); + settings.WeaklyUpAfter.Should().Be(7.Seconds()); settings.PublishStatsInterval.Should().NotHaveValue(); settings.AutoDownUnreachableAfter.Should().NotHaveValue(); settings.DownRemovalMargin.Should().Be(TimeSpan.Zero); diff --git a/src/core/Akka.Cluster.Tests/ClusterDomainEventPublisherSpec.cs b/src/core/Akka.Cluster.Tests/ClusterDomainEventPublisherSpec.cs index 052002a0666..02e61c2a11e 100644 --- a/src/core/Akka.Cluster.Tests/ClusterDomainEventPublisherSpec.cs +++ b/src/core/Akka.Cluster.Tests/ClusterDomainEventPublisherSpec.cs @@ -35,15 +35,25 @@ public class ClusterDomainEventPublisherSpec : AkkaSpec static readonly Member dUp = TestMember.Create(new Address("akka.tcp", "sys", "d", 2552), MemberStatus.Up, ImmutableHashSet.Create("GRP")); static readonly Gossip g0 = new Gossip(ImmutableSortedSet.Create(aUp)).Seen(aUp.UniqueAddress); + static readonly MembershipState state0 = new MembershipState(g0, aUp.UniqueAddress); static readonly Gossip g1 = new Gossip(ImmutableSortedSet.Create(aUp, cJoining)).Seen(aUp.UniqueAddress).Seen(cJoining.UniqueAddress); + static readonly MembershipState state1 = new MembershipState(g1, aUp.UniqueAddress); static readonly Gossip g2 = new Gossip(ImmutableSortedSet.Create(aUp, bExiting, cUp)).Seen(aUp.UniqueAddress); + static readonly MembershipState state2 = new MembershipState(g2, aUp.UniqueAddress); static readonly Gossip g3 = g2.Seen(bExiting.UniqueAddress).Seen(cUp.UniqueAddress); + static readonly MembershipState state3 = new MembershipState(g3, aUp.UniqueAddress); static readonly Gossip g4 = new Gossip(ImmutableSortedSet.Create(a51Up, aUp, bExiting, cUp)).Seen(aUp.UniqueAddress); + static readonly MembershipState state4 = new MembershipState(g4, aUp.UniqueAddress); static readonly Gossip g5 = new Gossip(ImmutableSortedSet.Create(a51Up, aUp, bExiting, cUp)).Seen(aUp.UniqueAddress).Seen(bExiting.UniqueAddress).Seen(cUp.UniqueAddress); + static readonly MembershipState state5 = new MembershipState(g5, aUp.UniqueAddress); static readonly Gossip g6 = new Gossip(ImmutableSortedSet.Create(aLeaving, bExiting, cUp)).Seen(aUp.UniqueAddress); + static readonly MembershipState state6 = new MembershipState(g6, aUp.UniqueAddress); static readonly Gossip g7 = new Gossip(ImmutableSortedSet.Create(aExiting, bExiting, cUp)).Seen(aUp.UniqueAddress); + static readonly MembershipState state7 = new MembershipState(g7, aUp.UniqueAddress); static readonly Gossip g8 = new Gossip(ImmutableSortedSet.Create(aUp, bExiting, cUp, dUp), new GossipOverview(Reachability.Empty.Unreachable(aUp.UniqueAddress, dUp.UniqueAddress))).Seen(aUp.UniqueAddress); + static readonly MembershipState state8 = new MembershipState(g8, aUp.UniqueAddress); + static readonly MembershipState _emptyMembershipState = new MembershipState(Gossip.Empty, aUp.UniqueAddress); readonly TestProbe _memberSubscriber; public ClusterDomainEventPublisherSpec() : base(Config) @@ -54,7 +64,7 @@ public ClusterDomainEventPublisherSpec() : base(Config) Sys.EventStream.Subscribe(_memberSubscriber.Ref, typeof(ClusterEvent.ClusterShuttingDown)); _publisher = Sys.ActorOf(Props.Create()); - _publisher.Tell(new InternalClusterAction.PublishChanges(g0)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state0)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberUp(aUp)); _memberSubscriber.ExpectMsg(new ClusterEvent.LeaderChanged(aUp.Address)); } @@ -62,15 +72,15 @@ public ClusterDomainEventPublisherSpec() : base(Config) [Fact] public void ClusterDomainEventPublisher_must_publish_MemberJoined() { - _publisher.Tell(new InternalClusterAction.PublishChanges(g1)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state1)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberJoined(cJoining)); } [Fact] public void ClusterDomainEventPublisher_must_publish_MemberUp() { - _publisher.Tell(new InternalClusterAction.PublishChanges(g2)); - _publisher.Tell(new InternalClusterAction.PublishChanges(g3)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state2)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state3)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberExited(bExiting)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberUp(cUp)); } @@ -78,7 +88,7 @@ public void ClusterDomainEventPublisher_must_publish_MemberUp() [Fact] public void ClusterDomainEventPublisher_must_publish_leader_changed() { - _publisher.Tell(new InternalClusterAction.PublishChanges(g4)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state4)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberUp(a51Up)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberExited(bExiting)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberUp(cUp)); @@ -89,17 +99,17 @@ public void ClusterDomainEventPublisher_must_publish_leader_changed() [Fact] public void ClusterDomainEventPublisher_must_publish_leader_changed_when_old_leader_leaves_and_is_removed() { - _publisher.Tell(new InternalClusterAction.PublishChanges(g3)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state3)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberExited(bExiting)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberUp(cUp)); - _publisher.Tell(new InternalClusterAction.PublishChanges(g6)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state6)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberLeft(aLeaving)); - _publisher.Tell(new InternalClusterAction.PublishChanges(g7)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state7)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberExited(aExiting)); _memberSubscriber.ExpectMsg(new ClusterEvent.LeaderChanged(cUp.Address)); _memberSubscriber.ExpectNoMsg(500.Milliseconds()); // at the removed member a an empty gossip is the last thing - _publisher.Tell(new InternalClusterAction.PublishChanges(Gossip.Empty)); + _publisher.Tell(new InternalClusterAction.PublishChanges(_emptyMembershipState)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberRemoved(aRemoved, MemberStatus.Exiting)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberRemoved(bRemoved, MemberStatus.Exiting)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberRemoved(cRemoved, MemberStatus.Up)); @@ -109,13 +119,13 @@ public void ClusterDomainEventPublisher_must_publish_leader_changed_when_old_lea [Fact] public void ClusterDomainEventPublisher_must_not_publish_leader_changed_when_same_leader() { - _publisher.Tell(new InternalClusterAction.PublishChanges(g4)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state4)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberUp(a51Up)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberExited(bExiting)); _memberSubscriber.ExpectMsg(new ClusterEvent.MemberUp(cUp)); _memberSubscriber.ExpectMsg(new ClusterEvent.LeaderChanged(a51Up.Address)); - _publisher.Tell(new InternalClusterAction.PublishChanges(g5)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state5)); _memberSubscriber.ExpectNoMsg(500.Milliseconds()); } @@ -125,9 +135,9 @@ public void ClusterDomainEventPublisher_must_publish_role_leader_changed() var subscriber = CreateTestProbe(); _publisher.Tell(new InternalClusterAction.Subscribe(subscriber.Ref, ClusterEvent.SubscriptionInitialStateMode.InitialStateAsSnapshot, ImmutableHashSet.Create(typeof(ClusterEvent.RoleLeaderChanged)))); subscriber.ExpectMsg(); - _publisher.Tell(new InternalClusterAction.PublishChanges(new Gossip(ImmutableSortedSet.Create(cJoining, dUp)))); + _publisher.Tell(new InternalClusterAction.PublishChanges(new MembershipState(new Gossip(ImmutableSortedSet.Create(cJoining, dUp)), dUp.UniqueAddress))); subscriber.ExpectMsg(new ClusterEvent.RoleLeaderChanged("GRP", dUp.Address)); - _publisher.Tell(new InternalClusterAction.PublishChanges(new Gossip(ImmutableSortedSet.Create(cUp, dUp)))); + _publisher.Tell(new InternalClusterAction.PublishChanges(new MembershipState(new Gossip(ImmutableSortedSet.Create(cUp, dUp)), dUp.UniqueAddress))); subscriber.ExpectMsg(new ClusterEvent.RoleLeaderChanged("GRP", cUp.Address)); } @@ -145,7 +155,7 @@ public void ClusterDomainEventPublisher_must_send_CurrentClusterState_when_subsc public void ClusterDomainEventPublisher_must_send_events_corresponding_to_current_state_when_subscribe() { var subscriber = CreateTestProbe(); - _publisher.Tell(new InternalClusterAction.PublishChanges(g8)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state8)); _publisher.Tell(new InternalClusterAction.Subscribe(subscriber.Ref, ClusterEvent.SubscriptionInitialStateMode.InitialStateAsEvents, ImmutableHashSet.Create(typeof(ClusterEvent.IMemberEvent), typeof(ClusterEvent.ReachabilityEvent)))); subscriber.ReceiveN(4).Should().BeEquivalentTo( @@ -165,7 +175,7 @@ public void ClusterDomainEventPublisher_should_support_unsubscribe() _publisher.Tell(new InternalClusterAction.Subscribe(subscriber.Ref, ClusterEvent.SubscriptionInitialStateMode.InitialStateAsSnapshot, ImmutableHashSet.Create(typeof(ClusterEvent.IMemberEvent)))); subscriber.ExpectMsg(); _publisher.Tell(new InternalClusterAction.Unsubscribe(subscriber.Ref, typeof(ClusterEvent.IMemberEvent))); - _publisher.Tell(new InternalClusterAction.PublishChanges(g3)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state3)); subscriber.ExpectNoMsg(500.Milliseconds()); // but memberSubscriber is still subscriber _memberSubscriber.ExpectMsg(new ClusterEvent.MemberExited(bExiting)); @@ -178,10 +188,10 @@ public void ClusterDomainEventPublisher_must_publish_seen_changed() var subscriber = CreateTestProbe(); _publisher.Tell(new InternalClusterAction.Subscribe(subscriber.Ref, ClusterEvent.SubscriptionInitialStateMode.InitialStateAsSnapshot, ImmutableHashSet.Create(typeof(ClusterEvent.SeenChanged)))); subscriber.ExpectMsg(); - _publisher.Tell(new InternalClusterAction.PublishChanges(g2)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state2)); subscriber.ExpectMsg(); subscriber.ExpectNoMsg(500.Milliseconds()); - _publisher.Tell(new InternalClusterAction.PublishChanges(g3)); + _publisher.Tell(new InternalClusterAction.PublishChanges(state3)); subscriber.ExpectMsg(); subscriber.ExpectNoMsg(500.Milliseconds()); } diff --git a/src/core/Akka.Cluster.Tests/ClusterDomainEventSpec.cs b/src/core/Akka.Cluster.Tests/ClusterDomainEventSpec.cs index e51fcf945b0..b6aae634582 100644 --- a/src/core/Akka.Cluster.Tests/ClusterDomainEventSpec.cs +++ b/src/core/Akka.Cluster.Tests/ClusterDomainEventSpec.cs @@ -45,12 +45,17 @@ private static (Gossip, ImmutableHashSet) Converge(Gossip gossip) (t, m) => (t.Item1.Seen(m.UniqueAddress), t.Item2.Add(m.UniqueAddress))); } + private MembershipState State(Gossip g) + { + return new MembershipState(g, selfDummyAddress); + } + [Fact] public void DomainEvents_must_be_empty_for_the_same_gossip() { - var g1 = new Gossip(ImmutableSortedSet.Create(aUp)); + var g1 =new Gossip(ImmutableSortedSet.Create(aUp)); - ClusterEvent.DiffUnreachable(g1, g1, selfDummyAddress) + ClusterEvent.DiffUnreachable(State(g1), State(g1)) .Should() .BeEquivalentTo(ImmutableList.Create()); } @@ -65,15 +70,15 @@ public void DomainEvents_must_be_produced_for_new_members() var g2 = t2.Item1; var s2 = t2.Item2; - ClusterEvent.DiffMemberEvents(g1, g2) + ClusterEvent.DiffMemberEvents(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.MemberUp(bUp), new ClusterEvent.MemberJoined(eJoining))); - ClusterEvent.DiffUnreachable(g1, g2, selfDummyAddress) + ClusterEvent.DiffUnreachable(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create()); - ClusterEvent.DiffSeen(g1, g2, selfDummyAddress) + ClusterEvent.DiffSeen(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.SeenChanged(true, s2.Select(s => s.Address).ToImmutableHashSet()))); } @@ -88,15 +93,15 @@ public void DomainEvents_must_be_produced_for_changed_status_of_members() var g2 = t2.Item1; var s2 = t2.Item2; - ClusterEvent.DiffMemberEvents(g1, g2) + ClusterEvent.DiffMemberEvents(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.MemberUp(aUp), new ClusterEvent.MemberLeft(cLeaving), new ClusterEvent.MemberJoined(eJoining))); - ClusterEvent.DiffUnreachable(g1, g2, selfDummyAddress) + ClusterEvent.DiffUnreachable(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create()); - ClusterEvent.DiffSeen(g1, g2, selfDummyAddress) + ClusterEvent.DiffSeen(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.SeenChanged(true, s2.Select(s => s.Address).ToImmutableHashSet()))); } @@ -112,16 +117,16 @@ public void DomainEvents_must_be_produced_for_members_in_unreachable() Unreachable(aUp.UniqueAddress, bDown.UniqueAddress); var g2 = new Gossip(ImmutableSortedSet.Create(aUp, cUp, bDown, eDown), new GossipOverview(reachability2)); - ClusterEvent.DiffUnreachable(g1, g2, selfDummyAddress) + ClusterEvent.DiffUnreachable(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.UnreachableMember(bDown))); // never include self member in unreachable - ClusterEvent.DiffUnreachable(g1, g2, bDown.UniqueAddress) + ClusterEvent.DiffUnreachable(new MembershipState(g1, bDown.UniqueAddress), new MembershipState(g2, bDown.UniqueAddress)) .Should() .BeEquivalentTo(ImmutableList.Create()); - ClusterEvent.DiffSeen(g1, g2, selfDummyAddress) + ClusterEvent.DiffSeen(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create()); } @@ -140,19 +145,19 @@ public void DomainEvents_must_be_produced_for_members_becoming_reachable_after_u Reachable(aUp.UniqueAddress, bUp.UniqueAddress); var g2 = new Gossip(ImmutableSortedSet.Create(aUp, cUp, bUp, eUp), new GossipOverview(reachability2)); - ClusterEvent.DiffUnreachable(g1, g2, selfDummyAddress) + ClusterEvent.DiffUnreachable(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.UnreachableMember(cUp))); // never include self member in unreachable - ClusterEvent.DiffUnreachable(g1, g2, cUp.UniqueAddress) + ClusterEvent.DiffUnreachable(new MembershipState(g1, cUp.UniqueAddress), new MembershipState(g2, cUp.UniqueAddress)) .Should() .BeEquivalentTo(ImmutableList.Create()); - ClusterEvent.DiffReachable(g1, g2, selfDummyAddress) + ClusterEvent.DiffReachable(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.ReachableMember(bUp))); // never include self member in reachable - ClusterEvent.DiffReachable(g1, g2, bUp.UniqueAddress) + ClusterEvent.DiffReachable(new MembershipState(g1, bUp.UniqueAddress), new MembershipState(g2, bUp.UniqueAddress)) .Should() .BeEquivalentTo(ImmutableList.Create()); } @@ -166,11 +171,11 @@ public void DomainEvents_must_be_produced_for_downed_members() var g1 = t1.Item1; var g2 = t2.Item1; - ClusterEvent.DiffMemberEvents(g1, g2) + ClusterEvent.DiffMemberEvents(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.MemberDowned(eDown))); - ClusterEvent.DiffUnreachable(g1, g2, selfDummyAddress) + ClusterEvent.DiffUnreachable(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create()); } @@ -185,15 +190,15 @@ public void DomainEvents_must_be_produced_for_removed_members() var g2 = t2.Item1; var s2 = t2.Item2; - ClusterEvent.DiffMemberEvents(g1, g2) + ClusterEvent.DiffMemberEvents(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.MemberRemoved(dRemoved, MemberStatus.Exiting))); - ClusterEvent.DiffUnreachable(g1, g2, selfDummyAddress) + ClusterEvent.DiffUnreachable(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create()); - ClusterEvent.DiffSeen(g1, g2, selfDummyAddress) + ClusterEvent.DiffSeen(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.SeenChanged(true, s2.Select(s => s.Address).ToImmutableHashSet()))); } @@ -209,22 +214,22 @@ public void DomainEvents_must_be_produced_for_convergence_changes() .Seen(aUp.UniqueAddress) .Seen(bUp.UniqueAddress); - ClusterEvent.DiffMemberEvents(g1, g2) + ClusterEvent.DiffMemberEvents(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create()); - ClusterEvent.DiffUnreachable(g1, g2, selfDummyAddress) + ClusterEvent.DiffUnreachable(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create()); - ClusterEvent.DiffSeen(g1, g2, selfDummyAddress) + ClusterEvent.DiffSeen(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.SeenChanged(true, ImmutableHashSet.Create(aUp.Address, bUp.Address)))); - ClusterEvent.DiffMemberEvents(g2, g1) + ClusterEvent.DiffMemberEvents(State(g2), State(g1)) .Should() .BeEquivalentTo(ImmutableList.Create()); - ClusterEvent.DiffUnreachable(g1, g1, selfDummyAddress) + ClusterEvent.DiffUnreachable(State(g1), State(g1)) .Should() .BeEquivalentTo(ImmutableList.Create()); - ClusterEvent.DiffSeen(g2, g1, selfDummyAddress) + ClusterEvent.DiffSeen(State(g2), State(g1)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.SeenChanged(true, ImmutableHashSet.Create(aUp.Address, bUp.Address, eJoining.Address)))); } @@ -239,16 +244,16 @@ public void DomainEvents_must_be_produced_for_leader_changes() var g2 = t2.Item1; var s2 = t2.Item2; - ClusterEvent.DiffMemberEvents(g1, g2) + ClusterEvent.DiffMemberEvents(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.MemberRemoved(aRemoved, MemberStatus.Up))); - ClusterEvent.DiffUnreachable(g1, g2, selfDummyAddress) + ClusterEvent.DiffUnreachable(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create()); - ClusterEvent.DiffSeen(g1, g2, selfDummyAddress) + ClusterEvent.DiffSeen(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.SeenChanged(true, s2.Select(a => a.Address).ToImmutableHashSet()))); - ClusterEvent.DiffLeader(g1, g2, selfDummyAddress) + ClusterEvent.DiffLeader(State(g1), State(g2)) .Should() .BeEquivalentTo(ImmutableList.Create(new ClusterEvent.LeaderChanged(bUp.Address))); } @@ -267,13 +272,13 @@ public void DomainEvents_must_be_produced_for_role_leader_changes() new ClusterEvent.RoleLeaderChanged("DD", dLeaving.Address), new ClusterEvent.RoleLeaderChanged("DE", dLeaving.Address), new ClusterEvent.RoleLeaderChanged("EE", eUp.Address)); - ClusterEvent.DiffRolesLeader(g0, g1, selfDummyAddress).Should().BeEquivalentTo(expected); + ClusterEvent.DiffRolesLeader(State(g0), State(g1)).Should().BeEquivalentTo(expected); var expected2 = ImmutableHashSet.Create( new ClusterEvent.RoleLeaderChanged("AA", null), new ClusterEvent.RoleLeaderChanged("AB", bUp.Address), new ClusterEvent.RoleLeaderChanged("DE", eJoining.Address)); - ClusterEvent.DiffRolesLeader(g1, g2, selfDummyAddress).Should().BeEquivalentTo(expected2); + ClusterEvent.DiffRolesLeader(State(g1), State(g2)).Should().BeEquivalentTo(expected2); } } } diff --git a/src/core/Akka.Cluster.Tests/ClusterGenerators.cs b/src/core/Akka.Cluster.Tests/ClusterGenerators.cs index 1eb1c985f7f..8b4a4774323 100644 --- a/src/core/Akka.Cluster.Tests/ClusterGenerators.cs +++ b/src/core/Akka.Cluster.Tests/ClusterGenerators.cs @@ -5,7 +5,6 @@ // //----------------------------------------------------------------------- -#if FSCHECK using System; using System.Linq; using System.Net; @@ -54,4 +53,3 @@ public static Arbitrary MemberStatusGenerator() } } } -#endif diff --git a/src/core/Akka.Cluster.Tests/ClusterHeartBeatSenderStateSpec.cs b/src/core/Akka.Cluster.Tests/ClusterHeartBeatSenderStateSpec.cs index ad89f94086f..f5303bf65c3 100644 --- a/src/core/Akka.Cluster.Tests/ClusterHeartBeatSenderStateSpec.cs +++ b/src/core/Akka.Cluster.Tests/ClusterHeartBeatSenderStateSpec.cs @@ -97,14 +97,14 @@ private FailureDetectorStub Fd(ClusterHeartbeatSenderState state, UniqueAddress public void ClusterHeartbeatSenderState_must_return_empty_active_set_when_no_nodes() { _emptyState - .ActiveReceivers.IsEmpty.Should().BeTrue(); + .ActiveReceivers.Count.Should().Be(0); } [Fact] public void ClusterHeartbeatSenderState_must_init_with_empty() { _emptyState.Init(ImmutableHashSet.Empty, ImmutableHashSet.Empty) - .ActiveReceivers.IsEmpty.Should().BeTrue(); + .ActiveReceivers.Count.Should().Be(0); } [Fact] diff --git a/src/core/Akka.Cluster.Tests/ClusterHeartbeatReceiverSpec.cs b/src/core/Akka.Cluster.Tests/ClusterHeartbeatReceiverSpec.cs new file mode 100644 index 00000000000..90dd2c6d90c --- /dev/null +++ b/src/core/Akka.Cluster.Tests/ClusterHeartbeatReceiverSpec.cs @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using Akka.Actor; +using Akka.Configuration; +using Akka.TestKit; +using Xunit; +using Xunit.Abstractions; +using static Akka.Cluster.ClusterHeartbeatSender; + +namespace Akka.Cluster.Tests +{ + public class ClusterHeartbeatReceiverSpec : AkkaSpec + { + public static Config Config = @"akka.actor.provider = cluster"; + + public ClusterHeartbeatReceiverSpec(ITestOutputHelper output) + : base(Config, output) + { + + } + + [Fact] + public void ClusterHeartbeatReceiver_should_respond_to_heartbeats_with_same_SeqNo_and_SendTime() + { + var heartbeater = Sys.ActorOf(ClusterHeartbeatReceiver.Props(() => Cluster.Get(Sys))); + heartbeater.Tell(new Heartbeat(Cluster.Get(Sys).SelfAddress, 1, 2)); + ExpectMsg(new HeartbeatRsp(Cluster.Get(Sys).SelfUniqueAddress, 1, 2)); + } + } +} diff --git a/src/core/Akka.Cluster.Tests/ClusterHeartbeatSenderSpec.cs b/src/core/Akka.Cluster.Tests/ClusterHeartbeatSenderSpec.cs new file mode 100644 index 00000000000..fdea539c91b --- /dev/null +++ b/src/core/Akka.Cluster.Tests/ClusterHeartbeatSenderSpec.cs @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System.Collections.Immutable; +using Akka.Actor; +using Akka.Configuration; +using Akka.TestKit; +using Akka.Util; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; +using static Akka.Cluster.ClusterHeartbeatSender; + +namespace Akka.Cluster.Tests +{ + public class ClusterHeartbeatSenderSpec : AkkaSpec + { + class TestClusterHeartbeatSender : ClusterHeartbeatSender + { + private readonly TestProbe _probe; + + public TestClusterHeartbeatSender(TestProbe probe) + { + _probe = probe; + } + + protected override void PreStart() + { + // don't register for cluster events + } + + protected override ActorSelection HeartbeatReceiver(Address address) + { + return Context.ActorSelection(_probe.Ref.Path); + } + } + + public static readonly Config Config = @" + akka.loglevel = DEBUG + akka.actor.provider = cluster + akka.cluster.failure-detector.heartbeat-interval = 0.2s + "; + + public ClusterHeartbeatSenderSpec(ITestOutputHelper output) + : base(Config, output){ } + + [Fact] + public void ClusterHeartBeatSender_must_increment_heartbeat_SeqNo() + { + var probe = CreateTestProbe(); + var underTest = Sys.ActorOf(Props.Create(() => new TestClusterHeartbeatSender(probe))); + + underTest.Tell(new ClusterEvent.CurrentClusterState()); + underTest.Tell(new ClusterEvent.MemberUp(new Member( + new UniqueAddress(new Address("akka", Sys.Name), 1), 1, + MemberStatus.Up, ImmutableHashSet.Empty, AppVersion.Zero))); + + probe.ExpectMsg().SequenceNr.Should().Be(1L); + probe.ExpectMsg().SequenceNr.Should().Be(2L); + } + } +} diff --git a/src/core/Akka.Cluster.Tests/ClusterLogSpec.cs b/src/core/Akka.Cluster.Tests/ClusterLogSpec.cs index 5b3926886dd..b482b13959c 100644 --- a/src/core/Akka.Cluster.Tests/ClusterLogSpec.cs +++ b/src/core/Akka.Cluster.Tests/ClusterLogSpec.cs @@ -5,6 +5,7 @@ // //----------------------------------------------------------------------- +using System; using System.Linq; using Akka.Actor; using Akka.Configuration; @@ -46,19 +47,25 @@ protected ClusterLogSpec(ITestOutputHelper output, Config config = null) protected void AwaitUp() { - AwaitCondition(() => ClusterView.IsSingletonCluster); - ClusterView.Self.Address.ShouldBe(_selfAddress); - ClusterView.Members.Select(m => m.Address).ShouldBe(new Address[] { _selfAddress }); - AwaitAssert(() => ClusterView.Status.ShouldBe(MemberStatus.Up)); + Within(TimeSpan.FromSeconds(10), () => + { + AwaitCondition(() => ClusterView.IsSingletonCluster); + ClusterView.Self.Address.ShouldBe(_selfAddress); + ClusterView.Members.Select(m => m.Address).ShouldBe(new Address[] { _selfAddress }); + AwaitAssert(() => ClusterView.Status.ShouldBe(MemberStatus.Up)); + }); } /// /// The expected log info pattern to intercept after a . /// protected void Join(string expected) { - EventFilter - .Info(contains: expected) - .ExpectOne(() => _cluster.Join(_selfAddress)); + Within(TimeSpan.FromSeconds(10), () => + { + EventFilter + .Info(contains: expected) + .ExpectOne(() => _cluster.Join(_selfAddress)); + }); } /// @@ -67,9 +74,12 @@ protected void Join(string expected) /// protected void Down(string expected) { - EventFilter + Within(TimeSpan.FromSeconds(10), () => + { + EventFilter .Info(contains: expected) .ExpectOne(() => _cluster.Down(_selfAddress)); + }); } } diff --git a/src/core/Akka.Cluster.Tests/GossipSpec.cs b/src/core/Akka.Cluster.Tests/GossipSpec.cs index c433becacac..d319aef2539 100644 --- a/src/core/Akka.Cluster.Tests/GossipSpec.cs +++ b/src/core/Akka.Cluster.Tests/GossipSpec.cs @@ -31,31 +31,37 @@ public class GossipSpec static readonly Member e2 = TestMember.Create(e1.Address, MemberStatus.Up); static readonly Member e3 = TestMember.Create(e1.Address, MemberStatus.Down); + private MembershipState State(Gossip g, Member selfMember = null) + { + selfMember = selfMember ?? a1; + return new MembershipState(g, selfMember.UniqueAddress); + } + [Fact] public void A_gossip_must_reach_convergence_when_its_empty() { - Gossip.Empty.Convergence(a1.UniqueAddress, new HashSet()).Should().BeTrue(); + State(Gossip.Empty).Convergence(ImmutableHashSet.Empty).Should().BeTrue(); } [Fact] public void A_gossip_must_reach_convergence_for_one_node() { var g1 = new Gossip(ImmutableSortedSet.Create(a1)).Seen(a1.UniqueAddress); - g1.Convergence(a1.UniqueAddress, new HashSet()).Should().BeTrue(); + State(g1).Convergence(ImmutableHashSet.Empty).Should().BeTrue(); } [Fact] public void A_gossip_must_not_reach_convergence_until_all_have_seen_version() { var g1 = new Gossip(ImmutableSortedSet.Create(a1, b1)).Seen(a1.UniqueAddress); - g1.Convergence(a1.UniqueAddress, new HashSet()).Should().BeFalse(); + State(g1).Convergence(ImmutableHashSet.Empty).Should().BeFalse(); } [Fact] public void A_gossip_must_reach_convergence_for_two_nodes() { var g1 = new Gossip(ImmutableSortedSet.Create(a1, b1)).Seen(a1.UniqueAddress).Seen(b1.UniqueAddress); - g1.Convergence(a1.UniqueAddress, new HashSet()).Should().BeTrue(); + State(g1).Convergence(ImmutableHashSet.Empty).Should().BeTrue(); } [Fact] @@ -63,7 +69,7 @@ public void A_gossip_must_reach_convergence_skipping_joining() { // e1 is joining var g1 = new Gossip(ImmutableSortedSet.Create(a1, b1, e1)).Seen(a1.UniqueAddress).Seen(b1.UniqueAddress); - g1.Convergence(a1.UniqueAddress, new HashSet()).Should().BeTrue(); + State(g1).Convergence(ImmutableHashSet.Empty).Should().BeTrue(); } [Fact] @@ -71,7 +77,7 @@ public void A_gossip_must_reach_convergence_skipping_down() { // e3 is down var g1 = new Gossip(ImmutableSortedSet.Create(a1, b1, e3)).Seen(a1.UniqueAddress).Seen(b1.UniqueAddress); - g1.Convergence(a1.UniqueAddress, new HashSet()).Should().BeTrue(); + State(g1).Convergence(ImmutableHashSet.Empty).Should().BeTrue(); } [Fact] @@ -79,7 +85,7 @@ public void A_gossip_must_reach_convergence_skipping_Leaving_with_ExitingConfirm { // c1 is leaving var g1 = new Gossip(ImmutableSortedSet.Create(a1, b1, c1)).Seen(a1.UniqueAddress).Seen(b1.UniqueAddress); - g1.Convergence(a1.UniqueAddress, new HashSet() { c1.UniqueAddress }).Should().BeTrue(); + State(g1).Convergence(ImmutableHashSet.Empty.Add(c1.UniqueAddress)).Should().BeTrue(); } [Fact] @@ -88,7 +94,7 @@ public void A_gossip_must_reach_convergence_skipping_Unreachable_Leaving_with_Ex // c1 is leaving var r1 = Reachability.Empty.Unreachable(b1.UniqueAddress, c1.UniqueAddress); var g1 = new Gossip(ImmutableSortedSet.Create(a1, b1, c1), new GossipOverview(r1)).Seen(a1.UniqueAddress).Seen(b1.UniqueAddress); - g1.Convergence(a1.UniqueAddress, new HashSet() { c1.UniqueAddress }).Should().BeTrue(); + State(g1).Convergence(ImmutableHashSet.Empty.Add(c1.UniqueAddress)).Should().BeTrue(); } [Fact] @@ -97,9 +103,9 @@ public void A_gossip_must_not_reach_convergence_when_unreachable() var r1 = Reachability.Empty.Unreachable(b1.UniqueAddress, a1.UniqueAddress); var g1 = new Gossip(ImmutableSortedSet.Create(a1, b1), new GossipOverview(r1)) .Seen(a1.UniqueAddress).Seen(b1.UniqueAddress); - g1.Convergence(b1.UniqueAddress, new HashSet()).Should().BeFalse(); + State(g1, b1).Convergence(ImmutableHashSet.Empty).Should().BeFalse(); // but from a1's point of view (it knows that itself is not unreachable) - g1.Convergence(a1.UniqueAddress, new HashSet()).Should().BeTrue(); + State(g1).Convergence(ImmutableHashSet.Empty).Should().BeTrue(); } [Fact] @@ -109,7 +115,7 @@ public void A_gossip_must_reach_convergence_when_downed_node_has_observed_unreac var r1 = Reachability.Empty.Unreachable(e3.UniqueAddress, a1.UniqueAddress); var g1 = new Gossip(ImmutableSortedSet.Create(a1, b1, e3), new GossipOverview(r1)) .Seen(a1.UniqueAddress).Seen(b1.UniqueAddress).Seen(e3.UniqueAddress); - g1.Convergence(b1.UniqueAddress, new HashSet()).Should().BeTrue(); + State(g1, b1).Convergence(ImmutableHashSet.Empty).Should().BeTrue(); } [Fact] @@ -171,15 +177,15 @@ public void A_gossip_must_merge_members_by_removing_removed_members() [Fact] public void A_gossip_must_have_leader_as_first_member_based_on_ordering_except_exiting_status() { - new Gossip(ImmutableSortedSet.Create(c2, e2)).Leader(c2.UniqueAddress).Should().Be(c2.UniqueAddress); - new Gossip(ImmutableSortedSet.Create(c3, e2)).Leader(c3.UniqueAddress).Should().Be(e2.UniqueAddress); - new Gossip(ImmutableSortedSet.Create(c3)).Leader(c3.UniqueAddress).Should().Be(c3.UniqueAddress); + State(new Gossip(ImmutableSortedSet.Create(c2, e2))).Leader.Should().Be(c2.UniqueAddress); + State(new Gossip(ImmutableSortedSet.Create(c3, e2))).Leader.Should().Be(e2.UniqueAddress); + State(new Gossip(ImmutableSortedSet.Create(c3))).Leader.Should().Be(c3.UniqueAddress); } [Fact] public void A_gossip_must_not_have_Down_member_as_leader() { - new Gossip(ImmutableSortedSet.Create(e3)).Leader(e3.UniqueAddress).Should().BeNull(); + State(new Gossip(ImmutableSortedSet.Create(e3))).Leader.Should().BeNull(); } [Fact] diff --git a/src/core/Akka.Cluster.Tests/MemberOrderingModelBasedTests.cs b/src/core/Akka.Cluster.Tests/MemberOrderingModelBasedTests.cs index 3b91a33fb21..ea56ea2814a 100644 --- a/src/core/Akka.Cluster.Tests/MemberOrderingModelBasedTests.cs +++ b/src/core/Akka.Cluster.Tests/MemberOrderingModelBasedTests.cs @@ -5,15 +5,13 @@ // //----------------------------------------------------------------------- -#if FSCHECK using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Akka.Actor; using Akka.Tests.Shared.Internals.Helpers; +using Akka.Util; using Akka.Util.Internal; using FsCheck; using FsCheck.Experimental; @@ -61,7 +59,7 @@ public Property DistinctMemberAddressesMustCompareDifferently(Address[] addresse } } - public class MembershipMachine : Machine + public class MembershipMachine : Machine { public MembershipMachine() { @@ -69,19 +67,19 @@ public MembershipMachine() Arb.Register(); } - public override Gen> Next(MembershipModel model) + public override Gen> Next(MembershipModel model) { if (model.AllMembers.Count == 0) return AddNewMember.Generator(); return Gen.OneOf(ChangeMemberStatus.Generator(model.AllMembers.Keys), AddNewMember.Generator()); } - public override Arbitrary> Setup => Arb.From(Arb.Generate() - .Select(x => (Setup)x)); // compiler ceremony :( + public override Arbitrary> Setup => Arb.From(Arb.Generate() + .Select(x => (Setup)x)); // compiler ceremony :( #region Setup - public class MembershipSetup : Setup + public class MembershipSetup : Setup { private readonly Member[] _members; @@ -90,13 +88,13 @@ public MembershipSetup(UniqueAddress[] addresses) // filter out any duplicates _members = addresses.Distinct() - .Select(x => new Member(x, int.MaxValue, MemberStatus.Up, ImmutableHashSet.Empty)) + .Select(x => new Member(x, int.MaxValue, MemberStatus.Up, ImmutableHashSet.Empty, AppVersion.Zero)) .ToArray(); } - public override MembershipState Actual() + public override MemberOrderingState Actual() { - return new MembershipState() { Members = ImmutableSortedSet.Empty.Union(_members) }; + return new MemberOrderingState() { Members = ImmutableSortedSet.Empty.Union(_members) }; } public override MembershipModel Model() @@ -114,12 +112,12 @@ public override string ToString() #region Operations - public class ChangeMemberStatus : Operation + public class ChangeMemberStatus : Operation { - public static Gen> Generator(IEnumerable
addresses) + public static Gen> Generator(IEnumerable
addresses) { var statusGen = ClusterGenerators.MemberStatusGenerator().Generator; - Func> generator = + Func> generator = (address, status) => new ChangeMemberStatus(address, status); var producer = FsharpDelegateHelper.Create(generator); @@ -145,7 +143,7 @@ public override bool Pre(MembershipModel model) return Member.AllowedTransitions[m.Status].Contains(NewStatus); } - public override Property Check(MembershipState actual, MembershipModel model) + public override Property Check(MemberOrderingState actual, MembershipModel model) { var members = actual.Members; @@ -171,11 +169,11 @@ public override string ToString() } } - public class AddNewMember : Operation + public class AddNewMember : Operation { - public static Gen> Generator() + public static Gen> Generator() { - return Arb.Generate().Select(x => (Operation)x); + return Arb.Generate().Select(x => (Operation)x); } private readonly UniqueAddress _address; @@ -195,11 +193,11 @@ public override bool Pre(MembershipModel model) return !member.UniqueAddress.Equals(_address); } - public override Property Check(MembershipState actual, MembershipModel model) + public override Property Check(MemberOrderingState actual, MembershipModel model) { var members = actual.Members; actual.Members = members.Add(new Member(_address, int.MaxValue, MemberStatus.Up, - ImmutableHashSet.Empty)); + ImmutableHashSet.Empty, AppVersion.Zero)); var except = actual.Members.SymmetricExcept(model.AllMembers.Values); @@ -212,7 +210,7 @@ public override MembershipModel Run(MembershipModel model) { return model.UpdateMember(new Member(_address, int.MaxValue, MemberStatus.Up, - ImmutableHashSet.Empty)); + ImmutableHashSet.Empty, AppVersion.Zero)); } public override string ToString() @@ -224,7 +222,7 @@ public override string ToString() #endregion } - public class MembershipState + public class MemberOrderingState { public ImmutableSortedSet Members { get; set; } } @@ -249,4 +247,3 @@ public MembershipModel UpdateMember(Member m) } } } -#endif diff --git a/src/core/Akka.Cluster.Tests/SBR/LeaseMajoritySpec.cs b/src/core/Akka.Cluster.Tests/SBR/LeaseMajoritySpec.cs index ed06184de5c..ea951d3d28b 100644 --- a/src/core/Akka.Cluster.Tests/SBR/LeaseMajoritySpec.cs +++ b/src/core/Akka.Cluster.Tests/SBR/LeaseMajoritySpec.cs @@ -1,4 +1,11 @@ -using Akka.Cluster.Configuration; +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using Akka.Cluster.Configuration; using Akka.Cluster.SBR; using Akka.Configuration; using Akka.TestKit; diff --git a/src/core/Akka.Cluster.Tests/Serialization/ClusterMessageSerializerSpec.cs b/src/core/Akka.Cluster.Tests/Serialization/ClusterMessageSerializerSpec.cs index 2781b51ffb4..19dc355ff8d 100644 --- a/src/core/Akka.Cluster.Tests/Serialization/ClusterMessageSerializerSpec.cs +++ b/src/core/Akka.Cluster.Tests/Serialization/ClusterMessageSerializerSpec.cs @@ -8,11 +8,14 @@ using System.Collections.Immutable; using Akka.Actor; using Akka.Cluster.Routing; +using Akka.Cluster.Serialization; using Akka.Routing; +using Akka.Serialization; using Akka.TestKit; using Xunit; using FluentAssertions; using Akka.Util; +using Google.Protobuf; namespace Akka.Cluster.Tests.Serialization { @@ -33,19 +36,47 @@ public ClusterMessageSerializerSpec() public void Can_serialize_Heartbeat() { var address = new Address("akka.tcp", "system", "some.host.org", 4711); - var message = new ClusterHeartbeatSender.Heartbeat(address); + var message = new ClusterHeartbeatSender.Heartbeat(address, -1, -1); AssertEqual(message); } + [Fact] + public void Can_serialize_Hearbeatv1419_later() + { + var hb = new Akka.Cluster.Serialization.Proto.Msg.Heartbeat() + { + From = Akka.Cluster.Serialization.ClusterMessageSerializer.AddressToProto(a1.Address), + CreationTime = 2, + SequenceNr = 1 + }.ToByteArray(); + + var serializer = (SerializerWithStringManifest)Sys.Serialization.FindSerializerForType(typeof(ClusterHeartbeatSender.Heartbeat)); + serializer.FromBinary(hb, Akka.Cluster.Serialization.ClusterMessageSerializer.HeartBeatManifest); + } + [Fact] public void Can_serialize_HeartbeatRsp() { var address = new Address("akka.tcp", "system", "some.host.org", 4711); var uniqueAddress = new UniqueAddress(address, 17); - var message = new ClusterHeartbeatSender.HeartbeatRsp(uniqueAddress); + var message = new ClusterHeartbeatSender.HeartbeatRsp(uniqueAddress, -1, -1); AssertEqual(message); } + [Fact] + public void Can_serialize_HearbeatRspv1419_later() + { + var hb = new Akka.Cluster.Serialization.Proto.Msg.HeartBeatResponse() + { + From = Akka.Cluster.Serialization.ClusterMessageSerializer.UniqueAddressToProto(a1.UniqueAddress), + CreationTime = 2, + SequenceNr = 1 + }.ToByteArray(); + + var serializer = (SerializerWithStringManifest)Sys.Serialization.FindSerializerForType(typeof(ClusterHeartbeatSender.Heartbeat)); + serializer.FromBinary(hb, Akka.Cluster.Serialization.ClusterMessageSerializer.HeartBeatRspManifest); + } + [Fact] public void Can_serialize_GossipEnvelope() { @@ -191,6 +222,7 @@ private T AssertAndReturn(T message) { var serializer = Sys.Serialization.FindSerializerFor(message); var serialized = serializer.ToBinary(message); + serializer.Should().BeOfType(); return serializer.FromBinary(serialized); } diff --git a/src/core/Akka.Cluster/AutoDown.cs b/src/core/Akka.Cluster/AutoDown.cs index 716e2502dc8..42167898622 100644 --- a/src/core/Akka.Cluster/AutoDown.cs +++ b/src/core/Akka.Cluster/AutoDown.cs @@ -10,6 +10,7 @@ using Akka.Actor; using Akka.Event; using Akka.Configuration; +using static Akka.Cluster.MembershipState; namespace Akka.Cluster { @@ -140,7 +141,7 @@ public override void Down(Address node) internal abstract class AutoDownBase : UntypedActor { private readonly ImmutableHashSet _skipMemberStatus = - Gossip.ConvergenceSkipUnreachableWithMemberStatus; + ConvergenceSkipUnreachableWithMemberStatus; private ImmutableDictionary _scheduledUnreachable = ImmutableDictionary.Create(); diff --git a/src/core/Akka.Cluster/Cluster.cs b/src/core/Akka.Cluster/Cluster.cs index 52de7e074b5..a246c163519 100644 --- a/src/core/Akka.Cluster/Cluster.cs +++ b/src/core/Akka.Cluster/Cluster.cs @@ -62,13 +62,29 @@ public static Cluster Get(ActorSystem system) return system.WithExtension(); } + static Cluster() + { + bool GetAssertInvariants() + { + var isOn = Environment.GetEnvironmentVariable("AKKA_CLUSTER_ASSERT")?.ToLowerInvariant(); + switch (isOn) + { + case "on": + return true; + default: + return false; + } + } + + IsAssertInvariantsEnabled = GetAssertInvariants(); + } + /// /// TBD /// internal static bool IsAssertInvariantsEnabled { - //TODO: Consequences of this? - get { return false; } + get; } /// @@ -538,10 +554,7 @@ internal void Shutdown() LogInfo("Shutting down..."); System.Stop(_clusterDaemons); - if (_readView != null) - { - _readView.Dispose(); - } + _readView?.Dispose(); LogInfo("Successfully shut down"); } @@ -583,6 +596,27 @@ public InfoLogger(ILoggingAdapter log, ClusterSettings settings, Address selfAdd _selfAddress = selfAddress; } + /// + /// Creates an log entry with the specific message. + /// + /// The message being logged. + internal void LogDebug(string message) + { + if (_log.IsDebugEnabled) + _log.Debug("Cluster Node [{0}] - {1}", _selfAddress, message); + } + + /// + /// Creates an log entry with the specific template and arguments. + /// + /// The template being rendered and logged. + /// The argument that fills in the template placeholder. + internal void LogDebug(string template, object arg1) + { + if (_log.IsDebugEnabled) + _log.Debug("Cluster Node [{1}] - " + template, arg1, _selfAddress); + } + /// /// Creates an log entry with the specific message. /// diff --git a/src/core/Akka.Cluster/ClusterDaemon.cs b/src/core/Akka.Cluster/ClusterDaemon.cs index 6785e89ae51..d2e27929c61 100644 --- a/src/core/Akka.Cluster/ClusterDaemon.cs +++ b/src/core/Akka.Cluster/ClusterDaemon.cs @@ -17,6 +17,7 @@ using Akka.Util; using Akka.Util.Internal; using Akka.Util.Internal.Collections; +using static Akka.Cluster.MembershipState; namespace Akka.Cluster { @@ -775,18 +776,18 @@ private interface IPublishMessage : INoSerializationVerificationNeeded { } internal sealed class PublishChanges : IPublishMessage { /// - /// Creates a new message with updated gossip. + /// Creates a new message with updated membership state. /// - /// The gossip to publish internally. - internal PublishChanges(Gossip newGossip) + /// The membership state to publish internally. + internal PublishChanges(MembershipState newState) { - NewGossip = newGossip; + NewState = newState; } /// /// The gossip being published. /// - public Gossip NewGossip { get; } + public MembershipState NewState { get; } } /// @@ -898,7 +899,7 @@ private void CreateChildren() { _coreSupervisor = Context.ActorOf(Props.Create(), "core"); - Context.ActorOf(Props.Create(), "heartbeatReceiver"); + Context.ActorOf(ClusterHeartbeatReceiver.Props(() => Cluster.Get(Context.System)), "heartbeatReceiver"); } protected override void PostStop() @@ -1001,7 +1002,8 @@ internal static string VclockName(UniqueAddress node) // note that self is not initially member, // and the SendGossip is not versioned for this 'Node' yet - private Gossip _latestGossip = Gossip.Empty; + private MembershipState _membershipState; + private Gossip LatestGossip => _membershipState.LatestGossip; private readonly bool _statsEnabled; private GossipStats _gossipStats = new GossipStats(); @@ -1017,7 +1019,7 @@ internal static string VclockName(UniqueAddress node) private bool _exitingTasksInProgress = false; private readonly TaskCompletionSource _selfExiting = new TaskCompletionSource(); private readonly CoordinatedShutdown _coordShutdown = CoordinatedShutdown.Get(Context.System); - private HashSet _exitingConfirmed = new HashSet(); + private ImmutableHashSet _exitingConfirmed = ImmutableHashSet.Empty; /// @@ -1027,6 +1029,7 @@ internal static string VclockName(UniqueAddress node) public ClusterCoreDaemon(IActorRef publisher) { _cluster = Cluster.Get(Context.System); + _membershipState = new MembershipState(Gossip.Empty, _cluster.SelfUniqueAddress); _publisher = publisher; SelfUniqueAddress = _cluster.SelfUniqueAddress; _vclockNode = VectorClock.Node.Create(VclockName(SelfUniqueAddress)); @@ -1087,7 +1090,7 @@ private void AddCoordinatedLeave() var self = Self; _coordShutdown.AddTask(CoordinatedShutdown.PhaseClusterExiting, "wait-exiting", () => { - if (_latestGossip.Members.IsEmpty) + if (LatestGossip.Members.IsEmpty) return Task.FromResult(Done.Instance); // not joined yet else return _selfExiting.Task; @@ -1146,7 +1149,7 @@ protected override void PostStop() _gossipTaskCancellable.Cancel(); _failureDetectorReaperTaskCancellable.Cancel(); _leaderActionsTaskCancellable.Cancel(); - if (_publishStatsTaskTaskCancellable != null) _publishStatsTaskTaskCancellable.Cancel(); + _publishStatsTaskTaskCancellable?.Cancel(); _selfExiting.TrySetResult(Done.Instance); } @@ -1156,35 +1159,38 @@ private void ExitingCompleted() // ExitingCompleted sent via CoordinatedShutdown to continue the leaving process. _exitingTasksInProgress = false; - // mark as seen - _latestGossip = _latestGossip.Seen(SelfUniqueAddress); - AssertLatestGossip(); - Publish(_latestGossip); - - // Let others know (best effort) before shutdown. Otherwise they will not see - // convergence of the Exiting state until they have detected this node as - // unreachable and the required downing has finished. They will still need to detect - // unreachable, but Exiting unreachable will be removed without downing, i.e. - // normally the leaving of a leader will be graceful without the need - // for downing. However, if those final gossip messages never arrive it is - // alright to require the downing, because that is probably caused by a - // network failure anyway. - SendGossipRandom(NumberOfGossipsBeforeShutdownWhenLeaderExits); - - // send ExitingConfirmed to two potential leaders - var membersWithoutSelf = _latestGossip.Members.Where(m => !m.UniqueAddress.Equals(SelfUniqueAddress)) - .ToImmutableSortedSet(); - var leader = _latestGossip.LeaderOf(membersWithoutSelf, SelfUniqueAddress); - if (leader != null) - { - ClusterCore(leader.Address).Tell(new InternalClusterAction.ExitingConfirmed(SelfUniqueAddress)); - var leader2 = - _latestGossip.LeaderOf( - membersWithoutSelf.Where(x => !x.UniqueAddress.Equals(leader)).ToImmutableSortedSet(), - SelfUniqueAddress); - if (leader2 != null) + // status Removed also before joining + if (_membershipState.SelfMember.Status != MemberStatus.Removed) + { + // mark as seen + _membershipState = _membershipState.Seen(); + AssertLatestGossip(); + PublishMembershipState(); + + // Let others know (best effort) before shutdown. Otherwise they will not see + // convergence of the Exiting state until they have detected this node as + // unreachable and the required downing has finished. They will still need to detect + // unreachable, but Exiting unreachable will be removed without downing, i.e. + // normally the leaving of a leader will be graceful without the need + // for downing. However, if those final gossip messages never arrive it is + // alright to require the downing, because that is probably caused by a + // network failure anyway. + SendGossipRandom(NumberOfGossipsBeforeShutdownWhenLeaderExits); + + // send ExitingConfirmed to two potential leaders + var membersWithoutSelf = LatestGossip.Members.Where(m => !m.UniqueAddress.Equals(SelfUniqueAddress)) + .ToImmutableSortedSet(); + var leader = _membershipState.LeaderOf(membersWithoutSelf); + if (leader != null) { - ClusterCore(leader2.Address).Tell(new InternalClusterAction.ExitingConfirmed(SelfUniqueAddress)); + ClusterCore(leader.Address).Tell(new InternalClusterAction.ExitingConfirmed(SelfUniqueAddress)); + var leader2 = + _membershipState.LeaderOf( + membersWithoutSelf.Where(x => !x.UniqueAddress.Equals(leader)).ToImmutableSortedSet()); + if (leader2 != null) + { + ClusterCore(leader2.Address).Tell(new InternalClusterAction.ExitingConfirmed(SelfUniqueAddress)); + } } } @@ -1194,7 +1200,7 @@ private void ExitingCompleted() private void ReceiveExitingConfirmed(UniqueAddress node) { _cluster.LogInfo("Exiting confirmed [{0}]", node.Address); - _exitingConfirmed.Add(node); + _exitingConfirmed = _exitingConfirmed.Add(node); } private void CleanupExitingConfirmed() @@ -1202,7 +1208,7 @@ private void CleanupExitingConfirmed() // in case the actual removal was performed by another leader node if (_exitingConfirmed.Any()) { - _exitingConfirmed = new HashSet(_exitingConfirmed.Where(n => _latestGossip.Members.Any(m => m.UniqueAddress.Equals(n)))); + _exitingConfirmed = _exitingConfirmed.Where(n => LatestGossip.Members.Any(m => m.UniqueAddress.Equals(n))).ToImmutableHashSet(); } } @@ -1443,8 +1449,8 @@ protected override void Unhandled(object message) /// public void InitJoin() { - var selfStatus = _latestGossip.GetMember(SelfUniqueAddress).Status; - if (Gossip.RemoveUnreachableWithMemberStatus.Contains(selfStatus)) + var selfStatus = LatestGossip.GetMember(SelfUniqueAddress).Status; + if (RemoveUnreachableWithMemberStatus.Contains(selfStatus)) { _cluster.LogInfo("Sending InitJoinNack message from node [{0}] to [{1}]", SelfUniqueAddress.Address, Sender); @@ -1514,7 +1520,7 @@ public void Join(Address address) else { //TODO: Akka exception? - if (!_latestGossip.Members.IsEmpty) throw new InvalidOperationException("Join can only be done from an empty state"); + if (!LatestGossip.Members.IsEmpty) throw new InvalidOperationException("Join can only be done from an empty state"); // to support manual join when joining to seed nodes is stuck (no seed nodes available) StopSeedNodeProcess(); @@ -1563,7 +1569,7 @@ public void StopSeedNodeProcess() /// TBD public void Joining(UniqueAddress node, ImmutableHashSet roles, AppVersion appVersion) { - var selfStatus = _latestGossip.GetMember(SelfUniqueAddress).Status; + var selfStatus = LatestGossip.GetMember(SelfUniqueAddress).Status; if (!node.Address.Protocol.Equals(_cluster.SelfAddress.Protocol)) { _log.Warning("Member with wrong protocol tried to join, but was ignored, expected [{0}] but was [{1}]", @@ -1574,14 +1580,14 @@ public void Joining(UniqueAddress node, ImmutableHashSet roles, AppVersi _log.Warning("Member with wrong ActorSystem name tried to join, but was ignored, expected [{0}] but was [{1}]", _cluster.SelfAddress.System, node.Address.System); } - else if (Gossip.RemoveUnreachableWithMemberStatus.Contains(selfStatus)) + else if (RemoveUnreachableWithMemberStatus.Contains(selfStatus)) { _cluster.LogInfo("Trying to join [{0}] to [{1}] member, ignoring. Use a member that is Up instead.", node, selfStatus); } else { - var localMembers = _latestGossip.Members; + var localMembers = LatestGossip.Members; // check by address without uid to make sure that node with same host:port is not allowed // to join until previous node with that host:port has been removed from the cluster @@ -1592,7 +1598,7 @@ public void Joining(UniqueAddress node, ImmutableHashSet roles, AppVersi _cluster.LogInfo("Existing member [{0}] is joining again.", node); if (!node.Equals(SelfUniqueAddress)) { - Sender.Tell(new InternalClusterAction.Welcome(SelfUniqueAddress, _latestGossip)); + Sender.Tell(new InternalClusterAction.Welcome(SelfUniqueAddress, LatestGossip)); } } else if (localMember != null) @@ -1605,10 +1611,10 @@ public void Joining(UniqueAddress node, ImmutableHashSet roles, AppVersi if (localMember.Status != MemberStatus.Down) { // we can confirm it as terminated/unreachable immediately - var newReachability = _latestGossip.Overview.Reachability.Terminated( + var newReachability = LatestGossip.Overview.Reachability.Terminated( _cluster.SelfUniqueAddress, localMember.UniqueAddress); - var newOverview = _latestGossip.Overview.Copy(reachability: newReachability); - var newGossip = _latestGossip.Copy(overview: newOverview); + var newOverview = LatestGossip.Overview.Copy(reachability: newReachability); + var newGossip = LatestGossip.Copy(overview: newOverview); UpdateLatestGossip(newGossip); Downing(localMember.Address); } @@ -1623,7 +1629,7 @@ public void Joining(UniqueAddress node, ImmutableHashSet roles, AppVersi var newMembers = localMembers .Add(Member.Create(node, roles, appVersion)) .Add(Member.Create(_cluster.SelfUniqueAddress, _cluster.SelfRoles, _cluster.Settings.AppVersion)); - var newGossip = _latestGossip.Copy(members: newMembers); + var newGossip = LatestGossip.Copy(members: newMembers); UpdateLatestGossip(newGossip); @@ -1642,10 +1648,10 @@ public void Joining(UniqueAddress node, ImmutableHashSet roles, AppVersi else { _cluster.LogInfo("Node [{0}] is JOINING, roles [{1}], version [{2}]", node.Address, string.Join(",", roles), appVersion); - Sender.Tell(new InternalClusterAction.Welcome(SelfUniqueAddress, _latestGossip)); + Sender.Tell(new InternalClusterAction.Welcome(SelfUniqueAddress, LatestGossip)); } - Publish(_latestGossip); + PublishMembershipState(); } } } @@ -1659,7 +1665,7 @@ public void Joining(UniqueAddress node, ImmutableHashSet roles, AppVersi /// Welcome can only be done from an empty state public void Welcome(Address joinWith, UniqueAddress from, Gossip gossip) { - if (!_latestGossip.Members.IsEmpty) throw new InvalidOperationException("Welcome can only be done from an empty state"); + if (!LatestGossip.Members.IsEmpty) throw new InvalidOperationException("Welcome can only be done from an empty state"); if (!joinWith.Equals(from.Address)) { _cluster.LogInfo("Ignoring welcome from [{0}] when trying to join with [{1}]", from.Address, joinWith); @@ -1667,9 +1673,9 @@ public void Welcome(Address joinWith, UniqueAddress from, Gossip gossip) else { _cluster.LogInfo("Welcome from [{0}]", from.Address); - _latestGossip = gossip.Seen(SelfUniqueAddress); + _membershipState = _membershipState.Copy(gossip).Seen(); AssertLatestGossip(); - Publish(_latestGossip); + PublishMembershipState(); if (!from.Equals(SelfUniqueAddress)) GossipTo(from, Sender); BecomeInitialized(); @@ -1685,20 +1691,20 @@ public void Welcome(Address joinWith, UniqueAddress from, Gossip gossip) public void Leaving(Address address) { // only try to update if the node is available (in the member ring) - if (_latestGossip.Members.Any(m => m.Address.Equals(address) && (m.Status == MemberStatus.Joining || m.Status == MemberStatus.WeaklyUp || m.Status == MemberStatus.Up))) + if (LatestGossip.Members.Any(m => m.Address.Equals(address) && (m.Status == MemberStatus.Joining || m.Status == MemberStatus.WeaklyUp || m.Status == MemberStatus.Up))) { // mark node as LEAVING - var newMembers = _latestGossip.Members.Select(m => + var newMembers = LatestGossip.Members.Select(m => { if (m.Address == address) return m.Copy(status: MemberStatus.Leaving); return m; }).ToImmutableSortedSet(); // mark node as LEAVING - var newGossip = _latestGossip.Copy(members: newMembers); + var newGossip = LatestGossip.Copy(members: newMembers); UpdateLatestGossip(newGossip); _cluster.LogInfo("Marked address [{0}] as [{1}]", address, MemberStatus.Leaving); - Publish(_latestGossip); + PublishMembershipState(); // immediate gossip to speed up the leaving process SendGossip(); } @@ -1721,11 +1727,11 @@ public void Shutdown() /// The address of the member that will be downed. public void Downing(Address address) { - var localGossip = _latestGossip; + var localGossip = LatestGossip; var localMembers = localGossip.Members; var localOverview = localGossip.Overview; var localSeen = localOverview.Seen; - var localReachability = localOverview.Reachability; + var localReachability = _membershipState.DcReachability; // check if the node to DOWN is in the 'members' set var member = localMembers.FirstOrDefault(m => m.Address == address); @@ -1736,17 +1742,11 @@ public void Downing(Address address) else _cluster.LogInfo("Marking unreachable node [{0}] as [{1}]", member.Address, MemberStatus.Down); - // replace member (changed status) - var newMembers = localMembers.Remove(member).Add(member.Copy(MemberStatus.Down)); - // remove nodes marked as DOWN from the 'seen' table - var newSeen = localSeen.Remove(member.UniqueAddress); - //update gossip overview - var newOverview = localOverview.Copy(seen: newSeen); - var newGossip = localGossip.Copy(members: newMembers, overview: newOverview); //update gossip + var newGossip = localGossip.MarkAsDown(member); //update gossip UpdateLatestGossip(newGossip); - Publish(_latestGossip); + PublishMembershipState(); if (address == _cluster.SelfAddress) { @@ -1775,7 +1775,7 @@ public void Downing(Address address) /// TBD public void Quarantined(UniqueAddress node) { - var localGossip = _latestGossip; + var localGossip = LatestGossip; if (localGossip.HasMember(node)) { var newReachability = localGossip.Overview.Reachability.Terminated(SelfUniqueAddress, node); @@ -1784,7 +1784,7 @@ public void Quarantined(UniqueAddress node) UpdateLatestGossip(newGossip); _log.Warning("Cluster Node [{0}] - Marking node as TERMINATED [{1}], due to quarantine. Node roles [{2}]. It must still be marked as down before it's removed.", Self, node.Address, string.Join(",", _cluster.SelfRoles)); - Publish(_latestGossip); + PublishMembershipState(); } } @@ -1795,14 +1795,14 @@ public void Quarantined(UniqueAddress node) public void ReceiveGossipStatus(GossipStatus status) { var from = status.From; - if (!_latestGossip.Overview.Reachability.IsReachable(SelfUniqueAddress, from)) + if (!LatestGossip.Overview.Reachability.IsReachable(SelfUniqueAddress, from)) _cluster.LogInfo("Ignoring received gossip status from unreachable [{0}]", from); - else if (_latestGossip.Members.All(m => !m.UniqueAddress.Equals(from))) + else if (LatestGossip.Members.All(m => !m.UniqueAddress.Equals(from))) _cluster.LogInfo("Cluster Node [{0}] - Ignoring received gossip status from unknown [{1}]", _cluster.SelfAddress, from); else { - var comparison = status.Version.CompareTo(_latestGossip.Version); + var comparison = status.Version.CompareTo(LatestGossip.Version); switch (comparison) { case VectorClock.Ordering.Same: @@ -1854,7 +1854,7 @@ public ReceiveGossipType ReceiveGossip(GossipEnvelope envelope) { var from = envelope.From; var remoteGossip = envelope.Gossip; - var localGossip = _latestGossip; + var localGossip = LatestGossip; if (remoteGossip.Equals(Gossip.Empty)) { @@ -1918,7 +1918,7 @@ public ReceiveGossipType ReceiveGossip(GossipEnvelope envelope) // Removal of member itself is handled in merge (pickHighestPriority) var prunedLocalGossip = localGossip.Members.Aggregate(localGossip, (g, m) => { - if (Gossip.RemoveUnreachableWithMemberStatus.Contains(m.Status) && !remoteGossip.Members.Contains(m)) + if (RemoveUnreachableWithMemberStatus.Contains(m.Status) && !remoteGossip.Members.Contains(m)) { _log.Debug("Cluster Node [{0}] - Pruned conflicting local gossip: {1}", _cluster.SelfAddress, m); return g.Prune(VectorClock.Node.Create(VclockName(m.UniqueAddress))); @@ -1928,7 +1928,7 @@ public ReceiveGossipType ReceiveGossip(GossipEnvelope envelope) var prunedRemoteGossip = remoteGossip.Members.Aggregate(remoteGossip, (g, m) => { - if (Gossip.RemoveUnreachableWithMemberStatus.Contains(m.Status) && !localGossip.Members.Contains(m)) + if (RemoveUnreachableWithMemberStatus.Contains(m.Status) && !localGossip.Members.Contains(m)) { _log.Debug("Cluster Node [{0}] - Pruned conflicting remote gossip: {1}", _cluster.SelfAddress, m); return g.Prune(VectorClock.Node.Create(VclockName(m.UniqueAddress))); @@ -1948,20 +1948,23 @@ public ReceiveGossipType ReceiveGossip(GossipEnvelope envelope) // the exiting tasks have been completed. if (_exitingTasksInProgress) { - _latestGossip = winningGossip; + _membershipState = _membershipState.Copy(winningGossip); } else { - _latestGossip = winningGossip.Seen(SelfUniqueAddress); + _membershipState = _membershipState.Copy(winningGossip.Seen(SelfUniqueAddress)); } AssertLatestGossip(); // for all new joining nodes we remove them from the failure detector - foreach (var node in _latestGossip.Members) + foreach (var node in LatestGossip.Members) { - if (node.Status == MemberStatus.Joining && !localGossip.Members.Contains(node)) + if (!localGossip.Members.Contains(node)) + { _cluster.FailureDetector.Remove(node.Address); + } + } _log.Debug("Cluster Node [{0}] - Receiving gossip from [{1}]", _cluster.SelfAddress, from); @@ -1991,9 +1994,9 @@ public ReceiveGossipType ReceiveGossip(GossipEnvelope envelope) } } - Publish(_latestGossip); + PublishMembershipState(); - var selfStatus = _latestGossip.GetMember(SelfUniqueAddress).Status; + var selfStatus = LatestGossip.GetMember(SelfUniqueAddress).Status; if (selfStatus == MemberStatus.Exiting && !_exitingTasksInProgress) { // ExitingCompleted will be received via CoordinatedShutdown to continue @@ -2049,17 +2052,17 @@ public void GossipSpeedupTick() /// public bool IsGossipSpeedupNeeded() { - return _latestGossip.Members.Any(m => m.Status == MemberStatus.Down) || - _latestGossip.Overview.Seen.Count < _latestGossip.Members.Count / 2; + return LatestGossip.Members.Any(m => m.Status == MemberStatus.Down) || + LatestGossip.Overview.Seen.Count < LatestGossip.Members.Count / 2; } private void SendGossipRandom(int n) { if (!IsSingletonCluster && n > 0) { - var localGossip = _latestGossip; + var localGossip = LatestGossip; var possibleTargets = - localGossip.Members.Where(m => ValidNodeForGossip(m.UniqueAddress)) + localGossip.Members.Where(m => _membershipState.ValidNodeForGossip(m.UniqueAddress)) .Select(m => m.UniqueAddress) .ToList(); var randomTargets = possibleTargets.Count <= n ? possibleTargets : possibleTargets.Shuffle().Slice(0, n); @@ -2074,7 +2077,7 @@ public void SendGossip() { if (!IsSingletonCluster) { - var localGossip = _latestGossip; + var localGossip = LatestGossip; ImmutableList preferredGossipTarget; @@ -2082,8 +2085,8 @@ public void SendGossip() { // If it's time to try to gossip to some nodes with a different view // gossip to a random alive member with preference to a member with older gossip version - preferredGossipTarget = ImmutableList.CreateRange(localGossip.Members.Where(m => !localGossip.SeenByNode(m.UniqueAddress) && - ValidNodeForGossip(m.UniqueAddress)).Select(m => m.UniqueAddress)); + preferredGossipTarget = ImmutableList.CreateRange(localGossip.Members.Where(m => !localGossip.SeenByNode(m.UniqueAddress) + && _membershipState.ValidNodeForGossip(m.UniqueAddress)).Select(m => m.UniqueAddress)); } else { @@ -2102,7 +2105,7 @@ public void SendGossip() var peer = SelectRandomNode( ImmutableList.CreateRange( - localGossip.Members.Where(m => ValidNodeForGossip(m.UniqueAddress)) + localGossip.Members.Where(m => _membershipState.ValidNodeForGossip(m.UniqueAddress)) .Select(m => m.UniqueAddress))); if (peer != null) @@ -2122,7 +2125,7 @@ public double AdjustedGossipDifferentViewProbability { get { - var size = _latestGossip.Members.Count; + var size = LatestGossip.Members.Count; var low = _cluster.Settings.ReduceGossipDifferentViewProbability; var high = low * 3; // start reduction when cluster is larger than configured ReduceGossipDifferentViewProbability @@ -2146,7 +2149,7 @@ public double AdjustedGossipDifferentViewProbability /// public void LeaderActions() { - if (_latestGossip.IsLeader(SelfUniqueAddress, SelfUniqueAddress)) + if (_membershipState.IsLeader(SelfUniqueAddress)) { // only run the leader actions if we are the LEADER if (!_isCurrentlyLeader) @@ -2156,7 +2159,7 @@ public void LeaderActions() } const int firstNotice = 20; const int periodicNotice = 60; - if (_latestGossip.Convergence(SelfUniqueAddress, _exitingConfirmed)) + if (_membershipState.Convergence(_exitingConfirmed)) { if (_leaderActionCounter >= firstNotice) _cluster.LogInfo("Leader can perform its duties again"); @@ -2167,19 +2170,19 @@ public void LeaderActions() { _leaderActionCounter += 1; - if (_cluster.Settings.AllowWeaklyUpMembers && _leaderActionCounter >= 3) + if (_cluster.Settings.AllowWeaklyUpMembers && (_leaderActionCounter * _cluster.Settings.LeaderActionsInterval.TotalMilliseconds) >= _cluster.Settings.WeaklyUpAfter.TotalMilliseconds) MoveJoiningToWeaklyUp(); if (_leaderActionCounter == firstNotice || _leaderActionCounter % periodicNotice == 0) { _cluster.LogInfo( "Leader can currently not perform its duties, reachability status: [{0}], member status: [{1}]", - _latestGossip.ReachabilityExcludingDownedObservers, - string.Join(", ", _latestGossip.Members + _membershipState.DcReachabilityExcludingDownedObservers, + string.Join(", ", LatestGossip.Members .Select(m => string.Format("${0} ${1} seen=${2}", m.Address, m.Status, - _latestGossip.SeenByNode(m.UniqueAddress))))); + LatestGossip.SeenByNode(m.UniqueAddress))))); } } } @@ -2195,13 +2198,13 @@ public void LeaderActions() private void MoveJoiningToWeaklyUp() { - var localGossip = _latestGossip; + var localGossip = LatestGossip; var localMembers = localGossip.Members; var enoughMembers = IsMinNrOfMembersFulfilled(); bool IsJoiningToWeaklyUp(Member m) => m.Status == MemberStatus.Joining && enoughMembers - && _latestGossip.ReachabilityExcludingDownedObservers.Value.IsReachable(m.UniqueAddress); + && _membershipState.DcReachabilityExcludingDownedObservers.IsReachable(m.UniqueAddress); var changedMembers = localMembers .Where(IsJoiningToWeaklyUp) @@ -2221,21 +2224,21 @@ bool IsJoiningToWeaklyUp(Member m) => m.Status == MemberStatus.Joining _cluster.LogInfo("Leader is moving node [{0}] to [{1}]", m.Address, m.Status); } - Publish(newGossip); + PublishMembershipState(); if (_cluster.Settings.PublishStatsInterval == TimeSpan.Zero) PublishInternalStats(); } } private void ShutdownSelfWhenDown() { - if (_latestGossip.GetMember(SelfUniqueAddress).Status == MemberStatus.Down) + if (LatestGossip.GetMember(SelfUniqueAddress).Status == MemberStatus.Down) { // When all reachable have seen the state this member will shutdown itself when it has // status Down. The down commands should spread before we shutdown. - var unreachable = _latestGossip.Overview.Reachability.AllUnreachableOrTerminated; - var downed = _latestGossip.Members.Where(m => m.Status == MemberStatus.Down) + var unreachable = LatestGossip.Overview.Reachability.AllUnreachableOrTerminated; + var downed = LatestGossip.Members.Where(m => m.Status == MemberStatus.Down) .Select(m => m.UniqueAddress).ToList(); - if (_selfDownCounter >= MaxTicksBeforeShuttingDownMyself || downed.All(node => unreachable.Contains(node) || _latestGossip.SeenByNode(node))) + if (_selfDownCounter >= MaxTicksBeforeShuttingDownMyself || downed.All(node => unreachable.Contains(node) || LatestGossip.SeenByNode(node))) { // the reason for not shutting down immediately is to give the gossip a chance to spread // the downing information to other downed nodes, so that they can shutdown themselves @@ -2262,10 +2265,10 @@ private void ShutdownSelfWhenDown() /// public bool IsMinNrOfMembersFulfilled() { - return _latestGossip.Members.Count >= _cluster.Settings.MinNrOfMembers + return LatestGossip.Members.Count >= _cluster.Settings.MinNrOfMembers && _cluster.Settings .MinNrOfMembersOfRole - .All(x => _latestGossip.Members.Count(c => c.HasRole(x.Key)) >= x.Value); + .All(x => LatestGossip.Members.Count(c => c.HasRole(x.Key)) >= x.Value); } /// @@ -2283,7 +2286,7 @@ public bool IsMinNrOfMembersFulfilled() /// public void LeaderActionsOnConvergence() { - var localGossip = _latestGossip; + var localGossip = LatestGossip; var localMembers = localGossip.Members; var localOverview = localGossip.Overview; var localSeen = localOverview.Seen; @@ -2293,7 +2296,7 @@ public void LeaderActionsOnConvergence() var removedUnreachable = localOverview.Reachability.AllUnreachableOrTerminated.Select(localGossip.GetMember) - .Where(m => Gossip.RemoveUnreachableWithMemberStatus.Contains(m.Status)) + .Where(m => RemoveUnreachableWithMemberStatus.Contains(m.Status)) .ToImmutableHashSet(); var removedExitingConfirmed = @@ -2374,7 +2377,7 @@ public void LeaderActionsOnConvergence() } UpdateLatestGossip(newGossip); - _exitingConfirmed = new HashSet(_exitingConfirmed.Except(removedExitingConfirmed)); + _exitingConfirmed = _exitingConfirmed.Except(removedExitingConfirmed); // log status changes foreach (var m in changedMembers) @@ -2392,7 +2395,7 @@ public void LeaderActionsOnConvergence() _cluster.LogInfo("Leader is removing confirmed Exiting node [{0}]", m.Address); } - Publish(_latestGossip); + PublishMembershipState(); GossipExitingMembersToOldest(changedMembers.Where(i => i.Status == MemberStatus.Exiting)); } } @@ -2403,7 +2406,7 @@ public void LeaderActionsOnConvergence() /// private void GossipExitingMembersToOldest(IEnumerable exitingMembers) { - var targets = GossipTargetsForExitingMembers(_latestGossip, exitingMembers); + var targets = GossipTargetsForExitingMembers(LatestGossip, exitingMembers); if (targets != null && targets.Any()) { if (_log.IsDebugEnabled) @@ -2425,7 +2428,7 @@ public void ReapUnreachableMembers() { // only scrutinize if we are a non-singleton cluster - var localGossip = _latestGossip; + var localGossip = LatestGossip; var localOverview = localGossip.Overview; var localMembers = localGossip.Members; @@ -2450,7 +2453,7 @@ public void ReapUnreachableMembers() newReachability1, (reachability, m) => reachability.Reachable(SelfUniqueAddress, m.UniqueAddress)); - if (!newReachability2.Equals(localOverview.Reachability)) + if (!ReferenceEquals(newReachability2, localOverview.Reachability)) { var newOverview = localOverview.Copy(reachability: newReachability2); var newGossip = localGossip.Copy(overview: newOverview); @@ -2472,7 +2475,7 @@ public void ReapUnreachableMembers() if (!newlyDetectedReachableMembers.IsEmpty) _cluster.LogInfo("Marking node(s) as REACHABLE [{0}]. Node roles [{1}]", newlyDetectedReachableMembers.Select(m => m.ToString()).Aggregate((a, b) => a + ", " + b), string.Join(",", _cluster.SelfRoles)); - Publish(_latestGossip); + PublishMembershipState(); } } } @@ -2494,7 +2497,12 @@ public UniqueAddress SelectRandomNode(ImmutableList nodes) ///
public bool IsSingletonCluster { - get { return _latestGossip.IsSingletonCluster; } + get { return LatestGossip.IsSingletonCluster; } + } + + public UniqueAddress SelfUniqueAddress1 + { + get { return SelfUniqueAddress; } } /// @@ -2503,7 +2511,7 @@ public bool IsSingletonCluster /// TBD public void SendGossipTo(Address address) { - foreach (var m in _latestGossip.Members) + foreach (var m in LatestGossip.Members) { if (m.Address.Equals(address)) GossipTo(m.UniqueAddress); @@ -2516,8 +2524,8 @@ public void SendGossipTo(Address address) /// The address of the node we want to send gossip to. public void GossipTo(UniqueAddress node) { - if (ValidNodeForGossip(node)) - ClusterCore(node.Address).Tell(new GossipEnvelope(SelfUniqueAddress, node, _latestGossip)); + if (_membershipState.ValidNodeForGossip(node)) + ClusterCore(node.Address).Tell(new GossipEnvelope(SelfUniqueAddress, node, LatestGossip)); } /// @@ -2527,8 +2535,8 @@ public void GossipTo(UniqueAddress node) /// TBD public void GossipTo(UniqueAddress node, IActorRef destination) { - if (ValidNodeForGossip(node)) - destination.Tell(new GossipEnvelope(SelfUniqueAddress, node, _latestGossip)); + if (_membershipState.ValidNodeForGossip(node)) + destination.Tell(new GossipEnvelope(SelfUniqueAddress, node, LatestGossip)); } /// @@ -2537,8 +2545,8 @@ public void GossipTo(UniqueAddress node, IActorRef destination) /// TBD public void GossipStatusTo(UniqueAddress node) { - if (ValidNodeForGossip(node)) - ClusterCore(node.Address).Tell(new GossipStatus(SelfUniqueAddress, _latestGossip.Version)); + if (_membershipState.ValidNodeForGossip(node)) + ClusterCore(node.Address).Tell(new GossipStatus(SelfUniqueAddress, LatestGossip.Version)); } /// @@ -2548,18 +2556,8 @@ public void GossipStatusTo(UniqueAddress node) /// TBD public void GossipStatusTo(UniqueAddress node, IActorRef destination) { - if (ValidNodeForGossip(node)) - destination.Tell(new GossipStatus(SelfUniqueAddress, _latestGossip.Version)); - } - - /// - /// TBD - /// - /// TBD - /// TBD - public bool ValidNodeForGossip(UniqueAddress node) - { - return !node.Equals(SelfUniqueAddress) && _latestGossip.Overview.Reachability.IsReachable(SelfUniqueAddress, node); + if (_membershipState.ValidNodeForGossip(node)) + destination.Tell(new GossipStatus(SelfUniqueAddress, LatestGossip.Version)); } /// @@ -2573,7 +2571,8 @@ public static IEnumerable GossipTargetsForExitingMembers(Gossip latestGo if (exitingMembers.Any()) { var roles = exitingMembers.SelectMany(m => m.Roles); - var membersSortedByAge = latestGossip.Members.OrderBy(m => m, Member.AgeOrdering); + var membersSortedByAge = latestGossip.Members + .OrderBy(m => m, Member.AgeOrdering).ToImmutableHashSet(); var targets = new HashSet(); var t = membersSortedByAge.Take(2).ToArray(); // 2 oldest of all nodes @@ -2594,25 +2593,31 @@ public static IEnumerable GossipTargetsForExitingMembers(Gossip latestGo /// /// Updates the local gossip with the latest received from over the network. /// - /// The new gossip to merge with our own. - public void UpdateLatestGossip(Gossip newGossip) + /// The new gossip to merge with our own. + public void UpdateLatestGossip(Gossip gossip) { // Updating the vclock version for the changes - var versionedGossip = newGossip.Increment(_vclockNode); + var versionedGossip = gossip.Increment(_vclockNode); - // Don't mark gossip state as seen while exiting is in progress, e.g. - // shutting down singleton actors. This delays removal of the member until - // the exiting tasks have been completed. - if (_exitingTasksInProgress) - _latestGossip = versionedGossip.ClearSeen(); - else + Gossip PickLatest() { - // Nobody else has seen this gossip but us - var seenVersionedGossip = versionedGossip.OnlySeen(SelfUniqueAddress); + if (_exitingTasksInProgress) + return versionedGossip.ClearSeen(); + else + { + // Nobody else has seen this gossip but us + var seenVersionedGossip = versionedGossip.OnlySeen(SelfUniqueAddress); - // Update the state with the new gossip - _latestGossip = seenVersionedGossip; + // Update the state with the new gossip + return seenVersionedGossip; + } } + + // Don't mark gossip state as seen while exiting is in progress, e.g. + // shutting down singleton actors. This delays removal of the member until + // the exiting tasks have been completed. + var newGossip = PickLatest(); + _membershipState = _membershipState.Copy(newGossip); AssertLatestGossip(); } @@ -2622,19 +2627,21 @@ public void UpdateLatestGossip(Gossip newGossip) /// Thrown if the VectorClock is corrupt and has not been pruned properly. public void AssertLatestGossip() { - if (Cluster.IsAssertInvariantsEnabled && _latestGossip.Version.Versions.Count > _latestGossip.Members.Count) + if (Cluster.IsAssertInvariantsEnabled && LatestGossip.Version.Versions.Count > LatestGossip.Members.Count) { - throw new InvalidOperationException($"Too many vector clock entries in gossip state {_latestGossip}"); + throw new InvalidOperationException($"Too many vector clock entries in gossip state {LatestGossip}"); } } /// /// Publishes gossip to other nodes in the cluster. /// - /// The new gossip to share. - public void Publish(Gossip newGossip) + public void PublishMembershipState() { - _publisher.Tell(new InternalClusterAction.PublishChanges(newGossip)); + if (_cluster.Settings.VerboseGossipReceivedLogging) + _log.Debug("Cluster Node [{0}] - New gossip published [{0}]", SelfUniqueAddress, _membershipState.LatestGossip); + + _publisher.Tell(new InternalClusterAction.PublishChanges(_membershipState)); if (_cluster.Settings.PublishStatsInterval == TimeSpan.Zero) { PublishInternalStats(); @@ -2646,8 +2653,8 @@ public void Publish(Gossip newGossip) /// public void PublishInternalStats() { - var vclockStats = new VectorClockStats(_latestGossip.Version.Versions.Count, - _latestGossip.Members.Count(m => _latestGossip.SeenByNode(m.UniqueAddress))); + var vclockStats = new VectorClockStats(LatestGossip.Version.Versions.Count, + LatestGossip.Members.Count(m => LatestGossip.SeenByNode(m.UniqueAddress))); _publisher.Tell(new ClusterEvent.CurrentInternalStats(_gossipStats, vclockStats)); } @@ -2684,6 +2691,7 @@ internal sealed class JoinSeedNodeProcess : UntypedActor private readonly ILoggingAdapter _log = Context.GetLogger(); private readonly ImmutableList
_seeds; + private readonly ImmutableList
_otherSeeds; private readonly Address _selfAddress; private int _attempts = 0; @@ -2699,6 +2707,7 @@ public JoinSeedNodeProcess(ImmutableList
seeds) { _selfAddress = Cluster.Get(Context.System).SelfAddress; _seeds = seeds; + _otherSeeds = _seeds.Remove(_selfAddress); if (seeds.IsEmpty || seeds.Head() == _selfAddress) throw new ArgumentException("Join seed node should not be done"); Context.SetReceiveTimeout(Cluster.Get(Context.System).Settings.SeedNodeTimeout); @@ -2718,46 +2727,53 @@ protected override void PreStart() /// TBD protected override void OnReceive(object message) { - if (message is InternalClusterAction.JoinSeenNode) + switch (message) { - //send InitJoin to all seed nodes (except myself) - foreach (var path in _seeds.Where(x => x != _selfAddress) - .Select(y => Context.ActorSelection(Context.Parent.Path.ToStringWithAddress(y)))) + case InternalClusterAction.JoinSeenNode _: { - path.Tell(new InternalClusterAction.InitJoin()); + //send InitJoin to all seed nodes (except myself) + foreach (var path in _otherSeeds + .Select(y => Context.ActorSelection(Context.Parent.Path.ToStringWithAddress(y)))) + { + path.Tell(new InternalClusterAction.InitJoin()); + } + _attempts++; + break; } - _attempts++; - } - else if (message is InternalClusterAction.InitJoinAck) - { - //first InitJoinAck reply - var initJoinAck = (InternalClusterAction.InitJoinAck)message; - Context.Parent.Tell(new ClusterUserAction.JoinTo(initJoinAck.Address)); - Context.Become(Done); - } - else if (message is InternalClusterAction.InitJoinNack) { } //that seed was uninitialized - else if (message is ReceiveTimeout) - { - if (_attempts >= 2) - _log.Warning( - "Couldn't join seed nodes after [{0}] attempts, will try again. seed-nodes=[{1}]", - _attempts, string.Join(",", _seeds.Where(x => !x.Equals(_selfAddress)))); - //no InitJoinAck received - try again - Self.Tell(new InternalClusterAction.JoinSeenNode()); - } - else - { - Unhandled(message); + case InternalClusterAction.InitJoinAck initJoinAck: + //first InitJoinAck reply + Context.Parent.Tell(new ClusterUserAction.JoinTo(initJoinAck.Address)); + Context.Become(Done); + break; + case InternalClusterAction.InitJoinNack _: + break; //that seed was uninitialized + case ReceiveTimeout _: + { + if (_attempts >= 2) + _log.Warning( + "Couldn't join seed nodes after [{0}] attempts, will try again. seed-nodes=[{1}]", + _attempts, string.Join(",", _seeds.Where(x => !x.Equals(_selfAddress)))); + //no InitJoinAck received - try again + Self.Tell(new InternalClusterAction.JoinSeenNode()); + break; + } + default: + Unhandled(message); + break; } } private void Done(object message) { - if (message is InternalClusterAction.InitJoinAck) + switch (message) { - //already received one, skip the rest + case InternalClusterAction.InitJoinAck _: + //already received one, skip the rest + break; + case ReceiveTimeout _: + Context.Stop(Self); + break; } - else if (message is ReceiveTimeout) Context.Stop(Self); } } @@ -2777,6 +2793,7 @@ internal sealed class FirstSeedNodeProcess : UntypedActor { private readonly ILoggingAdapter _log = Context.GetLogger(); + private readonly ImmutableList
_seeds; private ImmutableList
_remainingSeeds; private readonly Address _selfAddress; private readonly Cluster _cluster; @@ -2784,9 +2801,9 @@ internal sealed class FirstSeedNodeProcess : UntypedActor private readonly ICancelable _retryTaskToken; /// - /// TBD + /// Launches a new instance of the "first seed node" joining process. /// - /// TBD + /// The set of seed nodes to join. /// /// This exception is thrown when either the number of specified is less than or equal to 1 /// or the first listed seed is a reference to the IUntypedActorContext.System's address. @@ -2799,63 +2816,63 @@ public FirstSeedNodeProcess(ImmutableList
seeds) if (seeds.Count <= 1 || seeds.Head() != _selfAddress) throw new ArgumentException("Join seed node should not be done"); + _seeds = seeds; _remainingSeeds = seeds.Remove(_selfAddress); _timeout = Deadline.Now + _cluster.Settings.SeedNodeTimeout; _retryTaskToken = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), Self, new InternalClusterAction.JoinSeenNode(), Self); Self.Tell(new InternalClusterAction.JoinSeenNode()); } - /// - /// TBD - /// protected override void PostStop() { _retryTaskToken.Cancel(); } - /// - /// TBD - /// - /// TBD protected override void OnReceive(object message) { - if (message is InternalClusterAction.JoinSeenNode) + switch (message) { - if (_timeout.HasTimeLeft) + case InternalClusterAction.JoinSeenNode _ when _timeout.HasTimeLeft: { // send InitJoin to remaining seed nodes (except myself) foreach (var seed in _remainingSeeds.Select( - x => Context.ActorSelection(Context.Parent.Path.ToStringWithAddress(x)))) + x => Context.ActorSelection(Context.Parent.Path.ToStringWithAddress(x)))) seed.Tell(new InternalClusterAction.InitJoin()); + break; } - else + case InternalClusterAction.JoinSeenNode _: { + if (_log.IsDebugEnabled) + { + _log.Debug("Couldn't join other seed nodes, will join myself. seed-nodes=[{0}]", string.Join(",", _seeds)); + } // no InitJoinAck received, initialize new cluster by joining myself Context.Parent.Tell(new ClusterUserAction.JoinTo(_selfAddress)); Context.Stop(Self); + break; } - } - else if (message is InternalClusterAction.InitJoinAck) - { - // first InitJoinAck reply, join existing cluster - var initJoinAck = (InternalClusterAction.InitJoinAck)message; - Context.Parent.Tell(new ClusterUserAction.JoinTo(initJoinAck.Address)); - Context.Stop(Self); - } - else if (message is InternalClusterAction.InitJoinNack) - { - var initJoinNack = (InternalClusterAction.InitJoinNack)message; - _remainingSeeds = _remainingSeeds.Remove(initJoinNack.Address); - if (_remainingSeeds.IsEmpty) - { - // initialize new cluster by joining myself when nacks from all other seed nodes - Context.Parent.Tell(new ClusterUserAction.JoinTo(_selfAddress)); + case InternalClusterAction.InitJoinAck initJoinAck: + _log.Info("Received InitJoinAck message from [{0}] to [{1}]", initJoinAck.Address, _selfAddress); + // first InitJoinAck reply, join existing cluster + Context.Parent.Tell(new ClusterUserAction.JoinTo(initJoinAck.Address)); Context.Stop(Self); + break; + case InternalClusterAction.InitJoinNack initJoinNack: + { + _log.Info("Received InitJoinNack message from [{0}] to [{1}]", initJoinNack.Address, _selfAddress); + _remainingSeeds = _remainingSeeds.Remove(initJoinNack.Address); + if (_remainingSeeds.IsEmpty) + { + // initialize new cluster by joining myself when nacks from all other seed nodes + Context.Parent.Tell(new ClusterUserAction.JoinTo(_selfAddress)); + Context.Stop(Self); + } + + break; } - } - else - { - Unhandled(message); + default: + Unhandled(message); + break; } } } diff --git a/src/core/Akka.Cluster/ClusterEvent.cs b/src/core/Akka.Cluster/ClusterEvent.cs index 5f9c9175ff5..3697b22b764 100644 --- a/src/core/Akka.Cluster/ClusterEvent.cs +++ b/src/core/Akka.Cluster/ClusterEvent.cs @@ -829,44 +829,37 @@ public override bool Equals(object obj) } } - /// - /// TBD - /// - /// TBD - /// TBD - /// TBD - /// TBD - internal static ImmutableList DiffUnreachable(Gossip oldGossip, Gossip newGossip, UniqueAddress selfUniqueAddress) - { - if (newGossip.Equals(oldGossip)) + /// + /// INTERNAL API + /// + internal static ImmutableList DiffUnreachable(MembershipState oldState, MembershipState newState) + { + if (ReferenceEquals(newState, oldState)) { return ImmutableList.Empty; } - var oldUnreachableNodes = oldGossip.Overview.Reachability.AllUnreachableOrTerminated; - return newGossip.Overview.Reachability.AllUnreachableOrTerminated - .Where(node => !oldUnreachableNodes.Contains(node) && !node.Equals(selfUniqueAddress)) - .Select(node => new UnreachableMember(newGossip.GetMember(node))) + var oldUnreachableNodes = oldState.Overview.Reachability.AllUnreachableOrTerminated; + return newState.Overview.Reachability.AllUnreachableOrTerminated + .Where(node => !oldUnreachableNodes.Contains(node) && !node.Equals(newState.SelfUniqueAddress)) + .Select(node => new UnreachableMember(newState.LatestGossip.GetMember(node))) .ToImmutableList(); } - /// - /// TBD - /// - /// TBD - /// TBD - /// TBD - /// TBD - internal static ImmutableList DiffReachable(Gossip oldGossip, Gossip newGossip, UniqueAddress selfUniqueAddress) + /// + /// INTERNAL API + /// + internal static ImmutableList DiffReachable(MembershipState oldState, MembershipState newState) { - if (newGossip.Equals(oldGossip)) + if (ReferenceEquals(newState, oldState)) { return ImmutableList.Empty; } - return oldGossip.Overview.Reachability.AllUnreachable - .Where(node => newGossip.HasMember(node) && newGossip.Overview.Reachability.IsReachable(node) && !node.Equals(selfUniqueAddress)) - .Select(node => new ReachableMember(newGossip.GetMember(node))) + return oldState.Overview.Reachability.AllUnreachable + .Where(node => newState.LatestGossip.HasMember(node) && newState.Overview.Reachability.IsReachable(node) + && !node.Equals(newState.SelfUniqueAddress)) + .Select(node => new ReachableMember(newState.LatestGossip.GetMember(node))) .ToImmutableList(); } @@ -874,19 +867,20 @@ internal static ImmutableList DiffReachable(Gossip oldGossip, G /// Compares two instances and uses them to publish the appropriate /// for any given change to the membership of the current cluster. ///
- /// The previous gossip instance. - /// The new gossip instance. + /// The previous gossip instance. + /// The new gossip instance. /// A possibly empty set of membership events to be published to all subscribers. - internal static ImmutableList DiffMemberEvents(Gossip oldGossip, Gossip newGossip) + internal static ImmutableList DiffMemberEvents(MembershipState oldState, MembershipState newState) { - if (newGossip.Equals(oldGossip)) + if (ReferenceEquals(newState, oldState)) { return ImmutableList.Empty; } - var newMembers = newGossip.Members.Except(oldGossip.Members); - var membersGroupedByAddress = newGossip.Members - .Concat(oldGossip.Members) + var newMembers = newState.Members.Except(oldState.Members); + + var membersGroupedByAddress = newState.Members + .Concat(oldState.Members) .GroupBy(m => m.UniqueAddress); var changedMembers = membersGroupedByAddress @@ -896,7 +890,7 @@ internal static ImmutableList DiffMemberEvents(Gossip oldGossip, G .Select(g => g.First()); var memberEvents = CollectMemberEvents(newMembers.Union(changedMembers)); - var removedMembers = oldGossip.Members.Except(newGossip.Members); + var removedMembers = oldState.Members.Except(newState.Members); var removedEvents = removedMembers.Select(m => new MemberRemoved(m.Copy(status: MemberStatus.Removed), m.Status)); return memberEvents.Concat(removedEvents).ToImmutableList(); @@ -931,70 +925,48 @@ private static IEnumerable CollectMemberEvents(IEnumerable } /// - /// TBD + /// INTERNAL API /// - /// TBD - /// TBD - /// TBD - /// TBD - internal static ImmutableList DiffLeader(Gossip oldGossip, Gossip newGossip, UniqueAddress selfUniqueAddress) + internal static ImmutableList DiffLeader(MembershipState oldState, MembershipState newState) { - var newLeader = newGossip.Leader(selfUniqueAddress); - if ((newLeader == null && oldGossip.Leader(selfUniqueAddress) == null) - || newLeader != null && newLeader.Equals(oldGossip.Leader(selfUniqueAddress))) + var newLeader = newState.Leader; + if (newLeader == oldState.Leader) return ImmutableList.Empty; - return ImmutableList.Create(newLeader == null - ? new LeaderChanged(null) - : new LeaderChanged(newLeader.Address)); + return ImmutableList.Create(new LeaderChanged(newLeader?.Address)); } /// - /// TBD + /// INTERNAL API /// - /// TBD - /// TBD - /// TBD - /// TBD - internal static ImmutableHashSet DiffRolesLeader(Gossip oldGossip, Gossip newGossip, UniqueAddress selfUniqueAddress) + internal static ImmutableHashSet DiffRolesLeader(MembershipState oldState, MembershipState newState) { - return InternalDiffRolesLeader(oldGossip, newGossip, selfUniqueAddress).ToImmutableHashSet(); + return InternalDiffRolesLeader(oldState, newState).ToImmutableHashSet(); } - private static IEnumerable InternalDiffRolesLeader(Gossip oldGossip, Gossip newGossip, UniqueAddress selfUniqueAddress) + private static IEnumerable InternalDiffRolesLeader(MembershipState oldState, MembershipState newState) { - foreach (var role in oldGossip.AllRoles.Union(newGossip.AllRoles)) + foreach (var role in oldState.LatestGossip.AllRoles.Union(newState.LatestGossip.AllRoles)) { - var newLeader = newGossip.RoleLeader(role, selfUniqueAddress); - if (newLeader == null && oldGossip.RoleLeader(role, selfUniqueAddress) != null) - yield return new RoleLeaderChanged(role, null); - if (newLeader != null && !newLeader.Equals(oldGossip.RoleLeader(role, selfUniqueAddress))) - yield return new RoleLeaderChanged(role, newLeader.Address); + var newLeader = newState.RoleLeader(role); + if (newLeader != oldState.RoleLeader(role)) + yield return new RoleLeaderChanged(role, newLeader?.Address); } } /// - /// Used for checking convergence when we don't have any information from the cluster daemon. - /// - private static readonly HashSet EmptySet = new HashSet(); - - /// - /// TBD + /// INTERNAL API /// - /// TBD - /// TBD - /// TBD - /// TBD - internal static ImmutableList DiffSeen(Gossip oldGossip, Gossip newGossip, UniqueAddress selfUniqueAddress) + internal static ImmutableList DiffSeen(MembershipState oldState, MembershipState newState) { - if (newGossip.Equals(oldGossip)) + if (ReferenceEquals(newState, oldState)) { return ImmutableList.Empty; } - var newConvergence = newGossip.Convergence(selfUniqueAddress, EmptySet); - var newSeenBy = newGossip.SeenBy; - if (!newConvergence.Equals(oldGossip.Convergence(selfUniqueAddress, EmptySet)) || !newSeenBy.SequenceEqual(oldGossip.SeenBy)) + var newConvergence = newState.Convergence(ImmutableHashSet.Empty); + var newSeenBy = newState.LatestGossip.SeenBy; + if (!newConvergence.Equals(oldState.Convergence(ImmutableHashSet.Empty)) || !newSeenBy.SequenceEqual(oldState.LatestGossip.SeenBy)) { return ImmutableList.Create(new SeenChanged(newConvergence, newSeenBy.Select(s => s.Address).ToImmutableHashSet())); } @@ -1003,17 +975,14 @@ internal static ImmutableList DiffSeen(Gossip oldGossip, Gossip new } /// - /// TBD + /// INTERNAL API /// - /// TBD - /// TBD - /// TBD - internal static ImmutableList DiffReachability(Gossip oldGossip, Gossip newGossip) + internal static ImmutableList DiffReachability(MembershipState oldState, MembershipState newState) { - if (newGossip.Overview.Reachability.Equals(oldGossip.Overview.Reachability)) + if (newState.Overview.Reachability.Equals(oldState.Overview.Reachability)) return ImmutableList.Empty; - return ImmutableList.Create(new ReachabilityChanged(newGossip.Overview.Reachability)); + return ImmutableList.Create(new ReachabilityChanged(newState.Overview.Reachability)); } } @@ -1024,18 +993,19 @@ internal static ImmutableList DiffReachability(Gossip oldGo ///
internal sealed class ClusterDomainEventPublisher : ReceiveActor, IRequiresMessageQueue { - private Gossip _latestGossip; private readonly UniqueAddress _selfUniqueAddress = Cluster.Get(Context.System).SelfUniqueAddress; + private readonly MembershipState _emptyMembershipState = new MembershipState(Gossip.Empty, Cluster.Get(Context.System).SelfUniqueAddress); + private MembershipState _membershipState; /// /// Default constructor for ClusterDomainEventPublisher. /// public ClusterDomainEventPublisher() { - _latestGossip = Gossip.Empty; _eventStream = Context.System.EventStream; + _membershipState = _emptyMembershipState; - Receive(newGossip => PublishChanges(newGossip.NewGossip)); + Receive(p => PublishChanges(p.NewState)); Receive(currentStats => PublishInternalStats(currentStats)); Receive(receiver => SendCurrentClusterState(receiver.Receiver)); Receive(sub => Subscribe(sub.Subscriber, sub.InitialStateMode, sub.To)); @@ -1054,7 +1024,7 @@ protected override void PostStop() { // publish the final removed state before shutting down Publish(ClusterEvent.ClusterShuttingDown.Instance); - PublishChanges(Gossip.Empty); + PublishChanges(_emptyMembershipState); } private readonly EventStream _eventStream; @@ -1065,21 +1035,22 @@ protected override void PostStop() ///
private void SendCurrentClusterState(IActorRef receiver) { - var unreachable = _latestGossip.Overview.Reachability.AllUnreachableOrTerminated - .Where(node => !node.Equals(_selfUniqueAddress)) - .Select(node => _latestGossip.GetMember(node)) + var unreachable = _membershipState.LatestGossip.Overview.Reachability + .AllUnreachableOrTerminated.Where(x => x != _selfUniqueAddress) + .Select(x => _membershipState.LatestGossip.GetMember(x)) .ToImmutableHashSet(); var state = new ClusterEvent.CurrentClusterState( - members: _latestGossip.Members, + members: _membershipState.Members, unreachable: unreachable, - seenBy: _latestGossip.SeenBy.Select(s => s.Address).ToImmutableHashSet(), - leader: _latestGossip.Leader(_selfUniqueAddress) == null ? null : _latestGossip.Leader(_selfUniqueAddress).Address, - roleLeaderMap: _latestGossip.AllRoles.ToImmutableDictionary(r => r, r => - { - var leader = _latestGossip.RoleLeader(r, _selfUniqueAddress); - return leader == null ? null : leader.Address; - })); + seenBy: _membershipState.LatestGossip.SeenBy.Select(s => s.Address).ToImmutableHashSet(), + leader: _membershipState.Leader?.Address, + roleLeaderMap: _membershipState.LatestGossip.AllRoles + .ToImmutableDictionary(r => r, r => + { + var leader = _membershipState.RoleLeader(r); + return leader?.Address; + })); receiver.Tell(state); } @@ -1093,7 +1064,7 @@ private void Subscribe(IActorRef subscriber, ClusterEvent.SubscriptionInitialSta if (to.Any(o => o.IsAssignableFrom(eventType))) subscriber.Tell(@event); }; - PublishDiff(Gossip.Empty, _latestGossip, pub); + PublishDiff(_emptyMembershipState, _membershipState, pub); } else if (initMode == ClusterEvent.SubscriptionInitialStateMode.InitialStateAsSnapshot) { @@ -1109,24 +1080,24 @@ private void Unsubscribe(IActorRef subscriber, Type to) else _eventStream.Unsubscribe(subscriber, to); } - private void PublishChanges(Gossip newGossip) + private void PublishChanges(MembershipState newState) { - var oldGossip = _latestGossip; - // keep the _latestGossip to be sent to new subscribers - _latestGossip = newGossip; - PublishDiff(oldGossip, newGossip, Publish); + var oldState = _membershipState; + // keep the latest state to be sent to new subscribers + _membershipState = newState; + PublishDiff(oldState, newState, Publish); } - private void PublishDiff(Gossip oldGossip, Gossip newGossip, Action pub) + private void PublishDiff(MembershipState oldState, MembershipState newState, Action pub) { - foreach (var @event in ClusterEvent.DiffMemberEvents(oldGossip, newGossip)) pub(@event); - foreach (var @event in ClusterEvent.DiffUnreachable(oldGossip, newGossip, _selfUniqueAddress)) pub(@event); - foreach (var @event in ClusterEvent.DiffReachable(oldGossip, newGossip, _selfUniqueAddress)) pub(@event); - foreach (var @event in ClusterEvent.DiffLeader(oldGossip, newGossip, _selfUniqueAddress)) pub(@event); - foreach (var @event in ClusterEvent.DiffRolesLeader(oldGossip, newGossip, _selfUniqueAddress)) pub(@event); + foreach (var @event in ClusterEvent.DiffMemberEvents(oldState, newState)) pub(@event); + foreach (var @event in ClusterEvent.DiffUnreachable(oldState, newState)) pub(@event); + foreach (var @event in ClusterEvent.DiffReachable(oldState, newState)) pub(@event); + foreach (var @event in ClusterEvent.DiffLeader(oldState, newState)) pub(@event); + foreach (var @event in ClusterEvent.DiffRolesLeader(oldState, newState)) pub(@event); // publish internal SeenState for testing purposes - foreach (var @event in ClusterEvent.DiffSeen(oldGossip, newGossip, _selfUniqueAddress)) pub(@event); - foreach (var @event in ClusterEvent.DiffReachability(oldGossip, newGossip)) pub(@event); + foreach (var @event in ClusterEvent.DiffSeen(oldState, newState)) pub(@event); + foreach (var @event in ClusterEvent.DiffReachability(oldState, newState)) pub(@event); } private void PublishInternalStats(ClusterEvent.CurrentInternalStats currentStats) @@ -1141,7 +1112,7 @@ private void Publish(object @event) private void ClearState() { - _latestGossip = Gossip.Empty; + _membershipState = _emptyMembershipState; } } } diff --git a/src/core/Akka.Cluster/ClusterHeartbeat.cs b/src/core/Akka.Cluster/ClusterHeartbeat.cs index f4a8ef607f0..0ed134262fa 100644 --- a/src/core/Akka.Cluster/ClusterHeartbeat.cs +++ b/src/core/Akka.Cluster/ClusterHeartbeat.cs @@ -12,6 +12,7 @@ using Akka.Actor; using Akka.Event; using Akka.Remote; +using Akka.Util; using Akka.Util.Internal; namespace Akka.Cluster @@ -21,33 +22,53 @@ namespace Akka.Cluster /// /// Receives messages and replies. /// - internal sealed class ClusterHeartbeatReceiver : ReceiveActor + internal sealed class ClusterHeartbeatReceiver : UntypedActor { - private readonly Lazy _selfHeartbeatRsp; + // Important - don't use Cluster.Get(Context.System) in constructor because that would + // cause deadlock. See startup sequence in ClusterDaemon. + private readonly Lazy _cluster; + + public bool VerboseHeartbeat => _cluster.Value.Settings.VerboseHeartbeatLogging; /// /// TBD /// - public ClusterHeartbeatReceiver() + public ClusterHeartbeatReceiver(Func getCluster) + { + _cluster = new Lazy(getCluster); + } + + protected override void OnReceive(object message) { - // Important - don't use Cluster.Get(Context.System) in constructor because that would - // cause deadlock. See startup sequence in ClusterDaemon. - _selfHeartbeatRsp = new Lazy(() => - new ClusterHeartbeatSender.HeartbeatRsp(Cluster.Get(Context.System).SelfUniqueAddress)); + switch (message) + { + case ClusterHeartbeatSender.Heartbeat hb: + // TODO log the sequence nr once serializer is enabled + if(VerboseHeartbeat) _cluster.Value.CurrentInfoLogger.LogDebug("Heartbeat from [{0}]", hb.From); + Sender.Tell(new ClusterHeartbeatSender.HeartbeatRsp(_cluster.Value.SelfUniqueAddress, + hb.SequenceNr, hb.CreationTimeNanos)); + break; + default: + Unhandled(message); + break; + } + } - Receive(heartbeat => Sender.Tell(_selfHeartbeatRsp.Value)); + public static Props Props(Func getCluster) + { + return Akka.Actor.Props.Create(() => new ClusterHeartbeatReceiver(getCluster)); } + } /// /// INTERNAL API /// - internal sealed class ClusterHeartbeatSender : ReceiveActor + internal class ClusterHeartbeatSender : ReceiveActor { private readonly ILoggingAdapter _log = Context.GetLogger(); private readonly Cluster _cluster; private readonly IFailureDetectorRegistry
_failureDetector; - private readonly Heartbeat _selfHeartbeat; private ClusterHeartbeatSenderState _state; private readonly ICancelable _heartbeatTask; @@ -66,8 +87,6 @@ public ClusterHeartbeatSender() // the failureDetector is only updated by this actor, but read from other places _failureDetector = _cluster.FailureDetector; - _selfHeartbeat = new Heartbeat(_cluster.SelfAddress); - _state = new ClusterHeartbeatSenderState( ring: new HeartbeatNodeRing( _cluster.SelfUniqueAddress, @@ -88,6 +107,13 @@ public ClusterHeartbeatSender() Initializing(); } + private long _seqNo; + private Heartbeat SelfHeartbeat() + { + _seqNo += 1; + return new Heartbeat(_cluster.SelfAddress, _seqNo, MonotonicClock.GetNanos()); + } + /// /// TBD /// @@ -112,7 +138,7 @@ protected override void PostStop() /// /// Looks up and returns the remote cluster heartbeat connection for the specific address. /// - private ActorSelection HeartbeatReceiver(Address address) + protected virtual ActorSelection HeartbeatReceiver(Address address) { return Context.ActorSelection(new RootActorPath(address) / "system" / "cluster" / "heartbeatReceiver"); } @@ -133,7 +159,7 @@ private void Initializing() private void Active() { Receive(tick => DoHeartbeat()); - Receive(rsp => DoHeartbeatRsp(rsp.From)); + Receive(rsp => DoHeartbeatRsp(rsp)); Receive(removed => RemoveMember(removed.Member)); Receive(evt => AddMember(evt.Member)); Receive(m => UnreachableMember(m.Member)); @@ -204,7 +230,7 @@ private void DoHeartbeat() new ExpectedFirstHeartbeat(to), Self); } - HeartbeatReceiver(to.Address).Tell(_selfHeartbeat); + HeartbeatReceiver(to.Address).Tell(SelfHeartbeat()); } CheckTickInterval(); @@ -227,13 +253,14 @@ private void CheckTickInterval() _tickTimestamp = DateTime.UtcNow; } - private void DoHeartbeatRsp(UniqueAddress from) + private void DoHeartbeatRsp(HeartbeatRsp rsp) { if (_cluster.Settings.VerboseHeartbeatLogging) { - _log.Debug("Cluster Node [{0}] - Heartbeat response from [{1}]", _cluster.SelfAddress, from.Address); + // TODO: log response time and validate sequence nrs once serialisation of sendTime is released + _log.Debug("Cluster Node [{0}] - Heartbeat response from [{1}]", _cluster.SelfAddress, rsp.From.Address); } - _state = _state.HeartbeatRsp(from); + _state = _state.HeartbeatRsp(rsp.From); } private void TriggerFirstHeart(UniqueAddress from) @@ -253,70 +280,85 @@ private void TriggerFirstHeart(UniqueAddress from) /// /// Sent at regular intervals for failure detection /// - internal sealed class Heartbeat : IClusterMessage, IPriorityMessage, IDeadLetterSuppression + internal sealed class Heartbeat : IClusterMessage, IPriorityMessage, IDeadLetterSuppression, IEquatable { - /// - /// TBD - /// - /// TBD - public Heartbeat(Address from) + public Heartbeat(Address from, long sequenceNr, long creationTimeNanos) { From = from; + SequenceNr = sequenceNr; + CreationTimeNanos = creationTimeNanos; } - /// - /// TBD - /// public Address From { get; } -#pragma warning disable 659 //there might very well be multiple heartbeats from the same address. overriding GetHashCode may have uninteded side effects - /// + public long SequenceNr { get; } + + public long CreationTimeNanos { get; } + + public bool Equals(Heartbeat other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return From.Equals(other.From) && SequenceNr == other.SequenceNr && CreationTimeNanos == other.CreationTimeNanos; + } + public override bool Equals(object obj) -#pragma warning restore 659 { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj is Heartbeat && Equals((Heartbeat)obj); + return ReferenceEquals(this, obj) || obj is Heartbeat other && Equals(other); } - private bool Equals(Heartbeat other) + public override int GetHashCode() { - return Equals(From, other.From); + unchecked + { + var hashCode = From.GetHashCode(); + hashCode = (hashCode * 397) ^ SequenceNr.GetHashCode(); + hashCode = (hashCode * 397) ^ CreationTimeNanos.GetHashCode(); + return hashCode; + } } } /// /// Sends replies to messages /// - internal sealed class HeartbeatRsp : IClusterMessage, IPriorityMessage, IDeadLetterSuppression + internal sealed class HeartbeatRsp : IClusterMessage, IPriorityMessage, IDeadLetterSuppression, IEquatable { - /// - /// TBD - /// - /// TBD - public HeartbeatRsp(UniqueAddress from) + public HeartbeatRsp(UniqueAddress from, long sequenceNr, long creationTimeNanos) { From = from; + SequenceNr = sequenceNr; + CreationTimeNanos = creationTimeNanos; } - /// - /// TBD - /// public UniqueAddress From { get; } -#pragma warning disable 659 //there might very well be multiple heartbeats from the same address. overriding GetHashCode may have uninteded side effects - /// + public long SequenceNr { get; } + + public long CreationTimeNanos { get; } + + public bool Equals(HeartbeatRsp other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return From.Equals(other.From) && SequenceNr == other.SequenceNr + && CreationTimeNanos == other.CreationTimeNanos; + } + public override bool Equals(object obj) -#pragma warning restore 659 { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj is HeartbeatRsp && Equals((HeartbeatRsp)obj); + return ReferenceEquals(this, obj) || obj is HeartbeatRsp other && Equals(other); } - private bool Equals(HeartbeatRsp other) + public override int GetHashCode() { - return Equals(From, other.From); + unchecked + { + var hashCode = From.GetHashCode(); + hashCode = (hashCode * 397) ^ SequenceNr.GetHashCode(); + hashCode = (hashCode * 397) ^ CreationTimeNanos.GetHashCode(); + return hashCode; + } } } @@ -387,7 +429,7 @@ public ClusterHeartbeatSenderState(HeartbeatNodeRing ring, ImmutableHashSet /// TBD /// - public readonly ImmutableHashSet ActiveReceivers; + public readonly IImmutableSet ActiveReceivers; /// /// TBD @@ -468,7 +510,9 @@ private ClusterHeartbeatSenderState MembershipChange(HeartbeatNodeRing newRing) foreach (var r in removedReceivers) { if (FailureDetector.IsAvailable(r.Address)) + { FailureDetector.Remove(r.Address); + } else { adjustedOldReceiversNowUnreachable = adjustedOldReceiversNowUnreachable.Add(r); @@ -508,7 +552,7 @@ public ClusterHeartbeatSenderState HeartbeatRsp(UniqueAddress from) /// TBD /// TBD /// TBD - public ClusterHeartbeatSenderState Copy(HeartbeatNodeRing ring = null, ImmutableHashSet oldReceiversNowUnreachable = null, IFailureDetectorRegistry
failureDetector = null) + public ClusterHeartbeatSenderState Copy(HeartbeatNodeRing? ring = null, ImmutableHashSet oldReceiversNowUnreachable = null, IFailureDetectorRegistry
failureDetector = null) { return new ClusterHeartbeatSenderState(ring ?? Ring, oldReceiversNowUnreachable ?? OldReceiversNowUnreachable, failureDetector ?? FailureDetector); } @@ -522,9 +566,10 @@ public ClusterHeartbeatSenderState Copy(HeartbeatNodeRing ring = null, Immutable /// /// It is immutable, i.e. the methods all return new instances. ///
- internal sealed class HeartbeatNodeRing + internal struct HeartbeatNodeRing { private readonly bool _useAllAsReceivers; + private Option> _myReceivers; /// /// TBD @@ -544,14 +589,15 @@ public HeartbeatNodeRing( { SelfAddress = selfAddress; Nodes = nodes; + NodeRing = nodes.ToImmutableSortedSet(RingComparer.Instance); Unreachable = unreachable; MonitoredByNumberOfNodes = monitoredByNumberOfNodes; if (!nodes.Contains(selfAddress)) throw new ArgumentException($"Nodes [${string.Join(", ", nodes)}] must contain selfAddress [{selfAddress}]"); - _useAllAsReceivers = MonitoredByNumberOfNodes >= (NodeRing().Count - 1); - MyReceivers = new Lazy>(() => Receivers(SelfAddress)); + _useAllAsReceivers = MonitoredByNumberOfNodes >= (NodeRing.Count - 1); + _myReceivers = Option>.None; } /// @@ -574,26 +620,34 @@ public HeartbeatNodeRing( /// public int MonitoredByNumberOfNodes { get; } - private ImmutableSortedSet NodeRing() - { - return Nodes.ToImmutableSortedSet(RingComparer.Instance); - } + public ImmutableSortedSet NodeRing { get; } /// /// Receivers for . Cached for subsequent access. /// - public readonly Lazy> MyReceivers; + public Option> MyReceivers + { + get + { + if (_myReceivers.IsEmpty) + { + _myReceivers = new Option>(Receivers(SelfAddress)); + } + + return _myReceivers; + } + } /// - /// TBD + /// The set of Akka.Cluster nodes designated for receiving heartbeats from this node. /// - /// TBD - /// TBD - public ImmutableHashSet Receivers(UniqueAddress sender) + /// The node sending heartbeats. + /// An organized ring of unique nodes. + public IImmutableSet Receivers(UniqueAddress sender) { if (_useAllAsReceivers) { - return NodeRing().Remove(sender).ToImmutableHashSet(); + return NodeRing.Remove(sender); } else { @@ -602,41 +656,42 @@ public ImmutableHashSet Receivers(UniqueAddress sender) // The reason for not limiting it to strictly monitoredByNrOfMembers is that the leader must // be able to continue its duties (e.g. removal of downed nodes) when many nodes are shutdown // at the same time and nobody in the remaining cluster is monitoring some of the shutdown nodes. - Func, ImmutableSortedSet, (int, ImmutableSortedSet)> take = null; - take = (n, iter, acc) => + (int, ImmutableSortedSet) Take(int n, IEnumerator iter, ImmutableSortedSet acc, ImmutableHashSet unreachable, int monitoredByNumberOfNodes) { - if (iter.MoveNext() == false || n == 0) - { - return (n, acc); - } - else + while (true) { - UniqueAddress next = iter.Current; - var isUnreachable = Unreachable.Contains(next); - if (isUnreachable && acc.Count >= MonitoredByNumberOfNodes) + if (iter.MoveNext() == false || n == 0) { - return take(n, iter, acc); // skip the unreachable, since we have already picked `MonitoredByNumberOfNodes` - } - else if (isUnreachable) - { - return take(n, iter, acc.Add(next)); // include the unreachable, but don't count it + iter.Dispose(); // dispose enumerator + return (n, acc); } else { - return take(n - 1, iter, acc.Add(next)); // include the reachable + var next = iter.Current; + var isUnreachable = unreachable.Contains(next); + if (isUnreachable && acc.Count >= monitoredByNumberOfNodes) + { + } + else if (isUnreachable) + { + acc = acc.Add(next); + } + else + { + n = n - 1; + acc = acc.Add(next); + } } } - }; + } - var tuple = take(MonitoredByNumberOfNodes, NodeRing().From(sender).Skip(1).GetEnumerator(), ImmutableSortedSet.Empty); - var remaining = tuple.Item1; - var slice1 = tuple.Item2; + var (remaining, slice1) = Take(MonitoredByNumberOfNodes, NodeRing.From(sender).Skip(1).GetEnumerator(), ImmutableSortedSet.Empty, Unreachable, MonitoredByNumberOfNodes); IImmutableSet slice = remaining == 0 - ? slice1 - : take(remaining, NodeRing().Until(sender).Where(c => !c.Equals(sender)).GetEnumerator(), slice1).Item2; + ? slice1 // or, wrap-around + : Take(remaining, NodeRing.TakeWhile(x => x != sender).GetEnumerator(), slice1, Unreachable, MonitoredByNumberOfNodes).Item2; - return slice.ToImmutableHashSet(); + return slice; } } @@ -654,7 +709,7 @@ public HeartbeatNodeRing Copy(UniqueAddress selfAddress = null, ImmutableHashSet selfAddress ?? SelfAddress, nodes ?? Nodes, unreachable ?? Unreachable, - monitoredByNumberOfNodes.HasValue ? monitoredByNumberOfNodes.Value : MonitoredByNumberOfNodes); + monitoredByNumberOfNodes ?? MonitoredByNumberOfNodes); } #region Operators @@ -687,7 +742,9 @@ public HeartbeatNodeRing Copy(UniqueAddress selfAddress = null, ImmutableHashSet #region Comparer /// - /// TBD + /// Data structure for picking heartbeat receivers. The node ring is + /// shuffled by deterministic hashing to avoid picking physically co-located + /// neighbors. /// internal class RingComparer : IComparer { @@ -700,12 +757,10 @@ private RingComparer() { } /// public int Compare(UniqueAddress x, UniqueAddress y) { - var result = Member.AddressOrdering.Compare(x.Address, y.Address); - if (result == 0) - if (x.Uid < y.Uid) return -1; - else if (x.Uid == y.Uid) return 0; - else return 1; - return result; + var ha = x.Uid; + var hb = y.Uid; + var c = ha.CompareTo(hb); + return c == 0 ? Member.AddressOrdering.Compare(x.Address, y.Address) : c; } } #endregion diff --git a/src/core/Akka.Cluster/ClusterSettings.cs b/src/core/Akka.Cluster/ClusterSettings.cs index 06d16e348fb..2894cd50c5d 100644 --- a/src/core/Akka.Cluster/ClusterSettings.cs +++ b/src/core/Akka.Cluster/ClusterSettings.cs @@ -95,7 +95,29 @@ public ClusterSettings(Config config, string systemName) DowningProviderType = typeof(NoDowning); RunCoordinatedShutdownWhenDown = clusterConfig.GetBoolean("run-coordinated-shutdown-when-down", false); - AllowWeaklyUpMembers = clusterConfig.GetBoolean("allow-weakly-up-members", false); + + // TODO: replace with a switch expression when we upgrade to C#8 or later + TimeSpan GetWeaklyUpDuration() + { + var cKey = "allow-weakly-up-members"; + switch (clusterConfig.GetString(cKey, string.Empty) + .ToLowerInvariant()) + { + case "off": + return TimeSpan.Zero; + case "on": + + return TimeSpan.FromSeconds(7); // for backwards compatibility when it wasn't a duration + default: + var val = clusterConfig.GetTimeSpan(cKey, TimeSpan.FromSeconds(7)); + if(!(val > TimeSpan.Zero)) + throw new ConfigurationException($"Valid settings for [akka.cluster.{cKey}] are 'off', 'on', or a timespan greater than 0s. Received [{val}]"); + return val; + } + } + + WeaklyUpAfter = GetWeaklyUpDuration(); + } /// @@ -263,12 +285,21 @@ public ClusterSettings(Config config, string systemName) /// /// If this is set to "off", the leader will not move members to during a network /// split. This feature allows the leader to accept members to be - /// so they become part of the cluster even during a network split. The leader will - /// move members to after 3 rounds of 'leader-actions-interval' - /// without convergence. + /// so they become part of the cluster even during a network split. + /// + /// The leader will move members to status once convergence has been reached. + /// + public bool AllowWeaklyUpMembers => WeaklyUpAfter != TimeSpan.Zero; + + /// + /// The duration after which a member who is currently will be marked as + /// in the event that members of the cluster are currently unreachable. + /// + /// This is designed to allow new cluster members to perform work even in the event of a cluster split. + /// /// The leader will move members to status once convergence has been reached. /// - public bool AllowWeaklyUpMembers { get; } + public TimeSpan WeaklyUpAfter { get; } } } diff --git a/src/core/Akka.Cluster/Configuration/Cluster.conf b/src/core/Akka.Cluster/Configuration/Cluster.conf index d496c4f5064..96b9fa3ddd3 100644 --- a/src/core/Akka.Cluster/Configuration/Cluster.conf +++ b/src/core/Akka.Cluster/Configuration/Cluster.conf @@ -66,10 +66,9 @@ akka { # If this is set to "off", the leader will not move 'Joining' members to 'Up' during a network # split. This feature allows the leader to accept 'Joining' members to be 'WeaklyUp' # so they become part of the cluster even during a network split. The leader will - # move `Joining` members to 'WeaklyUp' after 3 rounds of 'leader-actions-interval' - # without convergence. + # move `Joining` members to 'WeaklyUp' after this configured duration without convergence. # The leader will move 'WeaklyUp' members to 'Up' status once convergence has been reached. - allow-weakly-up-members = on + allow-weakly-up-members = 7s # The roles of this member. List of strings, e.g. roles = ["A", "B"]. # The roles are part of the membership information and can be used by diff --git a/src/core/Akka.Cluster/Gossip.cs b/src/core/Akka.Cluster/Gossip.cs index fb5bd82be01..09104aef329 100644 --- a/src/core/Akka.Cluster/Gossip.cs +++ b/src/core/Akka.Cluster/Gossip.cs @@ -68,24 +68,6 @@ public static Gossip Create(ImmutableSortedSet members) return Empty.Copy(members: members); } - private static readonly ImmutableHashSet LeaderMemberStatus = - ImmutableHashSet.Create(MemberStatus.Up, MemberStatus.Leaving); - - private static readonly ImmutableHashSet ConvergenceMemberStatus = - ImmutableHashSet.Create(MemberStatus.Up, MemberStatus.Leaving); - - /// - /// If there are unreachable members in the cluster with any of these statuses, they will be skipped during convergence checks. - /// - public static readonly ImmutableHashSet ConvergenceSkipUnreachableWithMemberStatus = - ImmutableHashSet.Create(MemberStatus.Down, MemberStatus.Exiting); - - /// - /// If there are unreachable members in the cluster with any of these statuses, they will be pruned from the local gossip - /// - public static readonly ImmutableHashSet RemoveUnreachableWithMemberStatus = - ImmutableHashSet.Create(MemberStatus.Down, MemberStatus.Exiting); - readonly ImmutableSortedSet _members; readonly GossipOverview _overview; readonly VectorClock _version; @@ -134,7 +116,7 @@ public Gossip(ImmutableSortedSet members, GossipOverview overview, Vecto ReachabilityExcludingDownedObservers = new Lazy(() => { - var downed = Members.Where(m => m.Status == MemberStatus.Down).ToList(); + var downed = Members.Where(m => m.Status == MemberStatus.Down); return Overview.Reachability.RemoveObservers(downed.Select(m => m.UniqueAddress).ToImmutableHashSet()); }); @@ -156,25 +138,33 @@ public Gossip Copy(ImmutableSortedSet members = null, GossipOverview ove private void AssertInvariants() { - if (_members.Any(m => m.Status == MemberStatus.Removed)) + void IfTrueThrow(bool func, string expected, string actual) { - var members = string.Join(", ", _members.Where(m => m.Status == MemberStatus.Removed).Select(m => m.ToString())); - throw new ArgumentException($"Live members must not have status [Removed], got {members}", nameof(_members)); + if (func) throw new ArgumentException($"{expected}, but found [{actual}]"); } + + IfTrueThrow(_members.Any(m => m.Status == MemberStatus.Removed), + expected: "Live members must not have status [Removed]", + actual: string.Join(", ", + _members.Where(m => m.Status == MemberStatus.Removed).Select(m => m.ToString()))); + + var inReachabilityButNotMember = _overview.Reachability.AllObservers.Except(_members.Select(m => m.UniqueAddress)); - if (!inReachabilityButNotMember.IsEmpty) - { - var inreachability = string.Join(", ", inReachabilityButNotMember.Select(a => a.ToString())); - throw new ArgumentException($"Nodes not part of cluster in reachability table, got {inreachability}", nameof(_overview)); - } + IfTrueThrow(!inReachabilityButNotMember.IsEmpty, + expected: "Nodes not part of cluster in reachability table", + actual: string.Join(", ", inReachabilityButNotMember.Select(a => a.ToString()))); + + var inReachabilityVersionsButNotMember = + _overview.Reachability.Versions.Keys.Except(Members.Select(x => x.UniqueAddress)).ToImmutableHashSet(); + IfTrueThrow(!inReachabilityVersionsButNotMember.IsEmpty, + expected: "Nodes not part of cluster in reachability versions table", + actual: string.Join(", ", inReachabilityVersionsButNotMember.Select(a => a.ToString()))); var seenButNotMember = _overview.Seen.Except(_members.Select(m => m.UniqueAddress)); - if (!seenButNotMember.IsEmpty) - { - var seen = string.Join(", ", seenButNotMember.Select(a => a.ToString())); - throw new ArgumentException($"Nodes not part of cluster have marked the Gossip as seen, got {seen}", nameof(_overview)); - } + IfTrueThrow(!seenButNotMember.IsEmpty, + expected: "Nodes not part of cluster have marked the Gossip as seen", + actual: string.Join(", ", seenButNotMember.Select(a => a.ToString()))); } //TODO: Serializer should ignore @@ -274,7 +264,7 @@ public Gossip Merge(Gossip that) var mergedMembers = EmptyMembers.Union(Member.PickHighestPriority(this._members, that._members)); // 3. merge reachability table by picking records with highest version - var mergedReachability = this._overview.Reachability.Merge(mergedMembers.Select(m => m.UniqueAddress), + var mergedReachability = _overview.Reachability.Merge(mergedMembers.Select(m => m.UniqueAddress).ToImmutableSortedSet(), that._overview.Reachability); // 4. Nobody can have seen this new gossip yet @@ -283,92 +273,11 @@ public Gossip Merge(Gossip that) return new Gossip(mergedMembers, new GossipOverview(mergedSeen, mergedReachability), mergedVClock); } - - /// - /// First check that: - /// 1. we don't have any members that are unreachable, or - /// 2. all unreachable members in the set have status DOWN or EXITING - /// Else we can't continue to check for convergence. When that is done - /// we check that all members with a convergence status is in the seen - /// table and has the latest vector clock version. - /// - /// The unique address of the node checking for convergence. - /// The set of nodes who have been confirmed to be exiting. - /// true if convergence has been achieved. false otherwise. - public bool Convergence(UniqueAddress selfUniqueAddress, HashSet exitingConfirmed) - { - var unreachable = ReachabilityExcludingDownedObservers.Value.AllUnreachableOrTerminated - .Where(node => node != selfUniqueAddress && !exitingConfirmed.Contains(node)) - .Select(GetMember); - - return unreachable.All(m => ConvergenceSkipUnreachableWithMemberStatus.Contains(m.Status)) - && !_members.Any(m => ConvergenceMemberStatus.Contains(m.Status) - && !(SeenByNode(m.UniqueAddress) || exitingConfirmed.Contains(m.UniqueAddress))); - } - /// /// TBD /// public Lazy ReachabilityExcludingDownedObservers { get; } - /// - /// TBD - /// - /// TBD - /// TBD - /// TBD - public bool IsLeader(UniqueAddress node, UniqueAddress selfUniqueAddress) - { - return Leader(selfUniqueAddress) == node && node != null; - } - - /// - /// TBD - /// - /// TBD - /// TBD - public UniqueAddress Leader(UniqueAddress selfUniqueAddress) - { - return LeaderOf(_members, selfUniqueAddress); - } - - /// - /// TBD - /// - /// TBD - /// TBD - /// TBD - public UniqueAddress RoleLeader(string role, UniqueAddress selfUniqueAddress) - { - var roleMembers = _members - .Where(m => m.HasRole(role)) - .ToImmutableSortedSet(); - - return LeaderOf(roleMembers, selfUniqueAddress); - } - - /// - /// Determine which node is the leader of the given range of members. - /// - /// All members in the cluster. - /// The address of the current node. - /// null if is empty. The of the leader otherwise. - public UniqueAddress LeaderOf(ImmutableSortedSet mbrs, UniqueAddress selfUniqueAddress) - { - var reachableMembers = (_overview.Reachability.IsAllReachable - ? mbrs.Where(m => m.Status != MemberStatus.Down) - : mbrs - .Where(m => m.Status != MemberStatus.Down && _overview.Reachability.IsReachable(m.UniqueAddress) || m.UniqueAddress == selfUniqueAddress)) - .ToImmutableSortedSet(); - - if (!reachableMembers.Any()) return null; - - var member = reachableMembers.FirstOrDefault(m => LeaderMemberStatus.Contains(m.Status)) ?? - reachableMembers.Min(Member.LeaderStatusOrdering); - - return member.UniqueAddress; - } - /// /// TBD /// @@ -437,6 +346,18 @@ public Gossip Prune(VectorClock.Node removedNode) return new Gossip(Members, Overview, newVersion); } + public Gossip MarkAsDown(Member member) + { + // replace member (changed status) + var newMembers = Members.Remove(member).Add(member.Copy(MemberStatus.Down)); + // remove nodes marked as DOWN from the 'seen' table + var newSeen = Overview.Seen.Remove(member.UniqueAddress); + + //update gossip overview + var newOverview = Overview.Copy(seen: newSeen); + return Copy(newMembers, overview: newOverview); + } + /// public override string ToString() { @@ -448,7 +369,7 @@ public override string ToString() /// /// Represents the overview of the cluster, holds the cluster convergence table and set with unreachable nodes. /// - class GossipOverview + internal class GossipOverview { readonly ImmutableHashSet _seen; readonly Reachability _reachability; @@ -508,9 +429,6 @@ public GossipOverview Copy(ImmutableHashSet seen = null, Reachabi /// class GossipEnvelope : IClusterMessage { - //TODO: Serialization? - //TODO: ser stuff? - readonly UniqueAddress _from; readonly UniqueAddress _to; diff --git a/src/core/Akka.Cluster/Member.cs b/src/core/Akka.Cluster/Member.cs index d5d91d04b82..047580c7ec2 100644 --- a/src/core/Akka.Cluster/Member.cs +++ b/src/core/Akka.Cluster/Member.cs @@ -370,7 +370,7 @@ public static ImmutableHashSet PickHighestPriority(IEnumerable a else { var m = g.First(); - if (!Gossip.RemoveUnreachableWithMemberStatus.Contains(m.Status)) acc.Add(m); + if (!MembershipState.RemoveUnreachableWithMemberStatus.Contains(m.Status)) acc.Add(m); } } return acc.ToImmutableHashSet(); @@ -502,7 +502,7 @@ public bool Equals(UniqueAddress other) } /// - public override bool Equals(object obj) => obj is UniqueAddress && Equals((UniqueAddress)obj); + public override bool Equals(object obj) => obj is UniqueAddress address && Equals(address); /// public override int GetHashCode() diff --git a/src/core/Akka.Cluster/MembershipState.cs b/src/core/Akka.Cluster/MembershipState.cs new file mode 100644 index 00000000000..0b70aad5495 --- /dev/null +++ b/src/core/Akka.Cluster/MembershipState.cs @@ -0,0 +1,209 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Collections.Immutable; +using System.Linq; +using Akka.Annotations; +using Akka.Util; + +namespace Akka.Cluster +{ + /// + /// INTERNAL API + /// + [InternalApi] + internal sealed class MembershipState : IEquatable + { + private static readonly ImmutableHashSet LeaderMemberStatus = + ImmutableHashSet.Create(MemberStatus.Up, MemberStatus.Leaving); + + private static readonly ImmutableHashSet ConvergenceMemberStatus = + ImmutableHashSet.Create(MemberStatus.Up, MemberStatus.Leaving); + + /// + /// If there are unreachable members in the cluster with any of these statuses, they will be skipped during convergence checks. + /// + public static readonly ImmutableHashSet ConvergenceSkipUnreachableWithMemberStatus = + ImmutableHashSet.Create(MemberStatus.Down, MemberStatus.Exiting); + + /// + /// If there are unreachable members in the cluster with any of these statuses, they will be pruned from the local gossip + /// + public static readonly ImmutableHashSet RemoveUnreachableWithMemberStatus = + ImmutableHashSet.Create(MemberStatus.Down, MemberStatus.Exiting); + + public MembershipState(Gossip latestGossip, UniqueAddress selfUniqueAddress) + { + LatestGossip = latestGossip; + SelfUniqueAddress = selfUniqueAddress; + } + + private Member _selfMember = null; + + public Member SelfMember + { + get + { + if (_selfMember == null) + { + _selfMember = LatestGossip.GetMember(SelfUniqueAddress); + } + + return _selfMember; + } + } + + public Gossip LatestGossip { get; } + + public UniqueAddress SelfUniqueAddress { get; } + + public GossipOverview Overview => LatestGossip.Overview; + + public ImmutableSortedSet Members => LatestGossip.Members; + + /// + /// TODO: this will eventually need to be made DC-aware and tailored specifically to the current DC + /// + public Reachability DcReachability => Overview.Reachability; + + private Option _reachabilityExcludingDownedObservers = Option.None; + public Reachability DcReachabilityExcludingDownedObservers + { + get + { + if (_reachabilityExcludingDownedObservers.IsEmpty) + { + // TODO: adjust for data center + var membersToExclude = Members + .Where(x => x.Status == MemberStatus.Down) + .Select(x => x.UniqueAddress).ToImmutableHashSet(); + _reachabilityExcludingDownedObservers = Overview.Reachability.RemoveObservers(membersToExclude); + } + + return _reachabilityExcludingDownedObservers.Value; + } + } + + public bool IsReachableExcludingDownedObservers(UniqueAddress toAddress) + { + if (!LatestGossip.HasMember(toAddress)) return false; + + // TODO: check for multiple DCs + return LatestGossip.ReachabilityExcludingDownedObservers.Value.IsReachable(toAddress); + } + + public UniqueAddress Leader => LeaderOf(Members); + + public UniqueAddress LeaderOf(IImmutableSet mbrs) + { + var reachability = DcReachability; + var reachableMembers = (reachability.IsAllReachable + ? mbrs.Where(m => m.Status != MemberStatus.Down) + : mbrs + .Where(m => m.Status != MemberStatus.Down && reachability.IsReachable(m.UniqueAddress) || m.UniqueAddress == SelfUniqueAddress)) + .ToImmutableSortedSet(); + + if (!reachableMembers.Any()) return null; + + var member = reachableMembers.FirstOrDefault(m => LeaderMemberStatus.Contains(m.Status)) ?? + reachableMembers.Min(Member.LeaderStatusOrdering); + + return member.UniqueAddress; + } + + public bool IsLeader(UniqueAddress node) + { + return Leader != null && Leader.Equals(node); + } + + public UniqueAddress RoleLeader(string role) + { + return LeaderOf(Members.Where(x => x.HasRole(role)).ToImmutableHashSet()); + } + + /// + /// First check that: + /// 1. we don't have any members that are unreachable, or + /// 2. all unreachable members in the set have status DOWN or EXITING + /// Else we can't continue to check for convergence. When that is done + /// we check that all members with a convergence status is in the seen + /// table and has the latest vector clock version. + /// + /// The set of nodes who have been confirmed to be exiting. + /// true if convergence has been achieved. false otherwise. + public bool Convergence(IImmutableSet exitingConfirmed) + { + // If another member in the data center that is UP or LEAVING + // and has not seen this gossip or is exiting + // convergence cannot be reached + bool MemberHinderingConvergenceExists() + { + return Members.Any(x => ConvergenceMemberStatus.Contains(x.Status) + && !(LatestGossip.SeenByNode(x.UniqueAddress) || + exitingConfirmed.Contains(x.UniqueAddress))); + } + + // Find cluster members in the data center that are unreachable from other members of the data center + // excluding observations from members outside of the data center, that have status DOWN or is passed in as confirmed exiting. + var unreachable = DcReachabilityExcludingDownedObservers.AllUnreachableOrTerminated + .Where(node => node != SelfUniqueAddress && !exitingConfirmed.Contains(node)) + .Select(x => LatestGossip.GetMember(x)); + + // unreachables outside of the data center or with status DOWN or EXITING does not affect convergence + var allUnreachablesCanBeIgnored = + unreachable.All(m => ConvergenceSkipUnreachableWithMemberStatus.Contains(m.Status)); + + return allUnreachablesCanBeIgnored && !MemberHinderingConvergenceExists(); + } + + /// + /// Copies the current and marks the as Seen + /// by the . + /// + /// A new instance with the updated seen records. + public MembershipState Seen() => Copy(LatestGossip.Seen(SelfUniqueAddress)); + + public MembershipState Copy(Gossip gossip = null, UniqueAddress selfUniqueAddress = null) + { + return new MembershipState(gossip ?? LatestGossip, selfUniqueAddress ?? SelfUniqueAddress); + } + + public bool Equals(MembershipState other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return SelfUniqueAddress.Equals(other.SelfUniqueAddress) && LatestGossip.Equals(other.LatestGossip); + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is MembershipState other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return (LatestGossip.GetHashCode() * 397) ^ SelfUniqueAddress.GetHashCode(); + } + } + + /// + /// Never gossip to self and not to node marked as unreachable by self (heartbeat + /// messages are not getting through so no point in trying to gossip). + /// + /// Nodes marked as unreachable by others are still valid targets for gossip. + /// + /// The node to check for gossip validity. + /// true if we can gossip to this node, false otherwise. + public bool ValidNodeForGossip(UniqueAddress node) + { + return !node.Equals(SelfUniqueAddress) && Overview.Reachability.IsReachable(SelfUniqueAddress, node); + } + } +} diff --git a/src/core/Akka.Cluster/Properties/AssemblyInfo.cs b/src/core/Akka.Cluster/Properties/AssemblyInfo.cs index e998ee8714f..f6c293794b7 100644 --- a/src/core/Akka.Cluster/Properties/AssemblyInfo.cs +++ b/src/core/Akka.Cluster/Properties/AssemblyInfo.cs @@ -23,6 +23,7 @@ [assembly: InternalsVisibleTo("Akka.Cluster.Sharding.Tests.MultiNode")] [assembly: InternalsVisibleTo("Akka.Cluster.Metrics")] [assembly: InternalsVisibleTo("Akka.DistributedData")] +[assembly: InternalsVisibleTo("Akka.Benchmarks")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from diff --git a/src/core/Akka.Cluster/Reachability.cs b/src/core/Akka.Cluster/Reachability.cs index 49912e9e208..133f36a159a 100644 --- a/src/core/Akka.Cluster/Reachability.cs +++ b/src/core/Akka.Cluster/Reachability.cs @@ -15,218 +15,116 @@ namespace Akka.Cluster { /// - /// Immutable data structure that holds the reachability status of subject nodes as seen - /// from observer nodes. Failure detector for the subject nodes exist on the - /// observer nodes. Changes (reachable, unreachable, terminated) are only performed - /// by observer nodes to its own records. Each change bumps the version number of the - /// record, and thereby it is always possible to determine which record is newest - /// merging two instances. - /// - /// Aggregated status of a subject node is defined as (in this order): - /// - Terminated if any observer node considers it as Terminated - /// - Unreachable if any observer node considers it as Unreachable - /// - Reachable otherwise, i.e. no observer node considers it as Unreachable + /// Immutable data structure that holds the reachability status of subject nodes as seen + /// from observer nodes. Failure detector for the subject nodes exist on the + /// observer nodes. Changes (reachable, unreachable, terminated) are only performed + /// by observer nodes to its own records. Each change bumps the version number of the + /// record, and thereby it is always possible to determine which record is newest + /// merging two instances. + /// Aggregated status of a subject node is defined as (in this order): + /// - Terminated if any observer node considers it as Terminated + /// - Unreachable if any observer node considers it as Unreachable + /// - Reachable otherwise, i.e. no observer node considers it as Unreachable /// - internal class Reachability //TODO: ISerializable? + internal class Reachability { /// - /// TBD + /// TBD /// - public static readonly Reachability Empty = - new Reachability(ImmutableList.Create(), ImmutableDictionary.Create()); - - /// - /// TBD - /// - /// TBD - /// TBD - public Reachability(ImmutableList records, ImmutableDictionary versions) - { - _cache = new Lazy(() => new Cache(records)); - _versions = versions; - _records = records; - } - - /// - /// TBD - /// - public sealed class Record + public enum ReachabilityStatus { - readonly UniqueAddress _observer; - /// - /// TBD - /// - public UniqueAddress Observer { get { return _observer; } } - readonly UniqueAddress _subject; /// - /// TBD + /// TBD /// - public UniqueAddress Subject { get { return _subject; } } - readonly ReachabilityStatus _status; - /// - /// TBD - /// - public ReachabilityStatus Status { get { return _status; } } - readonly long _version; + Reachable, + /// - /// TBD + /// TBD /// - public long Version { get { return _version; } } + Unreachable, /// - /// TBD + /// TBD /// - /// TBD - /// TBD - /// TBD - /// TBD - public Record(UniqueAddress observer, UniqueAddress subject, ReachabilityStatus status, long version) - { - _observer = observer; - _subject = subject; - _status = status; - _version = version; - } + Terminated + } - /// - public override bool Equals(object obj) - { - var other = obj as Record; - if (other == null) return false; - return _version.Equals(other._version) && - _status == other.Status && - _observer.Equals(other._observer) && - _subject.Equals(other._subject); - } + /// + /// TBD + /// + public static readonly Reachability Empty = + new Reachability(ImmutableList.Create(), ImmutableDictionary.Create()); - /// - public override int GetHashCode() - { - unchecked - { - var hashCode = (Observer != null ? Observer.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ Version.GetHashCode(); - hashCode = (hashCode * 397) ^ Status.GetHashCode(); - hashCode = (hashCode * 397) ^ (Subject != null ? Subject.GetHashCode() : 0); - return hashCode; - } - } - } + private readonly Lazy _cache; /// - /// TBD + /// TBD /// - public enum ReachabilityStatus + /// TBD + /// TBD + public Reachability(ImmutableList records, ImmutableDictionary versions) { - /// - /// TBD - /// - Reachable, - /// - /// TBD - /// - Unreachable, - /// - /// TBD - /// - Terminated + _cache = new Lazy(() => new Cache(records)); + Versions = versions; + Records = records; } /// - /// TBD + /// TBD /// - public ImmutableList Records { get { return _records; } } - readonly ImmutableList _records; + public ImmutableList Records { get; } /// - /// TBD + /// TBD /// - public ImmutableDictionary Versions { get { return _versions; } } - readonly ImmutableDictionary _versions; + public ImmutableDictionary Versions { get; } /// - /// TBD + /// TBD /// - class Cache - { - /// - /// TBD - /// - public ImmutableDictionary> ObserverRowMap { get { return _observerRowsMap; } } - readonly ImmutableDictionary> _observerRowsMap; + public bool IsAllReachable => Records.IsEmpty; - /// - /// TBD - /// - public ImmutableHashSet AllTerminated { get { return _allTerminated; } } - readonly ImmutableHashSet _allTerminated; - - /// - /// TBD - /// - public ImmutableHashSet AllUnreachable { get { return _allUnreachable; } } - readonly ImmutableHashSet _allUnreachable; + /// + /// Doesn't include terminated + /// + public ImmutableHashSet AllUnreachable => _cache.Value.AllUnreachable; - /// - /// TBD - /// - public ImmutableHashSet AllUnreachableOrTerminated { get { return _allUnreachableOrTerminated; } } - readonly ImmutableHashSet _allUnreachableOrTerminated; + /// + /// TBD + /// + public ImmutableHashSet AllUnreachableOrTerminated => _cache.Value.AllUnreachableOrTerminated; - /// - /// TBD - /// - /// TBD - public Cache(ImmutableList records) + /// + /// TBD + /// + public ImmutableDictionary> ObserversGroupedByUnreachable + { + get { - if (records.IsEmpty) - { - _observerRowsMap = ImmutableDictionary.Create>(); - _allTerminated = ImmutableHashSet.Create(); - _allUnreachable = ImmutableHashSet.Create(); - } - else - { - var mapBuilder = new Dictionary>(); - var terminatedBuilder = ImmutableHashSet.CreateBuilder(); - var unreachableBuilder = ImmutableHashSet.CreateBuilder(); - - foreach (var r in records) - { - ImmutableDictionary m = mapBuilder.TryGetValue(r.Observer, out m) - ? m.SetItem(r.Subject, r) - //TODO: Other collections take items for Create. Create unnecessary array here - : ImmutableDictionary.CreateRange(new[] { new KeyValuePair(r.Subject, r) }); - - - mapBuilder.AddOrSet(r.Observer, m); - - if (r.Status == ReachabilityStatus.Unreachable) unreachableBuilder.Add(r.Subject); - else if (r.Status == ReachabilityStatus.Terminated) terminatedBuilder.Add(r.Subject); - } - - _observerRowsMap = ImmutableDictionary.CreateRange(mapBuilder); - _allTerminated = terminatedBuilder.ToImmutable(); - _allUnreachable = unreachableBuilder.ToImmutable().Except(AllTerminated); - } + var builder = new Dictionary>(); - _allUnreachableOrTerminated = _allTerminated.IsEmpty - ? AllUnreachable - : AllUnreachable.Union(AllTerminated); + var grouped = Records.GroupBy(p => p.Subject); + foreach (var records in grouped) + if (records.Any(r => r.Status == ReachabilityStatus.Unreachable)) + builder.Add(records.Key, records.Where(r => r.Status == ReachabilityStatus.Unreachable) + .Select(r => r.Observer).ToImmutableHashSet()); + return builder.ToImmutableDictionary(); } } - //TODO: Serialization should ignore - readonly Lazy _cache; + /// + /// TBD + /// + public ImmutableHashSet AllObservers => Records.Select(i => i.Observer).ToImmutableHashSet(); - ImmutableDictionary ObserverRows(UniqueAddress observer) + private ImmutableDictionary ObserverRows(UniqueAddress observer) { _cache.Value.ObserverRowMap.TryGetValue(observer, out var observerRows); return observerRows; } /// - /// TBD + /// TBD /// /// TBD /// TBD @@ -237,7 +135,7 @@ public Reachability Unreachable(UniqueAddress observer, UniqueAddress subject) } /// - /// TBD + /// TBD /// /// TBD /// TBD @@ -248,7 +146,7 @@ public Reachability Reachable(UniqueAddress observer, UniqueAddress subject) } /// - /// TBD + /// TBD /// /// TBD /// TBD @@ -258,12 +156,12 @@ public Reachability Terminated(UniqueAddress observer, UniqueAddress subject) return Change(observer, subject, ReachabilityStatus.Terminated); } - long CurrentVersion(UniqueAddress observer) + private long CurrentVersion(UniqueAddress observer) { - return _versions.TryGetValue(observer, out long version) ? version : 0; + return Versions.TryGetValue(observer, out var version) ? version : 0; } - long NextVersion(UniqueAddress observer) + private long NextVersion(UniqueAddress observer) { return CurrentVersion(observer) + 1; } @@ -271,18 +169,22 @@ long NextVersion(UniqueAddress observer) private Reachability Change(UniqueAddress observer, UniqueAddress subject, ReachabilityStatus status) { var v = NextVersion(observer); - var newVersions = _versions.SetItem(observer, v); + var newVersions = Versions.SetItem(observer, v); var newRecord = new Record(observer, subject, status, v); var oldObserverRows = ObserverRows(observer); + + // don't record Reachable observation if nothing has been noted so far if (oldObserverRows == null && status == ReachabilityStatus.Reachable) return this; - if (oldObserverRows == null) return new Reachability(_records.Add(newRecord), newVersions); - if(!oldObserverRows.TryGetValue(subject, out var oldRecord)) + // otherwise, create new instance including this first observation + if (oldObserverRows == null) return new Reachability(Records.Add(newRecord), newVersions); + + if (!oldObserverRows.TryGetValue(subject, out var oldRecord)) { if (status == ReachabilityStatus.Reachable && oldObserverRows.Values.All(r => r.Status == ReachabilityStatus.Reachable)) - return new Reachability(_records.FindAll(r => !r.Observer.Equals(observer)), newVersions); - return new Reachability(_records.Add(newRecord), newVersions); + return new Reachability(Records.FindAll(r => !r.Observer.Equals(observer)), newVersions); + return new Reachability(Records.Add(newRecord), newVersions); } if (oldRecord.Status == ReachabilityStatus.Terminated || oldRecord.Status == status) @@ -290,23 +192,23 @@ private Reachability Change(UniqueAddress observer, UniqueAddress subject, Reach if (status == ReachabilityStatus.Reachable && oldObserverRows.Values.All(r => r.Status == ReachabilityStatus.Reachable || r.Subject.Equals(subject))) - return new Reachability(_records.FindAll(r => !r.Observer.Equals(observer)), newVersions); + return new Reachability(Records.FindAll(r => !r.Observer.Equals(observer)), newVersions); - var newRecords = _records.SetItem(_records.IndexOf(oldRecord), newRecord); + var newRecords = Records.SetItem(Records.IndexOf(oldRecord), newRecord); return new Reachability(newRecords, newVersions); } /// - /// TBD + /// TBD /// /// TBD /// TBD /// TBD - public Reachability Merge(IEnumerable allowed, Reachability other) + public Reachability Merge(IImmutableSet allowed, Reachability other) { var recordBuilder = ImmutableList.CreateBuilder(); //TODO: Size hint somehow? - var newVersions = _versions; + var newVersions = Versions; foreach (var observer in allowed) { var observerVersion1 = CurrentVersion(observer); @@ -318,21 +220,18 @@ public Reachability Merge(IEnumerable allowed, Reachability other if (rows1 != null && rows2 != null) { var rows = observerVersion1 > observerVersion2 ? rows1 : rows2; - foreach(var record in rows.Values.Where(r => allowed.Contains(r.Subject))) + foreach (var record in rows.Values.Where(r => allowed.Contains(r.Subject))) recordBuilder.Add(record); } + if (rows1 != null && rows2 == null) - { - if(observerVersion1 > observerVersion2) + if (observerVersion1 > observerVersion2) foreach (var record in rows1.Values.Where(r => allowed.Contains(r.Subject))) recordBuilder.Add(record); - } if (rows1 == null && rows2 != null) - { if (observerVersion2 > observerVersion1) foreach (var record in rows2.Values.Where(r => allowed.Contains(r.Subject))) - recordBuilder.Add(record); - } + recordBuilder.Add(record); if (observerVersion2 > observerVersion1) newVersions = newVersions.SetItem(observer, observerVersion2); @@ -344,20 +243,20 @@ public Reachability Merge(IEnumerable allowed, Reachability other } /// - /// TBD + /// TBD /// /// TBD /// TBD public Reachability Remove(IEnumerable nodes) { var nodesSet = nodes.ToImmutableHashSet(); - var newRecords = _records.FindAll(r => !nodesSet.Contains(r.Observer) && !nodesSet.Contains(r.Subject)); - var newVersions = _versions.RemoveRange(nodes); + var newRecords = Records.FindAll(r => !nodesSet.Contains(r.Observer) && !nodesSet.Contains(r.Subject)); + var newVersions = Versions.RemoveRange(nodes); return new Reachability(newRecords, newVersions); } /// - /// TBD + /// TBD /// /// TBD /// TBD @@ -367,12 +266,10 @@ public Reachability RemoveObservers(ImmutableHashSet nodes) { return this; } - else - { - var newRecords = _records.FindAll(r => !nodes.Contains(r.Observer)); - var newVersions = _versions.RemoveRange(nodes); - return new Reachability(newRecords, newVersions); - } + + var newRecords = Records.FindAll(r => !nodes.Contains(r.Observer)); + var newVersions = Versions.RemoveRange(nodes); + return new Reachability(newRecords, newVersions); } public Reachability FilterRecords(Predicate f) @@ -381,7 +278,7 @@ public Reachability FilterRecords(Predicate f) } /// - /// TBD + /// TBD /// /// TBD /// TBD @@ -391,14 +288,14 @@ public ReachabilityStatus Status(UniqueAddress observer, UniqueAddress subject) var observerRows = ObserverRows(observer); if (observerRows == null) return ReachabilityStatus.Reachable; - if(!observerRows.TryGetValue(subject, out var record)) + if (!observerRows.TryGetValue(subject, out var record)) return ReachabilityStatus.Reachable; return record.Status; } /// - /// TBD + /// TBD /// /// TBD /// TBD @@ -410,7 +307,7 @@ public ReachabilityStatus Status(UniqueAddress node) } /// - /// TBD + /// TBD /// /// TBD /// TBD @@ -420,7 +317,7 @@ public bool IsReachable(UniqueAddress node) } /// - /// TBD + /// TBD /// /// TBD /// TBD @@ -430,116 +327,56 @@ public bool IsReachable(UniqueAddress observer, UniqueAddress subject) return Status(observer, subject) == ReachabilityStatus.Reachable; } - /* - * def isReachable(observer: UniqueAddress, subject: UniqueAddress): Boolean = - status(observer, subject) == Reachable - */ - - /// - /// TBD - /// - public bool IsAllReachable - { - get { return _records.IsEmpty; } - } - /// - /// Doesn't include terminated - /// - public ImmutableHashSet AllUnreachable - { - get { return _cache.Value.AllUnreachable; } - } - - /// - /// TBD - /// - public ImmutableHashSet AllUnreachableOrTerminated - { - get { return _cache.Value.AllUnreachableOrTerminated; } - } - - /// - /// TBD + /// TBD /// /// TBD /// TBD public ImmutableHashSet AllUnreachableFrom(UniqueAddress observer) { var observerRows = ObserverRows(observer); - if (observerRows == null) return ImmutableHashSet.Create(); + if (observerRows == null) return ImmutableHashSet.Empty; return ImmutableHashSet.CreateRange( observerRows.Where(p => p.Value.Status == ReachabilityStatus.Unreachable).Select(p => p.Key)); } /// - /// TBD - /// - public ImmutableDictionary> ObserversGroupedByUnreachable - { - get - { - var builder = new Dictionary>(); - - var grouped = _records.GroupBy(p => p.Subject); - foreach (var records in grouped) - { - if (records.Any(r => r.Status == ReachabilityStatus.Unreachable)) - { - builder.Add(records.Key, records.Where(r => r.Status == ReachabilityStatus.Unreachable) - .Select(r => r.Observer).ToImmutableHashSet()); - } - } - return builder.ToImmutableDictionary(); - } - } - - /// - /// TBD - /// - public ImmutableHashSet AllObservers - { - get - { - return _records.Select(i => i.Observer).ToImmutableHashSet(); - } - } - - /// - /// TBD + /// TBD /// /// TBD /// TBD public ImmutableList RecordsFrom(UniqueAddress observer) { var rows = ObserverRows(observer); - if (rows == null) return ImmutableList.Create(); + if (rows == null) return ImmutableList.Empty; return rows.Values.ToImmutableList(); } - /// + /// only used for testing + /// public override int GetHashCode() { - return _versions.GetHashCode(); + return Versions.GetHashCode(); } - /// + /// only used for testing + /// public override bool Equals(object obj) { var other = obj as Reachability; if (other == null) return false; - return _records.Count == other._records.Count && - _versions.Equals(other._versions) && - _cache.Value.ObserverRowMap.Equals(other._cache.Value.ObserverRowMap); + return Records.Count == other.Records.Count && + Versions.Equals(other.Versions) && + _cache.Value.ObserverRowMap.Equals(other._cache.Value.ObserverRowMap); } - /// + /// public override string ToString() { var builder = new StringBuilder("Reachability("); - foreach (var observer in _versions.Keys) + foreach (var observer in Versions.Keys) { var rows = ObserverRows(observer); if (rows == null) continue; @@ -551,5 +388,141 @@ public override string ToString() return builder.Append(')').ToString(); } + + /// + /// TBD + /// + public sealed class Record + { + /// + /// TBD + /// + /// TBD + /// TBD + /// TBD + /// TBD + public Record(UniqueAddress observer, UniqueAddress subject, ReachabilityStatus status, long version) + { + Observer = observer; + Subject = subject; + Status = status; + Version = version; + } + + /// + /// TBD + /// + public UniqueAddress Observer { get; } + + /// + /// TBD + /// + public UniqueAddress Subject { get; } + + /// + /// TBD + /// + public ReachabilityStatus Status { get; } + + /// + /// TBD + /// + public long Version { get; } + + /// + public override bool Equals(object obj) + { + var other = obj as Record; + if (other == null) return false; + return Version.Equals(other.Version) && + Status == other.Status && + Observer.Equals(other.Observer) && + Subject.Equals(other.Subject); + } + + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = Observer != null ? Observer.GetHashCode() : 0; + hashCode = (hashCode * 397) ^ Version.GetHashCode(); + hashCode = (hashCode * 397) ^ Status.GetHashCode(); + hashCode = (hashCode * 397) ^ (Subject != null ? Subject.GetHashCode() : 0); + return hashCode; + } + } + } + + /// + /// TBD + /// + private class Cache + { + /// + /// TBD + /// + /// TBD + public Cache(ImmutableList records) + { + if (records.IsEmpty) + { + ObserverRowMap = ImmutableDictionary> + .Empty; + AllTerminated = ImmutableHashSet.Empty; + AllUnreachable = ImmutableHashSet.Empty; + } + else + { + var mapBuilder = new Dictionary>(); + var terminatedBuilder = ImmutableHashSet.CreateBuilder(); + var unreachableBuilder = ImmutableHashSet.CreateBuilder(); + + foreach (var r in records) + { + ImmutableDictionary m = mapBuilder.TryGetValue(r.Observer, out var mR) + ? mR.SetItem(r.Subject, r) + : ImmutableDictionary.Empty.Add(r.Subject, r); + + + mapBuilder[r.Observer] = m; + + if (r.Status == ReachabilityStatus.Unreachable) unreachableBuilder.Add(r.Subject); + else if (r.Status == ReachabilityStatus.Terminated) terminatedBuilder.Add(r.Subject); + } + + ObserverRowMap = ImmutableDictionary.CreateRange(mapBuilder); + AllTerminated = terminatedBuilder.ToImmutable(); + AllUnreachable = unreachableBuilder.ToImmutable().Except(AllTerminated); + } + + AllUnreachableOrTerminated = AllTerminated.IsEmpty + ? AllUnreachable + : AllUnreachable.Union(AllTerminated); + } + + /// + /// TBD + /// + public ImmutableDictionary> ObserverRowMap + { + get; + } + + /// + /// Contains all nodes that have been observed as Terminated by at least one other node. + /// + public ImmutableHashSet AllTerminated { get; } + + /// + /// Contains all nodes that have been observed as Unreachable by at least one other node. + /// + public ImmutableHashSet AllUnreachable { get; } + + /// + /// TBD + /// + public ImmutableHashSet AllUnreachableOrTerminated { get; } + } } -} +} \ No newline at end of file diff --git a/src/core/Akka.Cluster/SBR/DowningStrategy.cs b/src/core/Akka.Cluster/SBR/DowningStrategy.cs index b8280224770..2eb64b472f5 100644 --- a/src/core/Akka.Cluster/SBR/DowningStrategy.cs +++ b/src/core/Akka.Cluster/SBR/DowningStrategy.cs @@ -730,4 +730,4 @@ public override IDecision Decide() return new AcquireLeaseAndDownUnreachable(AcquireLeaseDelay); } } -} \ No newline at end of file +} diff --git a/src/core/Akka.Cluster/SBR/SplitBrainResolver.cs b/src/core/Akka.Cluster/SBR/SplitBrainResolver.cs index 0303e649c2d..4bf85662238 100644 --- a/src/core/Akka.Cluster/SBR/SplitBrainResolver.cs +++ b/src/core/Akka.Cluster/SBR/SplitBrainResolver.cs @@ -178,7 +178,7 @@ string EarliestTimeOfDecision() if (unreachableBefore == 0 && unreachableAfter > 0) Log.Info( "SBR found unreachable members, waiting for stable-after = {0} ms before taking downing decision. " + - "Now {1} unreachable members found. Downing decision will not be made before {3}.", + "Now {1} unreachable members found. Downing decision will not be made before {2}.", StableAfter.TotalMilliseconds, unreachableAfter, EarliestTimeOfDecision()); @@ -772,4 +772,4 @@ public WhenTimeElapsed(Deadline deadline) } } } -} \ No newline at end of file +} diff --git a/src/core/Akka.Cluster/SBR/SplitBrainResolverProvider.cs b/src/core/Akka.Cluster/SBR/SplitBrainResolverProvider.cs index bdb0d2bbf02..91fa80a64cd 100644 --- a/src/core/Akka.Cluster/SBR/SplitBrainResolverProvider.cs +++ b/src/core/Akka.Cluster/SBR/SplitBrainResolverProvider.cs @@ -83,4 +83,4 @@ public Props DowningActorProps } } } -} \ No newline at end of file +} diff --git a/src/core/Akka.Cluster/SBR/SplitBrainResolverSettings.cs b/src/core/Akka.Cluster/SBR/SplitBrainResolverSettings.cs index d20cda33ec9..127550f49e7 100644 --- a/src/core/Akka.Cluster/SBR/SplitBrainResolverSettings.cs +++ b/src/core/Akka.Cluster/SBR/SplitBrainResolverSettings.cs @@ -190,4 +190,4 @@ public string SafeLeaseName(string systemName) public string LeaseName { get; } } -} \ No newline at end of file +} diff --git a/src/core/Akka.Cluster/Serialization/ClusterMessageSerializer.cs b/src/core/Akka.Cluster/Serialization/ClusterMessageSerializer.cs index 0f82abc375f..f3d7e443262 100644 --- a/src/core/Akka.Cluster/Serialization/ClusterMessageSerializer.cs +++ b/src/core/Akka.Cluster/Serialization/ClusterMessageSerializer.cs @@ -12,41 +12,55 @@ using System.Runtime.CompilerServices; using System.Runtime.Serialization; using Akka.Actor; -using Akka.Cluster.Routing; +using Akka.Annotations; +using Akka.Cluster.Serialization.Proto.Msg; using Akka.Serialization; using Akka.Util; using Akka.Util.Internal; using Google.Protobuf; using AddressData = Akka.Remote.Serialization.Proto.Msg.AddressData; +using ClusterRouterPool = Akka.Cluster.Routing.ClusterRouterPool; +using ClusterRouterPoolSettings = Akka.Cluster.Routing.ClusterRouterPoolSettings; namespace Akka.Cluster.Serialization { - public class ClusterMessageSerializer : Serializer + [InternalApi] + public class ClusterMessageSerializer : SerializerWithStringManifest { - private readonly Dictionary> _fromBinaryMap; + /* + * BUG: should have been a SerializerWithStringManifest this entire time + * Since it wasn't need to include full type names for backwards compatibility + */ + internal const string JoinManifest = "Akka.Cluster.InternalClusterAction+Join, Akka.Cluster"; + internal const string WelcomeManifest = "Akka.Cluster.InternalClusterAction+Welcome, Akka.Cluster"; + internal const string LeaveManifest = "Akka.Cluster.ClusterUserAction+Leave, Akka.Cluster"; + internal const string DownManifest = "Akka.Cluster.ClusterUserAction+Down, Akka.Cluster"; + + internal const string InitJoinManifest = "Akka.Cluster.InternalClusterAction+InitJoin, Akka.Cluster"; + + internal const string InitJoinAckManifest = "Akka.Cluster.InternalClusterAction+InitJoinAck, Akka.Cluster"; + + internal const string InitJoinNackManifest = "Akka.Cluster.InternalClusterAction+InitJoinNack, Akka.Cluster"; + + // TODO: remove in a future version of Akka.NET (2.0) + internal const string HeartBeatManifestPre1419 = "Akka.Cluster.ClusterHeartbeatSender+Heartbeat, Akka.Cluster"; + internal const string HeartBeatRspManifestPre1419 = "Akka.Cluster.ClusterHeartbeatSender+HeartbeatRsp, Akka.Cluster"; + + internal const string HeartBeatManifest = "HB"; + internal const string HeartBeatRspManifest = "HBR"; + + internal const string ExitingConfirmedManifest = "Akka.Cluster.InternalClusterAction+ExitingConfirmed, Akka.Cluster"; + + internal const string GossipStatusManifest = "Akka.Cluster.GossipStatus, Akka.Cluster"; + internal const string GossipEnvelopeManifest = "Akka.Cluster.GossipEnvelope, Akka.Cluster"; + internal const string ClusterRouterPoolManifest = "Akka.Cluster.Routing.ClusterRouterPool, Akka.Cluster"; + public ClusterMessageSerializer(ExtendedActorSystem system) : base(system) { - _fromBinaryMap = new Dictionary> - { - [typeof(ClusterHeartbeatSender.Heartbeat)] = bytes => new ClusterHeartbeatSender.Heartbeat(AddressFrom(AddressData.Parser.ParseFrom(bytes))), - [typeof(ClusterHeartbeatSender.HeartbeatRsp)] = bytes => new ClusterHeartbeatSender.HeartbeatRsp(UniqueAddressFrom(Proto.Msg.UniqueAddress.Parser.ParseFrom(bytes))), - [typeof(GossipEnvelope)] = GossipEnvelopeFrom, - [typeof(GossipStatus)] = GossipStatusFrom, - [typeof(InternalClusterAction.Join)] = JoinFrom, - [typeof(InternalClusterAction.Welcome)] = WelcomeFrom, - [typeof(ClusterUserAction.Leave)] = bytes => new ClusterUserAction.Leave(AddressFrom(AddressData.Parser.ParseFrom(bytes))), - [typeof(ClusterUserAction.Down)] = bytes => new ClusterUserAction.Down(AddressFrom(AddressData.Parser.ParseFrom(bytes))), - [typeof(InternalClusterAction.InitJoin)] = bytes => new InternalClusterAction.InitJoin(), - [typeof(InternalClusterAction.InitJoinAck)] = bytes => new InternalClusterAction.InitJoinAck(AddressFrom(AddressData.Parser.ParseFrom(bytes))), - [typeof(InternalClusterAction.InitJoinNack)] = bytes => new InternalClusterAction.InitJoinNack(AddressFrom(AddressData.Parser.ParseFrom(bytes))), - [typeof(InternalClusterAction.ExitingConfirmed)] = bytes => new InternalClusterAction.ExitingConfirmed(UniqueAddressFrom(Proto.Msg.UniqueAddress.Parser.ParseFrom(bytes))), - [typeof(ClusterRouterPool)] = ClusterRouterPoolFrom - }; - } - - public override bool IncludeManifest => true; + + } public override byte[] ToBinary(object obj) { @@ -83,12 +97,79 @@ public override byte[] ToBinary(object obj) } } - public override object FromBinary(byte[] bytes, Type type) + public override object FromBinary(byte[] bytes, string manifest) { - if (_fromBinaryMap.TryGetValue(type, out var factory)) - return factory(bytes); + switch (manifest) + { + case HeartBeatManifestPre1419: + return DeserializeHeartbeatAsAddress(bytes); + case HeartBeatRspManifestPre1419: + return DeserializeHeartbeatRspAsUniqueAddress(bytes); + case HeartBeatManifest: + return DeserializeHeartbeat(bytes); + case HeartBeatRspManifest: + return DeserializeHeartbeatRsp(bytes); + case GossipStatusManifest: + return GossipStatusFrom(bytes); + case GossipEnvelopeManifest: + return GossipEnvelopeFrom(bytes); + case InitJoinManifest: + return new InternalClusterAction.InitJoin(); + case InitJoinAckManifest: + return new InternalClusterAction.InitJoinAck(AddressFrom(AddressData.Parser.ParseFrom(bytes))); + case InitJoinNackManifest: + return new InternalClusterAction.InitJoinNack(AddressFrom(AddressData.Parser.ParseFrom(bytes))); + case JoinManifest: + return JoinFrom(bytes); + case WelcomeManifest: + return WelcomeFrom(bytes); + case LeaveManifest: + return new ClusterUserAction.Leave(AddressFrom(AddressData.Parser.ParseFrom(bytes))); + case DownManifest: + return new ClusterUserAction.Down(AddressFrom(AddressData.Parser.ParseFrom(bytes))); + case ExitingConfirmedManifest: + return new InternalClusterAction.ExitingConfirmed( + UniqueAddressFrom(Proto.Msg.UniqueAddress.Parser.ParseFrom(bytes))); + case ClusterRouterPoolManifest: + return ClusterRouterPoolFrom(bytes); + default: + throw new ArgumentException($"Unknown manifest [{manifest}] in [{nameof(ClusterMessageSerializer)}]"); + } + } - throw new SerializationException($"{nameof(ClusterMessageSerializer)} cannot deserialize object of type {type}"); + public override string Manifest(object o) + { + switch (o) + { + case InternalClusterAction.Join _: + return JoinManifest; + case InternalClusterAction.Welcome _: + return WelcomeManifest; + case ClusterUserAction.Leave _: + return LeaveManifest; + case ClusterUserAction.Down _: + return DownManifest; + case InternalClusterAction.InitJoin _: + return InitJoinManifest; + case InternalClusterAction.InitJoinAck _: + return InitJoinAckManifest; + case InternalClusterAction.InitJoinNack _: + return InitJoinNackManifest; + case ClusterHeartbeatSender.Heartbeat _: + return HeartBeatManifestPre1419; + case ClusterHeartbeatSender.HeartbeatRsp _: + return HeartBeatRspManifestPre1419; + case InternalClusterAction.ExitingConfirmed _: + return ExitingConfirmedManifest; + case GossipStatus _: + return GossipStatusManifest; + case GossipEnvelope _: + return GossipEnvelopeManifest; + case ClusterRouterPool _: + return ClusterRouterPoolManifest; + default: + throw new ArgumentException($"Can't serialize object of type [{o.GetType()}] in [{GetType()}]"); + } } // @@ -106,10 +187,11 @@ private static byte[] JoinToByteArray(InternalClusterAction.Join join) private static InternalClusterAction.Join JoinFrom(byte[] bytes) { var join = Proto.Msg.Join.Parser.ParseFrom(bytes); - AppVersion ver = join.HasAppVersion ? AppVersion.Create(join.AppVersion) : AppVersion.Zero; + var ver = !string.IsNullOrEmpty(join.AppVersion) ? AppVersion.Create(join.AppVersion) : AppVersion.Zero; return new InternalClusterAction.Join(UniqueAddressFrom(join.Node), join.Roles.ToImmutableHashSet(), ver); } + // TODO: need to gzip compress the Welcome message for large clusters private static byte[] WelcomeMessageBuilder(InternalClusterAction.Welcome welcome) { var welcomeProto = new Proto.Msg.Welcome(); @@ -366,12 +448,38 @@ private static int MapWithErrorMessage(Dictionary map, T value, strin throw new ArgumentException($"Unknown {unknown} [{value}] in cluster message"); } + // + // Heartbeat + // + private static ClusterHeartbeatSender.HeartbeatRsp DeserializeHeartbeatRspAsUniqueAddress(byte[] bytes) + { + var uniqueAddress = UniqueAddressFrom(Proto.Msg.UniqueAddress.Parser.ParseFrom(bytes)); + return new ClusterHeartbeatSender.HeartbeatRsp(uniqueAddress, -1, -1); + } + + private static ClusterHeartbeatSender.HeartbeatRsp DeserializeHeartbeatRsp(byte[] bytes) + { + var hbsp = HeartBeatResponse.Parser.ParseFrom(bytes); + return new ClusterHeartbeatSender.HeartbeatRsp(UniqueAddressFrom(hbsp.From), hbsp.SequenceNr, hbsp.CreationTime); + } + + private static ClusterHeartbeatSender.Heartbeat DeserializeHeartbeatAsAddress(byte[] bytes) + { + return new ClusterHeartbeatSender.Heartbeat(AddressFrom(AddressData.Parser.ParseFrom(bytes)), -1, -1); + } + + private static ClusterHeartbeatSender.Heartbeat DeserializeHeartbeat(byte[] bytes) + { + var hb = Heartbeat.Parser.ParseFrom(bytes); + return new ClusterHeartbeatSender.Heartbeat(AddressFrom(hb.From), hb.SequenceNr, hb.CreationTime); + } + // // Address // [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static AddressData AddressToProto(Address address) + internal static AddressData AddressToProto(Address address) { var message = new AddressData(); message.System = address.System; @@ -382,7 +490,7 @@ private static AddressData AddressToProto(Address address) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Address AddressFrom(AddressData addressProto) + internal static Address AddressFrom(AddressData addressProto) { return new Address( addressProto.Protocol, @@ -392,7 +500,7 @@ private static Address AddressFrom(AddressData addressProto) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Proto.Msg.UniqueAddress UniqueAddressToProto(UniqueAddress uniqueAddress) + internal static Proto.Msg.UniqueAddress UniqueAddressToProto(UniqueAddress uniqueAddress) { var message = new Proto.Msg.UniqueAddress(); message.Address = AddressToProto(uniqueAddress.Address); @@ -401,7 +509,7 @@ private static Proto.Msg.UniqueAddress UniqueAddressToProto(UniqueAddress unique } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static UniqueAddress UniqueAddressFrom(Proto.Msg.UniqueAddress uniqueAddressProto) + internal static UniqueAddress UniqueAddressFrom(Proto.Msg.UniqueAddress uniqueAddressProto) { return new UniqueAddress(AddressFrom(uniqueAddressProto.Address), (int)uniqueAddressProto.Uid); } @@ -409,8 +517,7 @@ private static UniqueAddress UniqueAddressFrom(Proto.Msg.UniqueAddress uniqueAdd [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string GetObjectManifest(Serializer serializer, object obj) { - var manifestSerializer = serializer as SerializerWithStringManifest; - if (manifestSerializer != null) + if (serializer is SerializerWithStringManifest manifestSerializer) { return manifestSerializer.Manifest(obj); } diff --git a/src/core/Akka.Cluster/Serialization/Proto/ClusterMessages.g.cs b/src/core/Akka.Cluster/Serialization/Proto/ClusterMessages.g.cs index a0bc89667e4..bf5059bfda8 100644 --- a/src/core/Akka.Cluster/Serialization/Proto/ClusterMessages.g.cs +++ b/src/core/Akka.Cluster/Serialization/Proto/ClusterMessages.g.cs @@ -1,14 +1,5 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2021 Lightbend Inc. -// Copyright (C) 2013-2021 .NET Foundation -// -//----------------------------------------------------------------------- - -// -// Generated by the protocol buffer compiler. DO NOT EDIT! -// source: ClusterMessages.proto -// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: ClusterMessages.proto #pragma warning disable 1591, 0612, 3021 #region Designer generated code @@ -32,77 +23,84 @@ static ClusterMessagesReflection() { byte[] descriptorData = global::System.Convert.FromBase64String( string.Concat( "ChVDbHVzdGVyTWVzc2FnZXMucHJvdG8SJEFra2EuQ2x1c3Rlci5TZXJpYWxp", - "emF0aW9uLlByb3RvLk1zZxoWQ29udGFpbmVyRm9ybWF0cy5wcm90byKAAQoE", - "Sm9pbhJBCgRub2RlGAEgASgLMjMuQWtrYS5DbHVzdGVyLlNlcmlhbGl6YXRp", - "b24uUHJvdG8uTXNnLlVuaXF1ZUFkZHJlc3MSDQoFcm9sZXMYAiADKAkSFwoK", - "YXBwVmVyc2lvbhgDIAEoCUgAiAEBQg0KC19hcHBWZXJzaW9uIooBCgdXZWxj", - "b21lEkEKBGZyb20YASABKAsyMy5Ba2thLkNsdXN0ZXIuU2VyaWFsaXphdGlv", - "bi5Qcm90by5Nc2cuVW5pcXVlQWRkcmVzcxI8CgZnb3NzaXAYAiABKAsyLC5B", - "a2thLkNsdXN0ZXIuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuR29zc2lwIq4B", - "Cg5Hb3NzaXBFbnZlbG9wZRJBCgRmcm9tGAEgASgLMjMuQWtrYS5DbHVzdGVy", - "LlNlcmlhbGl6YXRpb24uUHJvdG8uTXNnLlVuaXF1ZUFkZHJlc3MSPwoCdG8Y", - "AiABKAsyMy5Ba2thLkNsdXN0ZXIuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cu", - "VW5pcXVlQWRkcmVzcxIYChBzZXJpYWxpemVkR29zc2lwGAMgASgMIqgBCgxH", - "b3NzaXBTdGF0dXMSQQoEZnJvbRgBIAEoCzIzLkFra2EuQ2x1c3Rlci5TZXJp", - "YWxpemF0aW9uLlByb3RvLk1zZy5VbmlxdWVBZGRyZXNzEhEKCWFsbEhhc2hl", - "cxgCIAMoCRJCCgd2ZXJzaW9uGAMgASgLMjEuQWtrYS5DbHVzdGVyLlNlcmlh", - "bGl6YXRpb24uUHJvdG8uTXNnLlZlY3RvckNsb2NrItsCCgZHb3NzaXASSQoM", - "YWxsQWRkcmVzc2VzGAEgAygLMjMuQWtrYS5DbHVzdGVyLlNlcmlhbGl6YXRp", - "b24uUHJvdG8uTXNnLlVuaXF1ZUFkZHJlc3MSEAoIYWxsUm9sZXMYAiADKAkS", - "EQoJYWxsSGFzaGVzGAMgAygJEj0KB21lbWJlcnMYBCADKAsyLC5Ba2thLkNs", - "dXN0ZXIuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuTWVtYmVyEkYKCG92ZXJ2", - "aWV3GAUgASgLMjQuQWtrYS5DbHVzdGVyLlNlcmlhbGl6YXRpb24uUHJvdG8u", - "TXNnLkdvc3NpcE92ZXJ2aWV3EkIKB3ZlcnNpb24YBiABKAsyMS5Ba2thLkNs", - "dXN0ZXIuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuVmVjdG9yQ2xvY2sSFgoO", - "YWxsQXBwVmVyc2lvbnMYCCADKAkieAoOR29zc2lwT3ZlcnZpZXcSDAoEc2Vl", - "bhgBIAMoBRJYChRvYnNlcnZlclJlYWNoYWJpbGl0eRgCIAMoCzI6LkFra2Eu", - "Q2x1c3Rlci5TZXJpYWxpemF0aW9uLlByb3RvLk1zZy5PYnNlcnZlclJlYWNo", - "YWJpbGl0eSKVAQoUT2JzZXJ2ZXJSZWFjaGFiaWxpdHkSFAoMYWRkcmVzc0lu", - "ZGV4GAEgASgFEg8KB3ZlcnNpb24YBCABKAMSVgoTc3ViamVjdFJlYWNoYWJp", - "bGl0eRgCIAMoCzI5LkFra2EuQ2x1c3Rlci5TZXJpYWxpemF0aW9uLlByb3Rv", - "Lk1zZy5TdWJqZWN0UmVhY2hhYmlsaXR5IuABChNTdWJqZWN0UmVhY2hhYmls", - "aXR5EhQKDGFkZHJlc3NJbmRleBgBIAEoBRJcCgZzdGF0dXMYAyABKA4yTC5B", - "a2thLkNsdXN0ZXIuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuU3ViamVjdFJl", - "YWNoYWJpbGl0eS5SZWFjaGFiaWxpdHlTdGF0dXMSDwoHdmVyc2lvbhgEIAEo", - "AyJEChJSZWFjaGFiaWxpdHlTdGF0dXMSDQoJUmVhY2hhYmxlEAASDwoLVW5y", - "ZWFjaGFibGUQARIOCgpUZXJtaW5hdGVkEAIiqwIKBk1lbWJlchIUCgxhZGRy", - "ZXNzSW5kZXgYASABKAUSEAoIdXBOdW1iZXIYAiABKAUSSQoGc3RhdHVzGAMg", - "ASgOMjkuQWtrYS5DbHVzdGVyLlNlcmlhbGl6YXRpb24uUHJvdG8uTXNnLk1l", - "bWJlci5NZW1iZXJTdGF0dXMSGAoMcm9sZXNJbmRleGVzGAQgAygFQgIQARIc", - "Cg9hcHBWZXJzaW9uSW5kZXgYBSABKAVIAIgBASJiCgxNZW1iZXJTdGF0dXMS", - "CwoHSm9pbmluZxAAEgYKAlVwEAESCwoHTGVhdmluZxACEgsKB0V4aXRpbmcQ", - "AxIICgREb3duEAQSCwoHUmVtb3ZlZBAFEgwKCFdlYWtseVVwEAZCEgoQX2Fw", - "cFZlcnNpb25JbmRleCKeAQoLVmVjdG9yQ2xvY2sSEQoJdGltZXN0YW1wGAEg", - "ASgDEksKCHZlcnNpb25zGAIgAygLMjkuQWtrYS5DbHVzdGVyLlNlcmlhbGl6", - "YXRpb24uUHJvdG8uTXNnLlZlY3RvckNsb2NrLlZlcnNpb24aLwoHVmVyc2lv", - "bhIRCgloYXNoSW5kZXgYASABKAUSEQoJdGltZXN0YW1wGAIgASgDIl8KDVVu", - "aXF1ZUFkZHJlc3MSQQoHYWRkcmVzcxgBIAEoCzIwLkFra2EuUmVtb3RlLlNl", - "cmlhbGl6YXRpb24uUHJvdG8uTXNnLkFkZHJlc3NEYXRhEgsKA3VpZBgCIAEo", - "DSKgAQoRQ2x1c3RlclJvdXRlclBvb2wSOAoEcG9vbBgBIAEoCzIqLkFra2Eu", - "Q2x1c3Rlci5TZXJpYWxpemF0aW9uLlByb3RvLk1zZy5Qb29sElEKCHNldHRp", - "bmdzGAIgASgLMj8uQWtrYS5DbHVzdGVyLlNlcmlhbGl6YXRpb24uUHJvdG8u", - "TXNnLkNsdXN0ZXJSb3V0ZXJQb29sU2V0dGluZ3MiPAoEUG9vbBIUCgxzZXJp", - "YWxpemVySWQYASABKA0SEAoIbWFuaWZlc3QYAiABKAkSDAoEZGF0YRgDIAEo", - "DCJ8ChlDbHVzdGVyUm91dGVyUG9vbFNldHRpbmdzEhYKDnRvdGFsSW5zdGFu", - "Y2VzGAEgASgNEhsKE21heEluc3RhbmNlc1Blck5vZGUYAiABKA0SGQoRYWxs", - "b3dMb2NhbFJvdXRlZXMYAyABKAgSDwoHdXNlUm9sZRgEIAEoCWIGcHJvdG8z")); + "emF0aW9uLlByb3RvLk1zZxoWQ29udGFpbmVyRm9ybWF0cy5wcm90byJsCgRK", + "b2luEkEKBG5vZGUYASABKAsyMy5Ba2thLkNsdXN0ZXIuU2VyaWFsaXphdGlv", + "bi5Qcm90by5Nc2cuVW5pcXVlQWRkcmVzcxINCgVyb2xlcxgCIAMoCRISCgph", + "cHBWZXJzaW9uGAMgASgJIooBCgdXZWxjb21lEkEKBGZyb20YASABKAsyMy5B", + "a2thLkNsdXN0ZXIuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuVW5pcXVlQWRk", + "cmVzcxI8CgZnb3NzaXAYAiABKAsyLC5Ba2thLkNsdXN0ZXIuU2VyaWFsaXph", + "dGlvbi5Qcm90by5Nc2cuR29zc2lwInUKCUhlYXJ0YmVhdBI+CgRmcm9tGAEg", + "ASgLMjAuQWtrYS5SZW1vdGUuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuQWRk", + "cmVzc0RhdGESEgoKc2VxdWVuY2VOchgCIAEoAxIUCgxjcmVhdGlvblRpbWUY", + "AyABKBIigAEKEUhlYXJ0QmVhdFJlc3BvbnNlEkEKBGZyb20YASABKAsyMy5B", + "a2thLkNsdXN0ZXIuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuVW5pcXVlQWRk", + "cmVzcxISCgpzZXF1ZW5jZU5yGAIgASgDEhQKDGNyZWF0aW9uVGltZRgDIAEo", + "AyKuAQoOR29zc2lwRW52ZWxvcGUSQQoEZnJvbRgBIAEoCzIzLkFra2EuQ2x1", + "c3Rlci5TZXJpYWxpemF0aW9uLlByb3RvLk1zZy5VbmlxdWVBZGRyZXNzEj8K", + "AnRvGAIgASgLMjMuQWtrYS5DbHVzdGVyLlNlcmlhbGl6YXRpb24uUHJvdG8u", + "TXNnLlVuaXF1ZUFkZHJlc3MSGAoQc2VyaWFsaXplZEdvc3NpcBgDIAEoDCKo", + "AQoMR29zc2lwU3RhdHVzEkEKBGZyb20YASABKAsyMy5Ba2thLkNsdXN0ZXIu", + "U2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuVW5pcXVlQWRkcmVzcxIRCglhbGxI", + "YXNoZXMYAiADKAkSQgoHdmVyc2lvbhgDIAEoCzIxLkFra2EuQ2x1c3Rlci5T", + "ZXJpYWxpemF0aW9uLlByb3RvLk1zZy5WZWN0b3JDbG9jayLbAgoGR29zc2lw", + "EkkKDGFsbEFkZHJlc3NlcxgBIAMoCzIzLkFra2EuQ2x1c3Rlci5TZXJpYWxp", + "emF0aW9uLlByb3RvLk1zZy5VbmlxdWVBZGRyZXNzEhAKCGFsbFJvbGVzGAIg", + "AygJEhEKCWFsbEhhc2hlcxgDIAMoCRI9CgdtZW1iZXJzGAQgAygLMiwuQWtr", + "YS5DbHVzdGVyLlNlcmlhbGl6YXRpb24uUHJvdG8uTXNnLk1lbWJlchJGCghv", + "dmVydmlldxgFIAEoCzI0LkFra2EuQ2x1c3Rlci5TZXJpYWxpemF0aW9uLlBy", + "b3RvLk1zZy5Hb3NzaXBPdmVydmlldxJCCgd2ZXJzaW9uGAYgASgLMjEuQWtr", + "YS5DbHVzdGVyLlNlcmlhbGl6YXRpb24uUHJvdG8uTXNnLlZlY3RvckNsb2Nr", + "EhYKDmFsbEFwcFZlcnNpb25zGAggAygJIngKDkdvc3NpcE92ZXJ2aWV3EgwK", + "BHNlZW4YASADKAUSWAoUb2JzZXJ2ZXJSZWFjaGFiaWxpdHkYAiADKAsyOi5B", + "a2thLkNsdXN0ZXIuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuT2JzZXJ2ZXJS", + "ZWFjaGFiaWxpdHkilQEKFE9ic2VydmVyUmVhY2hhYmlsaXR5EhQKDGFkZHJl", + "c3NJbmRleBgBIAEoBRIPCgd2ZXJzaW9uGAQgASgDElYKE3N1YmplY3RSZWFj", + "aGFiaWxpdHkYAiADKAsyOS5Ba2thLkNsdXN0ZXIuU2VyaWFsaXphdGlvbi5Q", + "cm90by5Nc2cuU3ViamVjdFJlYWNoYWJpbGl0eSLgAQoTU3ViamVjdFJlYWNo", + "YWJpbGl0eRIUCgxhZGRyZXNzSW5kZXgYASABKAUSXAoGc3RhdHVzGAMgASgO", + "MkwuQWtrYS5DbHVzdGVyLlNlcmlhbGl6YXRpb24uUHJvdG8uTXNnLlN1Ympl", + "Y3RSZWFjaGFiaWxpdHkuUmVhY2hhYmlsaXR5U3RhdHVzEg8KB3ZlcnNpb24Y", + "BCABKAMiRAoSUmVhY2hhYmlsaXR5U3RhdHVzEg0KCVJlYWNoYWJsZRAAEg8K", + "C1VucmVhY2hhYmxlEAESDgoKVGVybWluYXRlZBACIpICCgZNZW1iZXISFAoM", + "YWRkcmVzc0luZGV4GAEgASgFEhAKCHVwTnVtYmVyGAIgASgFEkkKBnN0YXR1", + "cxgDIAEoDjI5LkFra2EuQ2x1c3Rlci5TZXJpYWxpemF0aW9uLlByb3RvLk1z", + "Zy5NZW1iZXIuTWVtYmVyU3RhdHVzEhgKDHJvbGVzSW5kZXhlcxgEIAMoBUIC", + "EAESFwoPYXBwVmVyc2lvbkluZGV4GAUgASgFImIKDE1lbWJlclN0YXR1cxIL", + "CgdKb2luaW5nEAASBgoCVXAQARILCgdMZWF2aW5nEAISCwoHRXhpdGluZxAD", + "EggKBERvd24QBBILCgdSZW1vdmVkEAUSDAoIV2Vha2x5VXAQBiKeAQoLVmVj", + "dG9yQ2xvY2sSEQoJdGltZXN0YW1wGAEgASgDEksKCHZlcnNpb25zGAIgAygL", + "MjkuQWtrYS5DbHVzdGVyLlNlcmlhbGl6YXRpb24uUHJvdG8uTXNnLlZlY3Rv", + "ckNsb2NrLlZlcnNpb24aLwoHVmVyc2lvbhIRCgloYXNoSW5kZXgYASABKAUS", + "EQoJdGltZXN0YW1wGAIgASgDIl8KDVVuaXF1ZUFkZHJlc3MSQQoHYWRkcmVz", + "cxgBIAEoCzIwLkFra2EuUmVtb3RlLlNlcmlhbGl6YXRpb24uUHJvdG8uTXNn", + "LkFkZHJlc3NEYXRhEgsKA3VpZBgCIAEoDSKgAQoRQ2x1c3RlclJvdXRlclBv", + "b2wSOAoEcG9vbBgBIAEoCzIqLkFra2EuQ2x1c3Rlci5TZXJpYWxpemF0aW9u", + "LlByb3RvLk1zZy5Qb29sElEKCHNldHRpbmdzGAIgASgLMj8uQWtrYS5DbHVz", + "dGVyLlNlcmlhbGl6YXRpb24uUHJvdG8uTXNnLkNsdXN0ZXJSb3V0ZXJQb29s", + "U2V0dGluZ3MiPAoEUG9vbBIUCgxzZXJpYWxpemVySWQYASABKA0SEAoIbWFu", + "aWZlc3QYAiABKAkSDAoEZGF0YRgDIAEoDCJ8ChlDbHVzdGVyUm91dGVyUG9v", + "bFNldHRpbmdzEhYKDnRvdGFsSW5zdGFuY2VzGAEgASgNEhsKE21heEluc3Rh", + "bmNlc1Blck5vZGUYAiABKA0SGQoRYWxsb3dMb2NhbFJvdXRlZXMYAyABKAgS", + "DwoHdXNlUm9sZRgEIAEoCWIGcHJvdG8z")); descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, new pbr::FileDescriptor[] { global::Akka.Remote.Serialization.Proto.Msg.ContainerFormatsReflection.Descriptor, }, - new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] { - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.Join), global::Akka.Cluster.Serialization.Proto.Msg.Join.Parser, new[]{ "Node", "Roles", "AppVersion" }, new[]{ "AppVersion" }, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.Welcome), global::Akka.Cluster.Serialization.Proto.Msg.Welcome.Parser, new[]{ "From", "Gossip" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.GossipEnvelope), global::Akka.Cluster.Serialization.Proto.Msg.GossipEnvelope.Parser, new[]{ "From", "To", "SerializedGossip" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.GossipStatus), global::Akka.Cluster.Serialization.Proto.Msg.GossipStatus.Parser, new[]{ "From", "AllHashes", "Version" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.Gossip), global::Akka.Cluster.Serialization.Proto.Msg.Gossip.Parser, new[]{ "AllAddresses", "AllRoles", "AllHashes", "Members", "Overview", "Version", "AllAppVersions" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.GossipOverview), global::Akka.Cluster.Serialization.Proto.Msg.GossipOverview.Parser, new[]{ "Seen", "ObserverReachability" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.ObserverReachability), global::Akka.Cluster.Serialization.Proto.Msg.ObserverReachability.Parser, new[]{ "AddressIndex", "Version", "SubjectReachability" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability), global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Parser, new[]{ "AddressIndex", "Status", "Version" }, null, new[]{ typeof(global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus) }, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.Member), global::Akka.Cluster.Serialization.Proto.Msg.Member.Parser, new[]{ "AddressIndex", "UpNumber", "Status", "RolesIndexes", "AppVersionIndex" }, new[]{ "AppVersionIndex" }, new[]{ typeof(global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus) }, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.VectorClock), global::Akka.Cluster.Serialization.Proto.Msg.VectorClock.Parser, new[]{ "Timestamp", "Versions" }, null, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.VectorClock.Types.Version), global::Akka.Cluster.Serialization.Proto.Msg.VectorClock.Types.Version.Parser, new[]{ "HashIndex", "Timestamp" }, null, null, null, null)}), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress), global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress.Parser, new[]{ "Address", "Uid" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPool), global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPool.Parser, new[]{ "Pool", "Settings" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.Pool), global::Akka.Cluster.Serialization.Proto.Msg.Pool.Parser, new[]{ "SerializerId", "Manifest", "Data" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPoolSettings), global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPoolSettings.Parser, new[]{ "TotalInstances", "MaxInstancesPerNode", "AllowLocalRoutees", "UseRole" }, null, null, null, null) + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.Join), global::Akka.Cluster.Serialization.Proto.Msg.Join.Parser, new[]{ "Node", "Roles", "AppVersion" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.Welcome), global::Akka.Cluster.Serialization.Proto.Msg.Welcome.Parser, new[]{ "From", "Gossip" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.Heartbeat), global::Akka.Cluster.Serialization.Proto.Msg.Heartbeat.Parser, new[]{ "From", "SequenceNr", "CreationTime" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.HeartBeatResponse), global::Akka.Cluster.Serialization.Proto.Msg.HeartBeatResponse.Parser, new[]{ "From", "SequenceNr", "CreationTime" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.GossipEnvelope), global::Akka.Cluster.Serialization.Proto.Msg.GossipEnvelope.Parser, new[]{ "From", "To", "SerializedGossip" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.GossipStatus), global::Akka.Cluster.Serialization.Proto.Msg.GossipStatus.Parser, new[]{ "From", "AllHashes", "Version" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.Gossip), global::Akka.Cluster.Serialization.Proto.Msg.Gossip.Parser, new[]{ "AllAddresses", "AllRoles", "AllHashes", "Members", "Overview", "Version", "AllAppVersions" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.GossipOverview), global::Akka.Cluster.Serialization.Proto.Msg.GossipOverview.Parser, new[]{ "Seen", "ObserverReachability" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.ObserverReachability), global::Akka.Cluster.Serialization.Proto.Msg.ObserverReachability.Parser, new[]{ "AddressIndex", "Version", "SubjectReachability" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability), global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Parser, new[]{ "AddressIndex", "Status", "Version" }, null, new[]{ typeof(global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus) }, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.Member), global::Akka.Cluster.Serialization.Proto.Msg.Member.Parser, new[]{ "AddressIndex", "UpNumber", "Status", "RolesIndexes", "AppVersionIndex" }, null, new[]{ typeof(global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus) }, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.VectorClock), global::Akka.Cluster.Serialization.Proto.Msg.VectorClock.Parser, new[]{ "Timestamp", "Versions" }, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.VectorClock.Types.Version), global::Akka.Cluster.Serialization.Proto.Msg.VectorClock.Types.Version.Parser, new[]{ "HashIndex", "Timestamp" }, null, null, null)}), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress), global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress.Parser, new[]{ "Address", "Uid" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPool), global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPool.Parser, new[]{ "Pool", "Settings" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.Pool), global::Akka.Cluster.Serialization.Proto.Msg.Pool.Parser, new[]{ "SerializerId", "Manifest", "Data" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPoolSettings), global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPoolSettings.Parser, new[]{ "TotalInstances", "MaxInstancesPerNode", "AllowLocalRoutees", "UseRole" }, null, null, null) })); } #endregion @@ -112,13 +110,8 @@ static ClusterMessagesReflection() { /// /// Join /// - internal sealed partial class Join : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class Join : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Join()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } @@ -141,10 +134,9 @@ public Join() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public Join(Join other) : this() { - node_ = other.node_ != null ? other.node_.Clone() : null; + Node = other.node_ != null ? other.Node.Clone() : null; roles_ = other.roles_.Clone(); appVersion_ = other.appVersion_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -175,24 +167,14 @@ public Join Clone() { /// Field number for the "appVersion" field. public const int AppVersionFieldNumber = 3; - private string appVersion_; + private string appVersion_ = ""; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public string AppVersion { - get { return appVersion_ ?? ""; } + get { return appVersion_; } set { appVersion_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); } } - /// Gets whether the "appVersion" field is set - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool HasAppVersion { - get { return appVersion_ != null; } - } - /// Clears the value of the "appVersion" field - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void ClearAppVersion() { - appVersion_ = null; - } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public override bool Equals(object other) { @@ -210,7 +192,7 @@ public bool Equals(Join other) { if (!object.Equals(Node, other.Node)) return false; if(!roles_.Equals(other.roles_)) return false; if (AppVersion != other.AppVersion) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -218,10 +200,7 @@ public override int GetHashCode() { int hash = 1; if (node_ != null) hash ^= Node.GetHashCode(); hash ^= roles_.GetHashCode(); - if (HasAppVersion) hash ^= AppVersion.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } + if (AppVersion.Length != 0) hash ^= AppVersion.GetHashCode(); return hash; } @@ -232,41 +211,16 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else if (node_ != null) { output.WriteRawTag(10); output.WriteMessage(Node); } roles_.WriteTo(output, _repeated_roles_codec); - if (HasAppVersion) { - output.WriteRawTag(26); - output.WriteString(AppVersion); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { - if (node_ != null) { - output.WriteRawTag(10); - output.WriteMessage(Node); - } - roles_.WriteTo(ref output, _repeated_roles_codec); - if (HasAppVersion) { + if (AppVersion.Length != 0) { output.WriteRawTag(26); output.WriteString(AppVersion); } - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } } - #endif [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { @@ -275,12 +229,9 @@ public int CalculateSize() { size += 1 + pb::CodedOutputStream.ComputeMessageSize(Node); } size += roles_.CalculateSize(_repeated_roles_codec); - if (HasAppVersion) { + if (AppVersion.Length != 0) { size += 1 + pb::CodedOutputStream.ComputeStringSize(AppVersion); } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -291,33 +242,29 @@ public void MergeFrom(Join other) { } if (other.node_ != null) { if (node_ == null) { - Node = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); + node_ = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); } Node.MergeFrom(other.Node); } roles_.Add(other.roles_); - if (other.HasAppVersion) { + if (other.AppVersion.Length != 0) { AppVersion = other.AppVersion; } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + input.SkipLastField(); break; case 10: { if (node_ == null) { - Node = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); + node_ = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); } - input.ReadMessage(Node); + input.ReadMessage(node_); break; } case 18: { @@ -330,50 +277,15 @@ public void MergeFrom(pb::CodedInputStream input) { } } } - #endif } - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); - break; - case 10: { - if (node_ == null) { - Node = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); - } - input.ReadMessage(Node); - break; - } - case 18: { - roles_.AddEntriesFrom(ref input, _repeated_roles_codec); - break; - } - case 26: { - AppVersion = input.ReadString(); - break; - } - } - } - } - #endif - } /// /// Welcome, reply to Join /// - internal sealed partial class Welcome : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class Welcome : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Welcome()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } @@ -396,9 +308,8 @@ public Welcome() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public Welcome(Welcome other) : this() { - from_ = other.from_ != null ? other.from_.Clone() : null; - gossip_ = other.gossip_ != null ? other.gossip_.Clone() : null; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + From = other.from_ != null ? other.From.Clone() : null; + Gossip = other.gossip_ != null ? other.Gossip.Clone() : null; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -443,7 +354,7 @@ public bool Equals(Welcome other) { } if (!object.Equals(From, other.From)) return false; if (!object.Equals(Gossip, other.Gossip)) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -451,9 +362,6 @@ public override int GetHashCode() { int hash = 1; if (from_ != null) hash ^= From.GetHashCode(); if (gossip_ != null) hash ^= Gossip.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -464,9 +372,6 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else if (from_ != null) { output.WriteRawTag(10); output.WriteMessage(From); @@ -475,29 +380,8 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(18); output.WriteMessage(Gossip); } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif } - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { - if (from_ != null) { - output.WriteRawTag(10); - output.WriteMessage(From); - } - if (gossip_ != null) { - output.WriteRawTag(18); - output.WriteMessage(Gossip); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } - } - #endif - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { int size = 0; @@ -507,9 +391,6 @@ public int CalculateSize() { if (gossip_ != null) { size += 1 + pb::CodedOutputStream.ComputeMessageSize(Gossip); } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -520,95 +401,429 @@ public void MergeFrom(Welcome other) { } if (other.from_ != null) { if (from_ == null) { - From = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); + from_ = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); } From.MergeFrom(other.From); } if (other.gossip_ != null) { if (gossip_ == null) { - Gossip = new global::Akka.Cluster.Serialization.Proto.Msg.Gossip(); + gossip_ = new global::Akka.Cluster.Serialization.Proto.Msg.Gossip(); } Gossip.MergeFrom(other.Gossip); } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + input.SkipLastField(); break; case 10: { if (from_ == null) { - From = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); + from_ = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); } - input.ReadMessage(From); + input.ReadMessage(from_); break; } case 18: { if (gossip_ == null) { - Gossip = new global::Akka.Cluster.Serialization.Proto.Msg.Gossip(); + gossip_ = new global::Akka.Cluster.Serialization.Proto.Msg.Gossip(); } - input.ReadMessage(Gossip); + input.ReadMessage(gossip_); break; } } } - #endif } - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + } + + /// + ///* + /// Prior to version 1.4.19 + /// Heartbeat + /// Sends an Address + /// Version 1.4.19 can deserialize this message but does not send it + /// + internal sealed partial class Heartbeat : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Heartbeat()); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[2]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Heartbeat() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Heartbeat(Heartbeat other) : this() { + From = other.from_ != null ? other.From.Clone() : null; + sequenceNr_ = other.sequenceNr_; + creationTime_ = other.creationTime_; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Heartbeat Clone() { + return new Heartbeat(this); + } + + /// Field number for the "from" field. + public const int FromFieldNumber = 1; + private global::Akka.Remote.Serialization.Proto.Msg.AddressData from_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Akka.Remote.Serialization.Proto.Msg.AddressData From { + get { return from_; } + set { + from_ = value; + } + } + + /// Field number for the "sequenceNr" field. + public const int SequenceNrFieldNumber = 2; + private long sequenceNr_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long SequenceNr { + get { return sequenceNr_; } + set { + sequenceNr_ = value; + } + } + + /// Field number for the "creationTime" field. + public const int CreationTimeFieldNumber = 3; + private long creationTime_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long CreationTime { + get { return creationTime_; } + set { + creationTime_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Heartbeat); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Heartbeat other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(From, other.From)) return false; + if (SequenceNr != other.SequenceNr) return false; + if (CreationTime != other.CreationTime) return false; + return true; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (from_ != null) hash ^= From.GetHashCode(); + if (SequenceNr != 0L) hash ^= SequenceNr.GetHashCode(); + if (CreationTime != 0L) hash ^= CreationTime.GetHashCode(); + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (from_ != null) { + output.WriteRawTag(10); + output.WriteMessage(From); + } + if (SequenceNr != 0L) { + output.WriteRawTag(16); + output.WriteInt64(SequenceNr); + } + if (CreationTime != 0L) { + output.WriteRawTag(24); + output.WriteSInt64(CreationTime); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (from_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(From); + } + if (SequenceNr != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(SequenceNr); + } + if (CreationTime != 0L) { + size += 1 + pb::CodedOutputStream.ComputeSInt64Size(CreationTime); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Heartbeat other) { + if (other == null) { + return; + } + if (other.from_ != null) { + if (from_ == null) { + from_ = new global::Akka.Remote.Serialization.Proto.Msg.AddressData(); + } + From.MergeFrom(other.From); + } + if (other.SequenceNr != 0L) { + SequenceNr = other.SequenceNr; + } + if (other.CreationTime != 0L) { + CreationTime = other.CreationTime; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + public void MergeFrom(pb::CodedInputStream input) { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + input.SkipLastField(); break; case 10: { if (from_ == null) { - From = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); + from_ = new global::Akka.Remote.Serialization.Proto.Msg.AddressData(); } - input.ReadMessage(From); + input.ReadMessage(from_); break; } - case 18: { - if (gossip_ == null) { - Gossip = new global::Akka.Cluster.Serialization.Proto.Msg.Gossip(); + case 16: { + SequenceNr = input.ReadInt64(); + break; + } + case 24: { + CreationTime = input.ReadSInt64(); + break; + } + } + } + } + + } + + /// + ///* + /// Prior to version 1.4.19 + /// HeartbeatRsp + /// Sends an UniqueAddress + /// Version 1.4.19 can deserialize this message but does not send it + /// + internal sealed partial class HeartBeatResponse : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new HeartBeatResponse()); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[3]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public HeartBeatResponse() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public HeartBeatResponse(HeartBeatResponse other) : this() { + From = other.from_ != null ? other.From.Clone() : null; + sequenceNr_ = other.sequenceNr_; + creationTime_ = other.creationTime_; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public HeartBeatResponse Clone() { + return new HeartBeatResponse(this); + } + + /// Field number for the "from" field. + public const int FromFieldNumber = 1; + private global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress from_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress From { + get { return from_; } + set { + from_ = value; + } + } + + /// Field number for the "sequenceNr" field. + public const int SequenceNrFieldNumber = 2; + private long sequenceNr_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long SequenceNr { + get { return sequenceNr_; } + set { + sequenceNr_ = value; + } + } + + /// Field number for the "creationTime" field. + public const int CreationTimeFieldNumber = 3; + private long creationTime_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long CreationTime { + get { return creationTime_; } + set { + creationTime_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as HeartBeatResponse); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(HeartBeatResponse other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(From, other.From)) return false; + if (SequenceNr != other.SequenceNr) return false; + if (CreationTime != other.CreationTime) return false; + return true; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (from_ != null) hash ^= From.GetHashCode(); + if (SequenceNr != 0L) hash ^= SequenceNr.GetHashCode(); + if (CreationTime != 0L) hash ^= CreationTime.GetHashCode(); + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (from_ != null) { + output.WriteRawTag(10); + output.WriteMessage(From); + } + if (SequenceNr != 0L) { + output.WriteRawTag(16); + output.WriteInt64(SequenceNr); + } + if (CreationTime != 0L) { + output.WriteRawTag(24); + output.WriteInt64(CreationTime); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (from_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(From); + } + if (SequenceNr != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(SequenceNr); + } + if (CreationTime != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(CreationTime); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(HeartBeatResponse other) { + if (other == null) { + return; + } + if (other.from_ != null) { + if (from_ == null) { + from_ = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); + } + From.MergeFrom(other.From); + } + if (other.SequenceNr != 0L) { + SequenceNr = other.SequenceNr; + } + if (other.CreationTime != 0L) { + CreationTime = other.CreationTime; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (from_ == null) { + from_ = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); } - input.ReadMessage(Gossip); + input.ReadMessage(from_); + break; + } + case 16: { + SequenceNr = input.ReadInt64(); + break; + } + case 24: { + CreationTime = input.ReadInt64(); break; } } } } - #endif } /// /// Gossip Envelope /// - internal sealed partial class GossipEnvelope : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class GossipEnvelope : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GossipEnvelope()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pbr::MessageDescriptor Descriptor { - get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[2]; } + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[4]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -625,10 +840,9 @@ public GossipEnvelope() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public GossipEnvelope(GossipEnvelope other) : this() { - from_ = other.from_ != null ? other.from_.Clone() : null; - to_ = other.to_ != null ? other.to_.Clone() : null; + From = other.from_ != null ? other.From.Clone() : null; + To = other.to_ != null ? other.To.Clone() : null; serializedGossip_ = other.serializedGossip_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -685,7 +899,7 @@ public bool Equals(GossipEnvelope other) { if (!object.Equals(From, other.From)) return false; if (!object.Equals(To, other.To)) return false; if (SerializedGossip != other.SerializedGossip) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -694,9 +908,6 @@ public override int GetHashCode() { if (from_ != null) hash ^= From.GetHashCode(); if (to_ != null) hash ^= To.GetHashCode(); if (SerializedGossip.Length != 0) hash ^= SerializedGossip.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -707,9 +918,6 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else if (from_ != null) { output.WriteRawTag(10); output.WriteMessage(From); @@ -722,33 +930,8 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(26); output.WriteBytes(SerializedGossip); } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif } - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { - if (from_ != null) { - output.WriteRawTag(10); - output.WriteMessage(From); - } - if (to_ != null) { - output.WriteRawTag(18); - output.WriteMessage(To); - } - if (SerializedGossip.Length != 0) { - output.WriteRawTag(26); - output.WriteBytes(SerializedGossip); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } - } - #endif - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { int size = 0; @@ -761,9 +944,6 @@ public int CalculateSize() { if (SerializedGossip.Length != 0) { size += 1 + pb::CodedOutputStream.ComputeBytesSize(SerializedGossip); } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -774,77 +954,41 @@ public void MergeFrom(GossipEnvelope other) { } if (other.from_ != null) { if (from_ == null) { - From = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); + from_ = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); } From.MergeFrom(other.From); } if (other.to_ != null) { if (to_ == null) { - To = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); + to_ = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); } To.MergeFrom(other.To); } if (other.SerializedGossip.Length != 0) { SerializedGossip = other.SerializedGossip; } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 10: { - if (from_ == null) { - From = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); - } - input.ReadMessage(From); - break; - } - case 18: { - if (to_ == null) { - To = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); - } - input.ReadMessage(To); - break; - } - case 26: { - SerializedGossip = input.ReadBytes(); - break; - } - } - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + input.SkipLastField(); break; case 10: { if (from_ == null) { - From = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); + from_ = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); } - input.ReadMessage(From); + input.ReadMessage(from_); break; } case 18: { if (to_ == null) { - To = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); + to_ = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); } - input.ReadMessage(To); + input.ReadMessage(to_); break; } case 26: { @@ -854,26 +998,20 @@ public void MergeFrom(pb::CodedInputStream input) { } } } - #endif } /// /// Gossip Status /// - internal sealed partial class GossipStatus : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class GossipStatus : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GossipStatus()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pbr::MessageDescriptor Descriptor { - get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[3]; } + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[5]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -890,10 +1028,9 @@ public GossipStatus() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public GossipStatus(GossipStatus other) : this() { - from_ = other.from_ != null ? other.from_.Clone() : null; + From = other.from_ != null ? other.From.Clone() : null; allHashes_ = other.allHashes_.Clone(); - version_ = other.version_ != null ? other.version_.Clone() : null; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + Version = other.version_ != null ? other.Version.Clone() : null; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -949,7 +1086,7 @@ public bool Equals(GossipStatus other) { if (!object.Equals(From, other.From)) return false; if(!allHashes_.Equals(other.allHashes_)) return false; if (!object.Equals(Version, other.Version)) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -958,9 +1095,6 @@ public override int GetHashCode() { if (from_ != null) hash ^= From.GetHashCode(); hash ^= allHashes_.GetHashCode(); if (version_ != null) hash ^= Version.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -971,9 +1105,6 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else if (from_ != null) { output.WriteRawTag(10); output.WriteMessage(From); @@ -983,29 +1114,7 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(26); output.WriteMessage(Version); } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { - if (from_ != null) { - output.WriteRawTag(10); - output.WriteMessage(From); - } - allHashes_.WriteTo(ref output, _repeated_allHashes_codec); - if (version_ != null) { - output.WriteRawTag(26); - output.WriteMessage(Version); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } } - #endif [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { @@ -1017,117 +1126,72 @@ public int CalculateSize() { if (version_ != null) { size += 1 + pb::CodedOutputStream.ComputeMessageSize(Version); } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(GossipStatus other) { - if (other == null) { - return; - } - if (other.from_ != null) { - if (from_ == null) { - From = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); - } - From.MergeFrom(other.From); - } - allHashes_.Add(other.allHashes_); - if (other.version_ != null) { - if (version_ == null) { - Version = new global::Akka.Cluster.Serialization.Proto.Msg.VectorClock(); - } - Version.MergeFrom(other.Version); - } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 10: { - if (from_ == null) { - From = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); - } - input.ReadMessage(From); - break; - } - case 18: { - allHashes_.AddEntriesFrom(input, _repeated_allHashes_codec); - break; - } - case 26: { - if (version_ == null) { - Version = new global::Akka.Cluster.Serialization.Proto.Msg.VectorClock(); - } - input.ReadMessage(Version); - break; - } + public void MergeFrom(GossipStatus other) { + if (other == null) { + return; + } + if (other.from_ != null) { + if (from_ == null) { + from_ = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); + } + From.MergeFrom(other.From); + } + allHashes_.Add(other.allHashes_); + if (other.version_ != null) { + if (version_ == null) { + version_ = new global::Akka.Cluster.Serialization.Proto.Msg.VectorClock(); } + Version.MergeFrom(other.Version); } - #endif } - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + public void MergeFrom(pb::CodedInputStream input) { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + input.SkipLastField(); break; case 10: { if (from_ == null) { - From = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); + from_ = new global::Akka.Cluster.Serialization.Proto.Msg.UniqueAddress(); } - input.ReadMessage(From); + input.ReadMessage(from_); break; } case 18: { - allHashes_.AddEntriesFrom(ref input, _repeated_allHashes_codec); + allHashes_.AddEntriesFrom(input, _repeated_allHashes_codec); break; } case 26: { if (version_ == null) { - Version = new global::Akka.Cluster.Serialization.Proto.Msg.VectorClock(); + version_ = new global::Akka.Cluster.Serialization.Proto.Msg.VectorClock(); } - input.ReadMessage(Version); + input.ReadMessage(version_); break; } } } } - #endif } /// /// Gossip /// - internal sealed partial class Gossip : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class Gossip : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Gossip()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pbr::MessageDescriptor Descriptor { - get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[4]; } + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[6]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -1148,10 +1212,9 @@ public Gossip(Gossip other) : this() { allRoles_ = other.allRoles_.Clone(); allHashes_ = other.allHashes_.Clone(); members_ = other.members_.Clone(); - overview_ = other.overview_ != null ? other.overview_.Clone() : null; - version_ = other.version_ != null ? other.version_.Clone() : null; + Overview = other.overview_ != null ? other.Overview.Clone() : null; + Version = other.version_ != null ? other.Version.Clone() : null; allAppVersions_ = other.allAppVersions_.Clone(); - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -1251,7 +1314,7 @@ public bool Equals(Gossip other) { if (!object.Equals(Overview, other.Overview)) return false; if (!object.Equals(Version, other.Version)) return false; if(!allAppVersions_.Equals(other.allAppVersions_)) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -1264,9 +1327,6 @@ public override int GetHashCode() { if (overview_ != null) hash ^= Overview.GetHashCode(); if (version_ != null) hash ^= Version.GetHashCode(); hash ^= allAppVersions_.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -1277,9 +1337,6 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else allAddresses_.WriteTo(output, _repeated_allAddresses_codec); allRoles_.WriteTo(output, _repeated_allRoles_codec); allHashes_.WriteTo(output, _repeated_allHashes_codec); @@ -1293,33 +1350,7 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteMessage(Version); } allAppVersions_.WriteTo(output, _repeated_allAppVersions_codec); - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { - allAddresses_.WriteTo(ref output, _repeated_allAddresses_codec); - allRoles_.WriteTo(ref output, _repeated_allRoles_codec); - allHashes_.WriteTo(ref output, _repeated_allHashes_codec); - members_.WriteTo(ref output, _repeated_members_codec); - if (overview_ != null) { - output.WriteRawTag(42); - output.WriteMessage(Overview); - } - if (version_ != null) { - output.WriteRawTag(50); - output.WriteMessage(Version); - } - allAppVersions_.WriteTo(ref output, _repeated_allAppVersions_codec); - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } } - #endif [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { @@ -1335,9 +1366,6 @@ public int CalculateSize() { size += 1 + pb::CodedOutputStream.ComputeMessageSize(Version); } size += allAppVersions_.CalculateSize(_repeated_allAppVersions_codec); - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -1352,30 +1380,26 @@ public void MergeFrom(Gossip other) { members_.Add(other.members_); if (other.overview_ != null) { if (overview_ == null) { - Overview = new global::Akka.Cluster.Serialization.Proto.Msg.GossipOverview(); + overview_ = new global::Akka.Cluster.Serialization.Proto.Msg.GossipOverview(); } Overview.MergeFrom(other.Overview); } if (other.version_ != null) { if (version_ == null) { - Version = new global::Akka.Cluster.Serialization.Proto.Msg.VectorClock(); + version_ = new global::Akka.Cluster.Serialization.Proto.Msg.VectorClock(); } Version.MergeFrom(other.Version); } allAppVersions_.Add(other.allAppVersions_); - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + input.SkipLastField(); break; case 10: { allAddresses_.AddEntriesFrom(input, _repeated_allAddresses_codec); @@ -1395,16 +1419,16 @@ public void MergeFrom(pb::CodedInputStream input) { } case 42: { if (overview_ == null) { - Overview = new global::Akka.Cluster.Serialization.Proto.Msg.GossipOverview(); + overview_ = new global::Akka.Cluster.Serialization.Proto.Msg.GossipOverview(); } - input.ReadMessage(Overview); + input.ReadMessage(overview_); break; } case 50: { if (version_ == null) { - Version = new global::Akka.Cluster.Serialization.Proto.Msg.VectorClock(); + version_ = new global::Akka.Cluster.Serialization.Proto.Msg.VectorClock(); } - input.ReadMessage(Version); + input.ReadMessage(version_); break; } case 66: { @@ -1413,75 +1437,21 @@ public void MergeFrom(pb::CodedInputStream input) { } } } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); - break; - case 10: { - allAddresses_.AddEntriesFrom(ref input, _repeated_allAddresses_codec); - break; - } - case 18: { - allRoles_.AddEntriesFrom(ref input, _repeated_allRoles_codec); - break; - } - case 26: { - allHashes_.AddEntriesFrom(ref input, _repeated_allHashes_codec); - break; - } - case 34: { - members_.AddEntriesFrom(ref input, _repeated_members_codec); - break; - } - case 42: { - if (overview_ == null) { - Overview = new global::Akka.Cluster.Serialization.Proto.Msg.GossipOverview(); - } - input.ReadMessage(Overview); - break; - } - case 50: { - if (version_ == null) { - Version = new global::Akka.Cluster.Serialization.Proto.Msg.VectorClock(); - } - input.ReadMessage(Version); - break; - } - case 66: { - allAppVersions_.AddEntriesFrom(ref input, _repeated_allAppVersions_codec); - break; - } - } - } } - #endif } /// /// Gossip Overview /// - internal sealed partial class GossipOverview : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class GossipOverview : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GossipOverview()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pbr::MessageDescriptor Descriptor { - get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[5]; } + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[7]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -1500,7 +1470,6 @@ public GossipOverview() { public GossipOverview(GossipOverview other) : this() { seen_ = other.seen_.Clone(); observerReachability_ = other.observerReachability_.Clone(); - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -1546,7 +1515,7 @@ public bool Equals(GossipOverview other) { } if(!seen_.Equals(other.seen_)) return false; if(!observerReachability_.Equals(other.observerReachability_)) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -1554,9 +1523,6 @@ public override int GetHashCode() { int hash = 1; hash ^= seen_.GetHashCode(); hash ^= observerReachability_.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -1567,36 +1533,15 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else seen_.WriteTo(output, _repeated_seen_codec); observerReachability_.WriteTo(output, _repeated_observerReachability_codec); - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { - seen_.WriteTo(ref output, _repeated_seen_codec); - observerReachability_.WriteTo(ref output, _repeated_observerReachability_codec); - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } } - #endif [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { int size = 0; size += seen_.CalculateSize(_repeated_seen_codec); size += observerReachability_.CalculateSize(_repeated_observerReachability_codec); - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -1607,19 +1552,15 @@ public void MergeFrom(GossipOverview other) { } seen_.Add(other.seen_); observerReachability_.Add(other.observerReachability_); - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + input.SkipLastField(); break; case 10: case 8: { @@ -1632,50 +1573,21 @@ public void MergeFrom(pb::CodedInputStream input) { } } } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); - break; - case 10: - case 8: { - seen_.AddEntriesFrom(ref input, _repeated_seen_codec); - break; - } - case 18: { - observerReachability_.AddEntriesFrom(ref input, _repeated_observerReachability_codec); - break; - } - } - } } - #endif } /// /// Reachability /// - internal sealed partial class ObserverReachability : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class ObserverReachability : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ObserverReachability()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pbr::MessageDescriptor Descriptor { - get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[6]; } + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[8]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -1695,7 +1607,6 @@ public ObserverReachability(ObserverReachability other) : this() { addressIndex_ = other.addressIndex_; version_ = other.version_; subjectReachability_ = other.subjectReachability_.Clone(); - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -1751,7 +1662,7 @@ public bool Equals(ObserverReachability other) { if (AddressIndex != other.AddressIndex) return false; if (Version != other.Version) return false; if(!subjectReachability_.Equals(other.subjectReachability_)) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -1760,9 +1671,6 @@ public override int GetHashCode() { if (AddressIndex != 0) hash ^= AddressIndex.GetHashCode(); if (Version != 0L) hash ^= Version.GetHashCode(); hash ^= subjectReachability_.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -1773,9 +1681,6 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else if (AddressIndex != 0) { output.WriteRawTag(8); output.WriteInt32(AddressIndex); @@ -1785,29 +1690,7 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(32); output.WriteInt64(Version); } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { - if (AddressIndex != 0) { - output.WriteRawTag(8); - output.WriteInt32(AddressIndex); - } - subjectReachability_.WriteTo(ref output, _repeated_subjectReachability_codec); - if (Version != 0L) { - output.WriteRawTag(32); - output.WriteInt64(Version); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } } - #endif [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { @@ -1819,9 +1702,6 @@ public int CalculateSize() { size += 1 + pb::CodedOutputStream.ComputeInt64Size(Version); } size += subjectReachability_.CalculateSize(_repeated_subjectReachability_codec); - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -1837,19 +1717,15 @@ public void MergeFrom(ObserverReachability other) { Version = other.Version; } subjectReachability_.Add(other.subjectReachability_); - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + input.SkipLastField(); break; case 8: { AddressIndex = input.ReadInt32(); @@ -1865,50 +1741,18 @@ public void MergeFrom(pb::CodedInputStream input) { } } } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); - break; - case 8: { - AddressIndex = input.ReadInt32(); - break; - } - case 18: { - subjectReachability_.AddEntriesFrom(ref input, _repeated_subjectReachability_codec); - break; - } - case 32: { - Version = input.ReadInt64(); - break; - } - } - } } - #endif } - internal sealed partial class SubjectReachability : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class SubjectReachability : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SubjectReachability()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pbr::MessageDescriptor Descriptor { - get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[7]; } + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[9]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -1928,7 +1772,6 @@ public SubjectReachability(SubjectReachability other) : this() { addressIndex_ = other.addressIndex_; status_ = other.status_; version_ = other.version_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -1949,7 +1792,7 @@ public int AddressIndex { /// Field number for the "status" field. public const int StatusFieldNumber = 3; - private global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus status_ = global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus.Reachable; + private global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus status_ = 0; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus Status { get { return status_; } @@ -1985,18 +1828,15 @@ public bool Equals(SubjectReachability other) { if (AddressIndex != other.AddressIndex) return false; if (Status != other.Status) return false; if (Version != other.Version) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public override int GetHashCode() { int hash = 1; if (AddressIndex != 0) hash ^= AddressIndex.GetHashCode(); - if (Status != global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus.Reachable) hash ^= Status.GetHashCode(); + if (Status != 0) hash ^= Status.GetHashCode(); if (Version != 0L) hash ^= Version.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -2007,35 +1847,11 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else - if (AddressIndex != 0) { - output.WriteRawTag(8); - output.WriteInt32(AddressIndex); - } - if (Status != global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus.Reachable) { - output.WriteRawTag(24); - output.WriteEnum((int) Status); - } - if (Version != 0L) { - output.WriteRawTag(32); - output.WriteInt64(Version); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { if (AddressIndex != 0) { output.WriteRawTag(8); output.WriteInt32(AddressIndex); } - if (Status != global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus.Reachable) { + if (Status != 0) { output.WriteRawTag(24); output.WriteEnum((int) Status); } @@ -2043,11 +1859,7 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(32); output.WriteInt64(Version); } - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } } - #endif [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { @@ -2055,15 +1867,12 @@ public int CalculateSize() { if (AddressIndex != 0) { size += 1 + pb::CodedOutputStream.ComputeInt32Size(AddressIndex); } - if (Status != global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus.Reachable) { + if (Status != 0) { size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Status); } if (Version != 0L) { size += 1 + pb::CodedOutputStream.ComputeInt64Size(Version); } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -2075,58 +1884,28 @@ public void MergeFrom(SubjectReachability other) { if (other.AddressIndex != 0) { AddressIndex = other.AddressIndex; } - if (other.Status != global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus.Reachable) { + if (other.Status != 0) { Status = other.Status; } if (other.Version != 0L) { Version = other.Version; } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 8: { - AddressIndex = input.ReadInt32(); - break; - } - case 24: { - Status = (global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus) input.ReadEnum(); - break; - } - case 32: { - Version = input.ReadInt64(); - break; - } - } - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + input.SkipLastField(); break; case 8: { AddressIndex = input.ReadInt32(); break; } case 24: { - Status = (global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus) input.ReadEnum(); + status_ = (global::Akka.Cluster.Serialization.Proto.Msg.SubjectReachability.Types.ReachabilityStatus) input.ReadEnum(); break; } case 32: { @@ -2136,7 +1915,6 @@ public void MergeFrom(pb::CodedInputStream input) { } } } - #endif #region Nested types /// Container for nested types declared in the SubjectReachability message type. @@ -2156,20 +1934,14 @@ internal enum ReachabilityStatus { /// /// Member /// - internal sealed partial class Member : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class Member : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Member()); - private pb::UnknownFieldSet _unknownFields; - private int _hasBits0; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pbr::MessageDescriptor Descriptor { - get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[8]; } + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[10]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -2186,13 +1958,11 @@ public Member() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public Member(Member other) : this() { - _hasBits0 = other._hasBits0; addressIndex_ = other.addressIndex_; upNumber_ = other.upNumber_; status_ = other.status_; rolesIndexes_ = other.rolesIndexes_.Clone(); appVersionIndex_ = other.appVersionIndex_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -2224,7 +1994,7 @@ public int UpNumber { /// Field number for the "status" field. public const int StatusFieldNumber = 3; - private global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus status_ = global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus.Joining; + private global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus status_ = 0; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus Status { get { return status_; } @@ -2248,22 +2018,11 @@ public int UpNumber { private int appVersionIndex_; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int AppVersionIndex { - get { if ((_hasBits0 & 1) != 0) { return appVersionIndex_; } else { return 0; } } + get { return appVersionIndex_; } set { - _hasBits0 |= 1; appVersionIndex_ = value; } } - /// Gets whether the "appVersionIndex" field is set - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool HasAppVersionIndex { - get { return (_hasBits0 & 1) != 0; } - } - /// Clears the value of the "appVersionIndex" field - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void ClearAppVersionIndex() { - _hasBits0 &= ~1; - } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public override bool Equals(object other) { @@ -2283,7 +2042,7 @@ public bool Equals(Member other) { if (Status != other.Status) return false; if(!rolesIndexes_.Equals(other.rolesIndexes_)) return false; if (AppVersionIndex != other.AppVersionIndex) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -2291,12 +2050,9 @@ public override int GetHashCode() { int hash = 1; if (AddressIndex != 0) hash ^= AddressIndex.GetHashCode(); if (UpNumber != 0) hash ^= UpNumber.GetHashCode(); - if (Status != global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus.Joining) hash ^= Status.GetHashCode(); + if (Status != 0) hash ^= Status.GetHashCode(); hash ^= rolesIndexes_.GetHashCode(); - if (HasAppVersionIndex) hash ^= AppVersionIndex.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } + if (AppVersionIndex != 0) hash ^= AppVersionIndex.GetHashCode(); return hash; } @@ -2307,9 +2063,6 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else if (AddressIndex != 0) { output.WriteRawTag(8); output.WriteInt32(AddressIndex); @@ -2318,46 +2071,16 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(16); output.WriteInt32(UpNumber); } - if (Status != global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus.Joining) { + if (Status != 0) { output.WriteRawTag(24); output.WriteEnum((int) Status); } rolesIndexes_.WriteTo(output, _repeated_rolesIndexes_codec); - if (HasAppVersionIndex) { - output.WriteRawTag(40); - output.WriteInt32(AppVersionIndex); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { - if (AddressIndex != 0) { - output.WriteRawTag(8); - output.WriteInt32(AddressIndex); - } - if (UpNumber != 0) { - output.WriteRawTag(16); - output.WriteInt32(UpNumber); - } - if (Status != global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus.Joining) { - output.WriteRawTag(24); - output.WriteEnum((int) Status); - } - rolesIndexes_.WriteTo(ref output, _repeated_rolesIndexes_codec); - if (HasAppVersionIndex) { + if (AppVersionIndex != 0) { output.WriteRawTag(40); output.WriteInt32(AppVersionIndex); } - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } } - #endif [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { @@ -2365,88 +2088,46 @@ public int CalculateSize() { if (AddressIndex != 0) { size += 1 + pb::CodedOutputStream.ComputeInt32Size(AddressIndex); } - if (UpNumber != 0) { - size += 1 + pb::CodedOutputStream.ComputeInt32Size(UpNumber); - } - if (Status != global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus.Joining) { - size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Status); - } - size += rolesIndexes_.CalculateSize(_repeated_rolesIndexes_codec); - if (HasAppVersionIndex) { - size += 1 + pb::CodedOutputStream.ComputeInt32Size(AppVersionIndex); - } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } - return size; - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(Member other) { - if (other == null) { - return; - } - if (other.AddressIndex != 0) { - AddressIndex = other.AddressIndex; - } - if (other.UpNumber != 0) { - UpNumber = other.UpNumber; - } - if (other.Status != global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus.Joining) { - Status = other.Status; - } - rolesIndexes_.Add(other.rolesIndexes_); - if (other.HasAppVersionIndex) { - AppVersionIndex = other.AppVersionIndex; - } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); - } - - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 8: { - AddressIndex = input.ReadInt32(); - break; - } - case 16: { - UpNumber = input.ReadInt32(); - break; - } - case 24: { - Status = (global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus) input.ReadEnum(); - break; - } - case 34: - case 32: { - rolesIndexes_.AddEntriesFrom(input, _repeated_rolesIndexes_codec); - break; - } - case 40: { - AppVersionIndex = input.ReadInt32(); - break; - } - } + if (UpNumber != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(UpNumber); + } + if (Status != 0) { + size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Status); + } + size += rolesIndexes_.CalculateSize(_repeated_rolesIndexes_codec); + if (AppVersionIndex != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(AppVersionIndex); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Member other) { + if (other == null) { + return; + } + if (other.AddressIndex != 0) { + AddressIndex = other.AddressIndex; + } + if (other.UpNumber != 0) { + UpNumber = other.UpNumber; + } + if (other.Status != 0) { + Status = other.Status; + } + rolesIndexes_.Add(other.rolesIndexes_); + if (other.AppVersionIndex != 0) { + AppVersionIndex = other.AppVersionIndex; } - #endif } - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + public void MergeFrom(pb::CodedInputStream input) { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + input.SkipLastField(); break; case 8: { AddressIndex = input.ReadInt32(); @@ -2457,12 +2138,12 @@ public void MergeFrom(pb::CodedInputStream input) { break; } case 24: { - Status = (global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus) input.ReadEnum(); + status_ = (global::Akka.Cluster.Serialization.Proto.Msg.Member.Types.MemberStatus) input.ReadEnum(); break; } case 34: case 32: { - rolesIndexes_.AddEntriesFrom(ref input, _repeated_rolesIndexes_codec); + rolesIndexes_.AddEntriesFrom(input, _repeated_rolesIndexes_codec); break; } case 40: { @@ -2472,7 +2153,6 @@ public void MergeFrom(pb::CodedInputStream input) { } } } - #endif #region Nested types /// Container for nested types declared in the Member message type. @@ -2496,19 +2176,14 @@ internal enum MemberStatus { /// /// Vector Clock /// - internal sealed partial class VectorClock : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class VectorClock : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new VectorClock()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pbr::MessageDescriptor Descriptor { - get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[9]; } + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[11]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -2527,7 +2202,6 @@ public VectorClock() { public VectorClock(VectorClock other) : this() { timestamp_ = other.timestamp_; versions_ = other.versions_.Clone(); - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -2574,7 +2248,7 @@ public bool Equals(VectorClock other) { } if (Timestamp != other.Timestamp) return false; if(!versions_.Equals(other.versions_)) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -2582,9 +2256,6 @@ public override int GetHashCode() { int hash = 1; if (Timestamp != 0L) hash ^= Timestamp.GetHashCode(); hash ^= versions_.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -2595,34 +2266,13 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else if (Timestamp != 0L) { output.WriteRawTag(8); output.WriteInt64(Timestamp); } versions_.WriteTo(output, _repeated_versions_codec); - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif } - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { - if (Timestamp != 0L) { - output.WriteRawTag(8); - output.WriteInt64(Timestamp); - } - versions_.WriteTo(ref output, _repeated_versions_codec); - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } - } - #endif - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { int size = 0; @@ -2630,9 +2280,6 @@ public int CalculateSize() { size += 1 + pb::CodedOutputStream.ComputeInt64Size(Timestamp); } size += versions_.CalculateSize(_repeated_versions_codec); - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -2645,19 +2292,15 @@ public void MergeFrom(VectorClock other) { Timestamp = other.Timestamp; } versions_.Add(other.versions_); - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + input.SkipLastField(); break; case 8: { Timestamp = input.ReadInt64(); @@ -2669,42 +2312,14 @@ public void MergeFrom(pb::CodedInputStream input) { } } } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); - break; - case 8: { - Timestamp = input.ReadInt64(); - break; - } - case 18: { - versions_.AddEntriesFrom(ref input, _repeated_versions_codec); - break; - } - } - } } - #endif #region Nested types /// Container for nested types declared in the VectorClock message type. [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static partial class Types { - internal sealed partial class Version : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class Version : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Version()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } @@ -2729,7 +2344,6 @@ public Version() { public Version(Version other) : this() { hashIndex_ = other.hashIndex_; timestamp_ = other.timestamp_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -2774,7 +2388,7 @@ public bool Equals(Version other) { } if (HashIndex != other.HashIndex) return false; if (Timestamp != other.Timestamp) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -2782,9 +2396,6 @@ public override int GetHashCode() { int hash = 1; if (HashIndex != 0) hash ^= HashIndex.GetHashCode(); if (Timestamp != 0L) hash ^= Timestamp.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -2795,26 +2406,6 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else - if (HashIndex != 0) { - output.WriteRawTag(8); - output.WriteInt32(HashIndex); - } - if (Timestamp != 0L) { - output.WriteRawTag(16); - output.WriteInt64(Timestamp); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { if (HashIndex != 0) { output.WriteRawTag(8); output.WriteInt32(HashIndex); @@ -2823,11 +2414,7 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(16); output.WriteInt64(Timestamp); } - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } } - #endif [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { @@ -2838,9 +2425,6 @@ public int CalculateSize() { if (Timestamp != 0L) { size += 1 + pb::CodedOutputStream.ComputeInt64Size(Timestamp); } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -2855,41 +2439,15 @@ public void MergeFrom(Version other) { if (other.Timestamp != 0L) { Timestamp = other.Timestamp; } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 8: { - HashIndex = input.ReadInt32(); - break; - } - case 16: { - Timestamp = input.ReadInt64(); - break; - } - } - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + input.SkipLastField(); break; case 8: { HashIndex = input.ReadInt32(); @@ -2902,7 +2460,6 @@ public void MergeFrom(pb::CodedInputStream input) { } } } - #endif } @@ -2914,19 +2471,14 @@ public void MergeFrom(pb::CodedInputStream input) { /// /// Defines a remote address with uid. /// - internal sealed partial class UniqueAddress : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class UniqueAddress : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new UniqueAddress()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pbr::MessageDescriptor Descriptor { - get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[10]; } + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[12]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -2943,9 +2495,8 @@ public UniqueAddress() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public UniqueAddress(UniqueAddress other) : this() { - address_ = other.address_ != null ? other.address_.Clone() : null; + Address = other.address_ != null ? other.Address.Clone() : null; uid_ = other.uid_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -2990,7 +2541,7 @@ public bool Equals(UniqueAddress other) { } if (!object.Equals(Address, other.Address)) return false; if (Uid != other.Uid) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -2998,9 +2549,6 @@ public override int GetHashCode() { int hash = 1; if (address_ != null) hash ^= Address.GetHashCode(); if (Uid != 0) hash ^= Uid.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -3011,26 +2559,6 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else - if (address_ != null) { - output.WriteRawTag(10); - output.WriteMessage(Address); - } - if (Uid != 0) { - output.WriteRawTag(16); - output.WriteUInt32(Uid); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { if (address_ != null) { output.WriteRawTag(10); output.WriteMessage(Address); @@ -3039,11 +2567,7 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(16); output.WriteUInt32(Uid); } - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } } - #endif [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { @@ -3054,9 +2578,6 @@ public int CalculateSize() { if (Uid != 0) { size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Uid); } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -3067,57 +2588,28 @@ public void MergeFrom(UniqueAddress other) { } if (other.address_ != null) { if (address_ == null) { - Address = new global::Akka.Remote.Serialization.Proto.Msg.AddressData(); + address_ = new global::Akka.Remote.Serialization.Proto.Msg.AddressData(); } Address.MergeFrom(other.Address); } if (other.Uid != 0) { Uid = other.Uid; } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 10: { - if (address_ == null) { - Address = new global::Akka.Remote.Serialization.Proto.Msg.AddressData(); - } - input.ReadMessage(Address); - break; - } - case 16: { - Uid = input.ReadUInt32(); - break; - } - } - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + input.SkipLastField(); break; case 10: { if (address_ == null) { - Address = new global::Akka.Remote.Serialization.Proto.Msg.AddressData(); + address_ = new global::Akka.Remote.Serialization.Proto.Msg.AddressData(); } - input.ReadMessage(Address); + input.ReadMessage(address_); break; } case 16: { @@ -3127,23 +2619,17 @@ public void MergeFrom(pb::CodedInputStream input) { } } } - #endif } - internal sealed partial class ClusterRouterPool : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class ClusterRouterPool : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ClusterRouterPool()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pbr::MessageDescriptor Descriptor { - get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[11]; } + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[13]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -3160,9 +2646,8 @@ public ClusterRouterPool() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public ClusterRouterPool(ClusterRouterPool other) : this() { - pool_ = other.pool_ != null ? other.pool_.Clone() : null; - settings_ = other.settings_ != null ? other.settings_.Clone() : null; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + Pool = other.pool_ != null ? other.Pool.Clone() : null; + Settings = other.settings_ != null ? other.Settings.Clone() : null; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -3207,7 +2692,7 @@ public bool Equals(ClusterRouterPool other) { } if (!object.Equals(Pool, other.Pool)) return false; if (!object.Equals(Settings, other.Settings)) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -3215,9 +2700,6 @@ public override int GetHashCode() { int hash = 1; if (pool_ != null) hash ^= Pool.GetHashCode(); if (settings_ != null) hash ^= Settings.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -3228,26 +2710,6 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else - if (pool_ != null) { - output.WriteRawTag(10); - output.WriteMessage(Pool); - } - if (settings_ != null) { - output.WriteRawTag(18); - output.WriteMessage(Settings); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { if (pool_ != null) { output.WriteRawTag(10); output.WriteMessage(Pool); @@ -3256,11 +2718,7 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(18); output.WriteMessage(Settings); } - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } } - #endif [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { @@ -3271,9 +2729,6 @@ public int CalculateSize() { if (settings_ != null) { size += 1 + pb::CodedOutputStream.ComputeMessageSize(Settings); } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -3284,92 +2739,54 @@ public void MergeFrom(ClusterRouterPool other) { } if (other.pool_ != null) { if (pool_ == null) { - Pool = new global::Akka.Cluster.Serialization.Proto.Msg.Pool(); + pool_ = new global::Akka.Cluster.Serialization.Proto.Msg.Pool(); } Pool.MergeFrom(other.Pool); } if (other.settings_ != null) { if (settings_ == null) { - Settings = new global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPoolSettings(); + settings_ = new global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPoolSettings(); } Settings.MergeFrom(other.Settings); } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 10: { - if (pool_ == null) { - Pool = new global::Akka.Cluster.Serialization.Proto.Msg.Pool(); - } - input.ReadMessage(Pool); - break; - } - case 18: { - if (settings_ == null) { - Settings = new global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPoolSettings(); - } - input.ReadMessage(Settings); - break; - } - } - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + input.SkipLastField(); break; case 10: { if (pool_ == null) { - Pool = new global::Akka.Cluster.Serialization.Proto.Msg.Pool(); + pool_ = new global::Akka.Cluster.Serialization.Proto.Msg.Pool(); } - input.ReadMessage(Pool); + input.ReadMessage(pool_); break; } case 18: { if (settings_ == null) { - Settings = new global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPoolSettings(); + settings_ = new global::Akka.Cluster.Serialization.Proto.Msg.ClusterRouterPoolSettings(); } - input.ReadMessage(Settings); + input.ReadMessage(settings_); break; } } } } - #endif } - internal sealed partial class Pool : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class Pool : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Pool()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pbr::MessageDescriptor Descriptor { - get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[12]; } + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[14]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -3389,7 +2806,6 @@ public Pool(Pool other) : this() { serializerId_ = other.serializerId_; manifest_ = other.manifest_; data_ = other.data_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -3446,7 +2862,7 @@ public bool Equals(Pool other) { if (SerializerId != other.SerializerId) return false; if (Manifest != other.Manifest) return false; if (Data != other.Data) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -3455,9 +2871,6 @@ public override int GetHashCode() { if (SerializerId != 0) hash ^= SerializerId.GetHashCode(); if (Manifest.Length != 0) hash ^= Manifest.GetHashCode(); if (Data.Length != 0) hash ^= Data.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -3468,30 +2881,6 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else - if (SerializerId != 0) { - output.WriteRawTag(8); - output.WriteUInt32(SerializerId); - } - if (Manifest.Length != 0) { - output.WriteRawTag(18); - output.WriteString(Manifest); - } - if (Data.Length != 0) { - output.WriteRawTag(26); - output.WriteBytes(Data); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { if (SerializerId != 0) { output.WriteRawTag(8); output.WriteUInt32(SerializerId); @@ -3504,11 +2893,7 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(26); output.WriteBytes(Data); } - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } } - #endif [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { @@ -3522,9 +2907,6 @@ public int CalculateSize() { if (Data.Length != 0) { size += 1 + pb::CodedOutputStream.ComputeBytesSize(Data); } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -3542,45 +2924,15 @@ public void MergeFrom(Pool other) { if (other.Data.Length != 0) { Data = other.Data; } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 8: { - SerializerId = input.ReadUInt32(); - break; - } - case 18: { - Manifest = input.ReadString(); - break; - } - case 26: { - Data = input.ReadBytes(); - break; - } - } - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + input.SkipLastField(); break; case 8: { SerializerId = input.ReadUInt32(); @@ -3597,23 +2949,17 @@ public void MergeFrom(pb::CodedInputStream input) { } } } - #endif } - internal sealed partial class ClusterRouterPoolSettings : pb::IMessage - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - , pb::IBufferMessage - #endif - { + internal sealed partial class ClusterRouterPoolSettings : pb::IMessage { private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ClusterRouterPoolSettings()); - private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pb::MessageParser Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public static pbr::MessageDescriptor Descriptor { - get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[13]; } + get { return global::Akka.Cluster.Serialization.Proto.Msg.ClusterMessagesReflection.Descriptor.MessageTypes[15]; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -3634,7 +2980,6 @@ public ClusterRouterPoolSettings(ClusterRouterPoolSettings other) : this() { maxInstancesPerNode_ = other.maxInstancesPerNode_; allowLocalRoutees_ = other.allowLocalRoutees_; useRole_ = other.useRole_; - _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -3703,7 +3048,7 @@ public bool Equals(ClusterRouterPoolSettings other) { if (MaxInstancesPerNode != other.MaxInstancesPerNode) return false; if (AllowLocalRoutees != other.AllowLocalRoutees) return false; if (UseRole != other.UseRole) return false; - return Equals(_unknownFields, other._unknownFields); + return true; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] @@ -3713,9 +3058,6 @@ public override int GetHashCode() { if (MaxInstancesPerNode != 0) hash ^= MaxInstancesPerNode.GetHashCode(); if (AllowLocalRoutees != false) hash ^= AllowLocalRoutees.GetHashCode(); if (UseRole.Length != 0) hash ^= UseRole.GetHashCode(); - if (_unknownFields != null) { - hash ^= _unknownFields.GetHashCode(); - } return hash; } @@ -3726,34 +3068,6 @@ public override string ToString() { [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void WriteTo(pb::CodedOutputStream output) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - output.WriteRawMessage(this); - #else - if (TotalInstances != 0) { - output.WriteRawTag(8); - output.WriteUInt32(TotalInstances); - } - if (MaxInstancesPerNode != 0) { - output.WriteRawTag(16); - output.WriteUInt32(MaxInstancesPerNode); - } - if (AllowLocalRoutees != false) { - output.WriteRawTag(24); - output.WriteBool(AllowLocalRoutees); - } - if (UseRole.Length != 0) { - output.WriteRawTag(34); - output.WriteString(UseRole); - } - if (_unknownFields != null) { - _unknownFields.WriteTo(output); - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { if (TotalInstances != 0) { output.WriteRawTag(8); output.WriteUInt32(TotalInstances); @@ -3770,11 +3084,7 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(34); output.WriteString(UseRole); } - if (_unknownFields != null) { - _unknownFields.WriteTo(ref output); - } } - #endif [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public int CalculateSize() { @@ -3791,9 +3101,6 @@ public int CalculateSize() { if (UseRole.Length != 0) { size += 1 + pb::CodedOutputStream.ComputeStringSize(UseRole); } - if (_unknownFields != null) { - size += _unknownFields.CalculateSize(); - } return size; } @@ -3814,49 +3121,15 @@ public void MergeFrom(ClusterRouterPoolSettings other) { if (other.UseRole.Length != 0) { UseRole = other.UseRole; } - _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] public void MergeFrom(pb::CodedInputStream input) { - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - input.ReadRawMessage(this); - #else - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); - break; - case 8: { - TotalInstances = input.ReadUInt32(); - break; - } - case 16: { - MaxInstancesPerNode = input.ReadUInt32(); - break; - } - case 24: { - AllowLocalRoutees = input.ReadBool(); - break; - } - case 34: { - UseRole = input.ReadString(); - break; - } - } - } - #endif - } - - #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: - _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + input.SkipLastField(); break; case 8: { TotalInstances = input.ReadUInt32(); @@ -3877,7 +3150,6 @@ public void MergeFrom(pb::CodedInputStream input) { } } } - #endif } diff --git a/src/core/Akka.Cluster/VectorClock.cs b/src/core/Akka.Cluster/VectorClock.cs index 7d208883f25..4c5b7c6387c 100644 --- a/src/core/Akka.Cluster/VectorClock.cs +++ b/src/core/Akka.Cluster/VectorClock.cs @@ -40,7 +40,7 @@ public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; - return obj is VectorClock && Equals((VectorClock) obj); + return obj is VectorClock vc && Equals(vc); } public override int GetHashCode() @@ -119,22 +119,26 @@ public static Node FromHash(string hash) { return new Node(hash); } + + private static Node Hash(string name) { - var md5 = System.Security.Cryptography.MD5.Create(); - var inputBytes = Encoding.UTF8.GetBytes(name); - var hash = md5.ComputeHash(inputBytes); + // TODO: replace with Murmur3 or SHA512, some other consistent hash algorithm for FIPS complaince and no collisions in Akka.NET v1.5 or 2.0 + using(var md5 = System.Security.Cryptography.MD5.Create()){ + var inputBytes = Encoding.UTF8.GetBytes(name); + var hash = md5.ComputeHash(inputBytes); - var sb = new StringBuilder(); + var sb = new StringBuilder(); - foreach (var t in hash) - { - sb.Append(t.ToString("X2")); - } + foreach (var t in hash) + { + sb.Append(t.ToString("X2")); + } - return new Node(sb.ToString()); + return new Node(sb.ToString()); + } } /// @@ -167,7 +171,7 @@ public int CompareTo(Node other) /// /// Timestamp used by the . /// - internal class Timestamp + internal static class Timestamp { /// /// TBD @@ -338,7 +342,7 @@ public bool IsSameAs(VectorClock that) return left.IsConcurrentWith(right); } - private static readonly KeyValuePair CmpEndMarker = new KeyValuePair(Node.Create("endmarker"), long.MinValue); + private static readonly (Node, long) CmpEndMarker = (Node.Create("endmarker"), Timestamp.EndMarker); /// /// @@ -364,58 +368,55 @@ public bool IsSameAs(VectorClock that) /// The true ordering based on the contents of the vectorclock. internal Ordering CompareOnlyTo(VectorClock that, Ordering order) { - if (ReferenceEquals(this, that) || Versions.Equals(that.Versions)) return Ordering.Same; + if (ReferenceEquals(this, that) || ReferenceEquals(this.Versions, that.Versions)) return Ordering.Same; - return Compare(_versions.GetEnumerator(), that._versions.GetEnumerator(), order == Ordering.Concurrent ? Ordering.FullOrder : order); + return Compare(_versions.Select(x => (x.Key, x.Value)).GetEnumerator(), that._versions.Select(y => (y.Key, y.Value)).GetEnumerator(), order == Ordering.Concurrent ? Ordering.FullOrder : order); } - private static Ordering Compare(IEnumerator> i1, IEnumerator> i2, Ordering requestedOrder) + private static Ordering Compare(IEnumerator<(Node, long)> i1, IEnumerator<(Node, long)> i2, Ordering requestedOrder) { //TODO: Tail recursion issues? - Func, KeyValuePair, Ordering, Ordering> compareNext = null; - compareNext = - (nt1, nt2, currentOrder) => + Ordering CompareNext((Node, long) nt1, (Node, long) nt2, Ordering currentOrder) + { + if (requestedOrder != Ordering.FullOrder && currentOrder != Ordering.Same && currentOrder != requestedOrder) return currentOrder; + if (nt1.Equals(CmpEndMarker) && nt2.Equals(CmpEndMarker)) return currentOrder; + // i1 is empty but i2 is not, so i1 can only be Before + if (nt1.Equals(CmpEndMarker)) return currentOrder == Ordering.After ? Ordering.Concurrent : Ordering.Before; + // i2 is empty but i1 is not, so i1 can only be After + if (nt2.Equals(CmpEndMarker)) return currentOrder == Ordering.Before ? Ordering.Concurrent : Ordering.After; + // compare the nodes + var nc = nt1.Item1.CompareTo(nt2.Item1); + if (nc == 0) { - if (requestedOrder != Ordering.FullOrder && currentOrder != Ordering.Same && - currentOrder != requestedOrder) - return currentOrder; - if (nt1.Equals(CmpEndMarker) && nt2.Equals(CmpEndMarker)) return currentOrder; - // i1 is empty but i2 is not, so i1 can only be Before - if (nt1.Equals(CmpEndMarker)) - return currentOrder == Ordering.After ? Ordering.Concurrent : Ordering.Before; - // i2 is empty but i1 is not, so i1 can only be After - if (nt2.Equals(CmpEndMarker)) - return currentOrder == Ordering.Before ? Ordering.Concurrent : Ordering.After; - // compare the nodes - var nc = nt1.Key.CompareTo(nt2.Key); - if (nc == 0) - { - // both nodes exist compare the timestamps - // same timestamp so just continue with the next nodes - if (nt1.Value == nt2.Value) - return compareNext(NextOrElse(i1, CmpEndMarker), NextOrElse(i2, CmpEndMarker), currentOrder); - if (nt1.Value < nt2.Value) - { - // t1 is less than t2, so i1 can only be Before - if (currentOrder == Ordering.After) return Ordering.Concurrent; - return compareNext(NextOrElse(i1, CmpEndMarker), NextOrElse(i2, CmpEndMarker), - Ordering.Before); - } - if (currentOrder == Ordering.Before) return Ordering.Concurrent; - return compareNext(NextOrElse(i1, CmpEndMarker), NextOrElse(i2, CmpEndMarker), Ordering.After); - } - if (nc < 0) + // both nodes exist compare the timestamps + // same timestamp so just continue with the next nodes + if (nt1.Item2 == nt2.Item2) return CompareNext(NextOrElse(i1, CmpEndMarker), NextOrElse(i2, CmpEndMarker), currentOrder); + if (nt1.Item2 < nt2.Item2) { - // this node only exists in i1 so i1 can only be After - if (currentOrder == Ordering.Before) return Ordering.Concurrent; - return compareNext(NextOrElse(i1, CmpEndMarker), nt2, Ordering.After); + // t1 is less than t2, so i1 can only be Before + if (currentOrder == Ordering.After) return Ordering.Concurrent; + return CompareNext(NextOrElse(i1, CmpEndMarker), NextOrElse(i2, CmpEndMarker), Ordering.Before); } - // this node only exists in i2 so i1 can only be Before - if (currentOrder == Ordering.After) return Ordering.Concurrent; - return compareNext(nt1, NextOrElse(i2, CmpEndMarker), Ordering.Before); - }; - return compareNext(NextOrElse(i1, CmpEndMarker), NextOrElse(i2, CmpEndMarker), Ordering.Same); + if (currentOrder == Ordering.Before) return Ordering.Concurrent; + return CompareNext(NextOrElse(i1, CmpEndMarker), NextOrElse(i2, CmpEndMarker), Ordering.After); + } + + if (nc < 0) + { + // this node only exists in i1 so i1 can only be After + if (currentOrder == Ordering.Before) return Ordering.Concurrent; + return CompareNext(NextOrElse(i1, CmpEndMarker), nt2, Ordering.After); + } + + // this node only exists in i2 so i1 can only be Before + if (currentOrder == Ordering.After) return Ordering.Concurrent; + return CompareNext(nt1, NextOrElse(i2, CmpEndMarker), Ordering.Before); + } + + using(i1) + using(i2) + return CompareNext(NextOrElse(i1, CmpEndMarker), NextOrElse(i2, CmpEndMarker), Ordering.Same); } private static T NextOrElse(IEnumerator iter, T @default) diff --git a/src/core/Akka.Docs.Tests/Networking/Serialization/ProgrammaticJsonSerializerSetup.cs b/src/core/Akka.Docs.Tests/Networking/Serialization/ProgrammaticJsonSerializerSetup.cs new file mode 100644 index 00000000000..4e23d21ebdc --- /dev/null +++ b/src/core/Akka.Docs.Tests/Networking/Serialization/ProgrammaticJsonSerializerSetup.cs @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Akka.Actor; +using Akka.Actor.Setup; +using Akka.Serialization; +using Newtonsoft.Json; + +namespace DocsExamples.Networking.Serialization +{ + public class ProgrammaticJsonSerializerSetup + { + public ProgrammaticJsonSerializerSetup() + { + #region CustomJsonSetup + var jsonSerializerSetup = NewtonSoftJsonSerializerSetup.Create( + settings => + { + settings.NullValueHandling = NullValueHandling.Include; + settings.PreserveReferencesHandling = PreserveReferencesHandling.None; + settings.Formatting = Formatting.None; + }); + + var systemSetup = ActorSystemSetup.Create(jsonSerializerSetup); + + var system = ActorSystem.Create("MySystem", systemSetup); + #endregion + + system.Terminate().Wait(); + } + } +} diff --git a/src/core/Akka.FSharp.Tests/Akka.FSharp.Tests.fsproj b/src/core/Akka.FSharp.Tests/Akka.FSharp.Tests.fsproj index 7438fcd34e5..7a040e60726 100644 --- a/src/core/Akka.FSharp.Tests/Akka.FSharp.Tests.fsproj +++ b/src/core/Akka.FSharp.Tests/Akka.FSharp.Tests.fsproj @@ -22,7 +22,7 @@ - + diff --git a/src/core/Akka.MultiNodeTestRunner.Shared.Tests/MultiNodeTestRunnerDiscovery/DiscoveryCases.cs b/src/core/Akka.MultiNodeTestRunner.Shared.Tests/MultiNodeTestRunnerDiscovery/DiscoveryCases.cs index e9954d7a39d..950b3beb88e 100644 --- a/src/core/Akka.MultiNodeTestRunner.Shared.Tests/MultiNodeTestRunnerDiscovery/DiscoveryCases.cs +++ b/src/core/Akka.MultiNodeTestRunner.Shared.Tests/MultiNodeTestRunnerDiscovery/DiscoveryCases.cs @@ -5,6 +5,7 @@ // //----------------------------------------------------------------------- +using System.Linq; using Akka.Remote.TestKit; namespace Akka.MultiNodeTestRunner.Shared.Tests.MultiNodeTestRunnerDiscovery @@ -133,6 +134,30 @@ public FloodyChildSpec3(FloodyConfig config) : base(config) } } + public class NoReflectionConfig : MultiNodeConfig + { + public NoReflectionConfig() + { + foreach(var i in Enumerable.Range(1, 10)) + { + Role("node-" + i); + } + } + } + + public class NoReflectionSpec + { + public NoReflectionSpec(NoReflectionConfig config) + { + + } + + [MultiNodeFact(Skip = "Only for discovery tests")] + public void Dummy() + { + } + } + public class DiverseConfig : MultiNodeConfig { public RoleName RoleProp { get; set; } diff --git a/src/core/Akka.MultiNodeTestRunner.Shared.Tests/MultiNodeTestRunnerDiscovery/DiscoverySpec.cs b/src/core/Akka.MultiNodeTestRunner.Shared.Tests/MultiNodeTestRunnerDiscovery/DiscoverySpec.cs index 769428afdc3..8aa0c32d700 100644 --- a/src/core/Akka.MultiNodeTestRunner.Shared.Tests/MultiNodeTestRunnerDiscovery/DiscoverySpec.cs +++ b/src/core/Akka.MultiNodeTestRunner.Shared.Tests/MultiNodeTestRunnerDiscovery/DiscoverySpec.cs @@ -53,11 +53,11 @@ public void Discovered_count_equals_number_of_roles_mult_specs() Assert.Equal(5, discoveredSpecs[KeyFromSpecName(nameof(DiscoveryCases.FloodyChildSpec3))].Count); } - [Fact(DisplayName = "Only public props and fields are considered when looking for RoleNames")] - public void Public_props_and_fields_are_considered() + [Fact(DisplayName = "Only the MultiNodeConfig.Roles property is used to compute the number of Roles in MultiNodeFact")] + public void Only_MultiNodeConfig_role_count_used() { var discoveredSpecs = DiscoverSpecs(); - Assert.Equal(discoveredSpecs[KeyFromSpecName(nameof(DiscoveryCases.DiverseSpec))].Select(c => c.Role), new[] {"RoleProp", "RoleField"}); + Assert.Equal(10, discoveredSpecs[KeyFromSpecName(nameof(DiscoveryCases.NoReflectionSpec))].Select(c => c.Role).Count()); } private static Dictionary> DiscoverSpecs() diff --git a/src/core/Akka.MultiNodeTestRunner.Shared.Tests/ParsingSpec.cs b/src/core/Akka.MultiNodeTestRunner.Shared.Tests/ParsingSpec.cs index 89231235619..3874fd949e3 100644 --- a/src/core/Akka.MultiNodeTestRunner.Shared.Tests/ParsingSpec.cs +++ b/src/core/Akka.MultiNodeTestRunner.Shared.Tests/ParsingSpec.cs @@ -5,6 +5,7 @@ // //----------------------------------------------------------------------- +using System; using System.Reflection; using Akka.Actor; using Akka.Configuration; @@ -115,11 +116,23 @@ public void MessageSink_should_be_able_to_infer_message_type() //format the string as it would appear when reported by multinode test runner var nodeMessageFragment = "[NODE1:super_role_1] Only part of a message!"; var runnerMessageStr = foundMessage.ToString(); + try + { + throw new ApplicationException("test"); + } + catch (Exception ex) + { + specFail.FailureExceptionTypes.Add(ex.GetType().ToString()); + specFail.FailureMessages.Add(ex.Message); + specFail.FailureStackTraces.Add(ex.StackTrace); + } + var specFailMsg = specFail.ToString(); + MessageSink.DetermineMessageType(runnerMessageStr).ShouldBe(MessageSink.MultiNodeTestRunnerMessageType.RunnerLogMessage); MessageSink.DetermineMessageType(specPass.ToString()).ShouldBe(MessageSink.MultiNodeTestRunnerMessageType.NodePassMessage); - MessageSink.DetermineMessageType(specFail.ToString()).ShouldBe(MessageSink.MultiNodeTestRunnerMessageType.NodeFailMessage); - MessageSink.DetermineMessageType("[Node2][FAIL-EXCEPTION] Type: Xunit.Sdk.TrueException").ShouldBe(MessageSink.MultiNodeTestRunnerMessageType.NodeFailureException); + MessageSink.DetermineMessageType(specFailMsg).ShouldBe(MessageSink.MultiNodeTestRunnerMessageType.NodeFailMessage); + MessageSink.DetermineMessageType($"[Node2][{DateTime.UtcNow}][FAIL-EXCEPTION] Type: Xunit.Sdk.TrueException").ShouldBe(MessageSink.MultiNodeTestRunnerMessageType.NodeFailureException); MessageSink.DetermineMessageType(nodeMessageFragment).ShouldBe(MessageSink.MultiNodeTestRunnerMessageType.NodeLogFragment); MessageSink.DetermineMessageType("foo!").ShouldBe(MessageSink.MultiNodeTestRunnerMessageType.Unknown); } diff --git a/src/core/Akka.MultiNodeTestRunner.Shared/Sinks/MessageSink.cs b/src/core/Akka.MultiNodeTestRunner.Shared/Sinks/MessageSink.cs index 98d48ded004..79288d13e87 100644 --- a/src/core/Akka.MultiNodeTestRunner.Shared/Sinks/MessageSink.cs +++ b/src/core/Akka.MultiNodeTestRunner.Shared/Sinks/MessageSink.cs @@ -89,7 +89,7 @@ public enum MultiNodeTestRunnerMessageType }; private const string NodePassStatusRegexString = - @"\[(\w){4}(?[0-9]{1,2})(?:\w+)?\]\[(?(PASS|FAIL))\]{1}\s(?.*)"; + @"\[(\w){4}(?[0-9]{1,2})(?:\w+)?\]\[(? static void Main(string[] args) { + // Force load the args + CommandLine.GetPropertyOrDefault("force load", null); + if (CommandLine.ShowHelp) + { + PrintHelp(); + return; + } + + if (CommandLine.ShowVersion) + { + var version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + Console.WriteLine($"Version: {version}"); + return; + } + OutputDirectory = CommandLine.GetPropertyOrDefault("multinode.output-directory", string.Empty); FailedSpecsDirectory = CommandLine.GetPropertyOrDefault("multinode.failed-specs-directory", "FAILED_SPECS_LOGS"); @@ -130,7 +159,7 @@ static void Main(string[] args) var suiteName = Path.GetFileNameWithoutExtension(Path.GetFullPath(args[0].Trim('"'))); var teamCityFormattingOn = CommandLine.GetPropertyOrDefault("multinode.teamcity", "false"); if (!Boolean.TryParse(teamCityFormattingOn, out TeamCityFormattingOn)) - throw new ArgumentException("Invalid argument provided for -Dteamcity"); + throw new ArgumentException("Invalid argument provided for -Dmultinode.teamcity"); var listenAddress = IPAddress.Parse(CommandLine.GetPropertyOrDefault("multinode.listen-address", "127.0.0.1")); var listenPort = CommandLine.GetInt32OrDefault("multinode.listen-port", 6577); @@ -143,6 +172,9 @@ static void Main(string[] args) if (clearOutputDirectory > 0 && Directory.Exists(OutputDirectory)) Directory.Delete(OutputDirectory, true); + var include = new WildcardPatterns(CommandLine.GetPropertyOrDefault("multinode.include", null), true); + var exclude = new WildcardPatterns(CommandLine.GetPropertyOrDefault("multinode.exclude", null), false); + Props coordinatorProps; switch (reporter.ToLowerInvariant()) { @@ -221,25 +253,39 @@ static void Main(string[] args) { foreach (var test in discovery.Tests.Reverse()) { - if (!string.IsNullOrEmpty(test.Value.First().SkipReason)) + var node = test.Value.First(); + if (!string.IsNullOrEmpty(node.SkipReason)) { - PublishRunnerMessage($"Skipping test {test.Value.First().MethodName}. Reason - {test.Value.First().SkipReason}"); + PublishRunnerMessage($"Skipping test {node.MethodName}. Reason - {node.SkipReason}"); continue; } if (!string.IsNullOrWhiteSpace(specName) && - CultureInfo.InvariantCulture.CompareInfo.IndexOf(test.Value.First().TestName, + CultureInfo.InvariantCulture.CompareInfo.IndexOf(node.TestName, specName, CompareOptions.IgnoreCase) < 0) { - PublishRunnerMessage($"Skipping [{test.Value.First().MethodName}] (Filtering)"); + PublishRunnerMessage($"Skipping [{node.MethodName}] (Filtering)"); + continue; + } + + // include filter + if (!include.IsMatch(node.MethodName)) + { + PublishRunnerMessage($"Skipping [{node.MethodName}] (Include filter)"); + continue; + } + + if (exclude.IsMatch(node.MethodName)) + { + PublishRunnerMessage($"Skipping [{node.MethodName}] (Exclude filter)"); continue; } var processes = new List(); - PublishRunnerMessage($"Starting test {test.Value.First().MethodName}"); - Console.Out.WriteLine($"Starting test {test.Value.First().MethodName}"); + PublishRunnerMessage($"Starting test {node.MethodName}"); + Console.Out.WriteLine($"Starting test {node.MethodName}"); StartNewSpec(test.Value); #if CORECLR @@ -491,6 +537,49 @@ private static void PublishToAllSinks(string message) { SinkCoordinator.Tell(message, ActorRefs.NoSender); } + + private static void PrintHelp() + { + var version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + Console.WriteLine($@"Akka.NET Multi Node Test Runner ({version}) +Usage: MultiNodeTestRunner [path-to-test-dll] [runtime-options] + +Run a compiled Akka.NET multi node test + +runtime-options: + -Dmultinode.output-directory= + Folder where the test report will be exported. + Default value : current working directory. + -Dmultinode.failed-specs-directory= + Folder name inside the output directory where failed test log will be exported, if a test should fail. + Default value : FAILED_SPECS_LOG. + -Dmultinode.loglevel= + Sets the minimum reported log level used within the test. + Valid values : DEBUG, INFO, WARNING, ERROR. + Default value: WARNING. + -Dmultinode.listen-address= + The TCP/IP address or host name the multi node test runner should listen for test node reports/logs. + Default value: 127.0.0.1. + -Dmultinode.listen-port= + The TCP/IP port the multi node test runner should listen for test node reports/logs. + Default value: 6577. + -Dmultinode.reporter= + The report type this runner should export in. Note that report files are exported to the current directory for trx. + Valid values : trx, teamcity, console. + Default value: console. + -Dmultinode.clear-output=<0|1> + This flag will clear the output folder before any test is run when it is set to 1. + Default value: 0. + -Dmultinode.spec= + Apply a filter to the test class names within the dll. Any fully qualified test class name that contains this string will run. + Default value: (all). + -Dmultinode.include= + A comma separated list of wildcard pattern to be matched and included in the tests. The filter is applied on the name of the test method. + Default value: * (all). + -Dmultinode.exclude= + A comma separated list of wildcard pattern to be matched and excluded in the tests. The filter is applied on the name of the test method. + Default value: (none)."); + } } internal class TcpLoggingServer : ReceiveActor diff --git a/src/core/Akka.MultiNodeTestRunner/README.md b/src/core/Akka.MultiNodeTestRunner/README.md index 2cc04cb477c..03757b95426 100644 --- a/src/core/Akka.MultiNodeTestRunner/README.md +++ b/src/core/Akka.MultiNodeTestRunner/README.md @@ -8,13 +8,40 @@ This README explains how to run the `Akka.MultiNodeTestRunner` to execute any `M ## Running the MultiNodeTestRunner -Right now the only options for running the `MultiNodeTestRunner` is to build from the Akka.NET source and manually copy the binaries out of `src\core\Akka.MultiNodeTestRunner\bin\[Debug|Release]`: +Right now the only options for running the `MultiNodeTestRunner` is to build from the Akka.NET source and manually copy the binaries out of `src\core\Akka.MultiNodeTestRunner\bin\[Debug|Release]\[Platform name]`, where `[Platform name]` can be either `net5.0`, `net471`, or `netcoreapp3.1`: ![MultiNodeTestRunner binaries](../../../documentation/wiki/images/multinode-teskit/multi-node-testrunner-binaries.png) -The `Akka.MultiNodeTestRunner` process requires only one argument - the full path or name of the assembly containing `MultiNodeSpec` tests. - - C:> Akka.MultiNodeTestRunner.exe [assembly name] +## Commandline Arguments + +``` +C:> Akka.MultiNodeTestRunner.exe [assembly name] + [-Dmultinode.output-directory={dir-path}] + [-Dmultinode.failed-specs-directory={folder-name}] + [-Dmultinode.loglevel={DEBUG|INFO|WARNING|ERROR}] + [-Dmultinode.listen-address={ip-address}] + [-Dmultinode.listen-port={ip-port}] + [-Dmultinode.reporter={trx|teamcity|console}] + [-Dmultinode.clear-output={0|1}] + [-Dmultinode.spec={spec-filter}] + [-Dmultinode.include={include-filter}] + [-Dmultinode.exclude={exclude-filter}] +``` + +- __assembly name__ : The full path or name of the assembly containing `MultiNodeSpec` tests. +- __-Dmultinode.output-directory__ : Folder where the test report will be exported. Default value is the current working directory. +- __-Dmultinode.failed-specs-directory__ : Folder name inside the output directory where failed test log will be exported, if a test should fail. Default value is `FAILED_SPECS_LOG`. +- __-Dmultinode.loglevel__ : Sets the minimum reported log level used within the test. Valid values are `DEBUG`, `INFO`, `WARNING`, and `ERROR`. Default value is `WARNING`. +- __-Dmultinode.listen-address__ : The TCP/IP address the multi node test runner should listen for test node reports/logs. Default value is `127.0.0.1`. +- __-Dmultinode.listen-port__ : The TCP/IP port the multi node test runner should listen for test node reports/logs. Default value is `6577`. +- __-Dmultinode.reporter__ : The report type this runner should export in. Valid values are `trx`, `teamcity`, and `console`. Note that report files are exported to the current directory for `trx`. Default value is `console`. +- __-Dmultinode.clear-output__ : This flag will clear the output folder before any test is run when it is set to 1. Default value is 0. +- __-Dmultinode.spec__ : Apply a filter to the test class names within the dll. Any fully qualified test class name that contains this string will run. Default value is (all). +- __-Dmultinode.include__ : A "`,`" (comma) separted list of wildcard pattern to be matched and included in the tests. Default value is `*` (all). The filter is applied on the name of the test method. +- __-Dmultinode.exclude__ : A "`,`" (comma) separted list of wildcard pattern to be matched and excluded in the tests. Default value is (none). The filter is applied on the name of the test method. + +## Deprecated Commandline Arguments +- __-Dmultinode.teamcity__ : This argument is no longer processed. Use __-Dmultinode.reporter__ with `teamcity` value instead. ### Built-in Tests for Akka.Cluster diff --git a/src/core/Akka.MultiNodeTestRunner/WildcardPatterns.cs b/src/core/Akka.MultiNodeTestRunner/WildcardPatterns.cs new file mode 100644 index 00000000000..b08647d5a1d --- /dev/null +++ b/src/core/Akka.MultiNodeTestRunner/WildcardPatterns.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Akka.MultiNodeTestRunner +{ + public class WildcardPatterns + { + private readonly Regex[] _regex; + + public WildcardPatterns(string patternCsv, bool matchAllByDefault) + { + if (string.IsNullOrWhiteSpace(patternCsv)) + { + if (matchAllByDefault) + { + patternCsv = "*"; + } + else + { + _regex = null; + return; + } + } + var patterns = patternCsv.Split(','); + + _regex = patterns + .Select(pattern => + Regex.Escape(pattern.Trim()) + .Replace("\\?", ".") + .Replace("\\*", ".*")) + .Select(clean => new Regex($"^{clean}$", RegexOptions.Compiled | RegexOptions.IgnoreCase)) + .ToArray(); + } + + public bool IsMatch(string input) => _regex?.Any(rx => rx.IsMatch(input)) ?? false; + } +} diff --git a/src/core/Akka.NodeTestRunner/Sink.cs b/src/core/Akka.NodeTestRunner/Sink.cs index 7da52a4b9a3..ce48223dd4f 100644 --- a/src/core/Akka.NodeTestRunner/Sink.cs +++ b/src/core/Akka.NodeTestRunner/Sink.cs @@ -40,47 +40,47 @@ public Sink(int nodeIndex, string nodeRole, IActorRef logger) public bool OnMessage(IMessageSinkMessage message) { - var resultMessage = message as ITestResultMessage; - if (resultMessage != null) + if (message is ITestResultMessage resultMessage) { _logger.Tell(resultMessage.Output); Console.WriteLine(resultMessage.Output); } - var testPassed = message as ITestPassed; - if (testPassed != null) - { - //the MultiNodeTestRunner uses 1-based indexing, which is why we have to add 1 to the index. - var specPass = new SpecPass(_nodeIndex + 1, _nodeRole, testPassed.TestCase.DisplayName); - _logger.Tell(specPass.ToString()); - Console.WriteLine(specPass.ToString()); //so the message also shows up in the individual per-node build log - Passed = true; - return true; - } - var testFailed = message as ITestFailed; - if (testFailed != null) - { - //the MultiNodeTestRunner uses 1-based indexing, which is why we have to add 1 to the index. - var specFail = new SpecFail(_nodeIndex + 1, _nodeRole, testFailed.TestCase.DisplayName); - foreach (var failedMessage in testFailed.Messages) specFail.FailureMessages.Add(failedMessage); - foreach (var stackTrace in testFailed.StackTraces) specFail.FailureStackTraces.Add(stackTrace); - foreach(var exceptionType in testFailed.ExceptionTypes) specFail.FailureExceptionTypes.Add(exceptionType); - _logger.Tell(specFail.ToString()); - Console.WriteLine(specFail.ToString()); - return true; - } - var errorMessage = message as ErrorMessage; - if (errorMessage != null) - { - var specFail = new SpecFail(_nodeIndex + 1, _nodeRole, "ERRORED"); - foreach (var failedMessage in errorMessage.Messages) specFail.FailureMessages.Add(failedMessage); - foreach (var stackTrace in errorMessage.StackTraces) specFail.FailureStackTraces.Add(stackTrace); - foreach (var exceptionType in errorMessage.ExceptionTypes) specFail.FailureExceptionTypes.Add(exceptionType); - _logger.Tell(specFail.ToString()); - Console.WriteLine(specFail.ToString()); - } - if (message is ITestAssemblyFinished) + + switch (message) { - Finished.Set(); + case ITestPassed testPassed: + { + //the MultiNodeTestRunner uses 1-based indexing, which is why we have to add 1 to the index. + var specPass = new SpecPass(_nodeIndex + 1, _nodeRole, testPassed.TestCase.DisplayName); + _logger.Tell(specPass.ToString()); + Console.WriteLine(specPass.ToString()); //so the message also shows up in the individual per-node build log + Passed = true; + return true; + } + case ITestFailed testFailed: + { + //the MultiNodeTestRunner uses 1-based indexing, which is why we have to add 1 to the index. + var specFail = new SpecFail(_nodeIndex + 1, _nodeRole, testFailed.TestCase.DisplayName); + foreach (var failedMessage in testFailed.Messages) specFail.FailureMessages.Add(failedMessage); + foreach (var stackTrace in testFailed.StackTraces) specFail.FailureStackTraces.Add(stackTrace); + foreach(var exceptionType in testFailed.ExceptionTypes) specFail.FailureExceptionTypes.Add(exceptionType); + _logger.Tell(specFail.ToString()); + Console.WriteLine(specFail.ToString()); + return true; + } + case ErrorMessage errorMessage: + { + var specFail = new SpecFail(_nodeIndex + 1, _nodeRole, "ERRORED"); + foreach (var failedMessage in errorMessage.Messages) specFail.FailureMessages.Add(failedMessage); + foreach (var stackTrace in errorMessage.StackTraces) specFail.FailureStackTraces.Add(stackTrace); + foreach (var exceptionType in errorMessage.ExceptionTypes) specFail.FailureExceptionTypes.Add(exceptionType); + _logger.Tell(specFail.ToString()); + Console.WriteLine(specFail.ToString()); + break; + } + case ITestAssemblyFinished _: + Finished.Set(); + break; } return true; diff --git a/src/core/Akka.Persistence.TCK/Journal/JournalSpec.cs b/src/core/Akka.Persistence.TCK/Journal/JournalSpec.cs index c95220a00dd..10248194248 100644 --- a/src/core/Akka.Persistence.TCK/Journal/JournalSpec.cs +++ b/src/core/Akka.Persistence.TCK/Journal/JournalSpec.cs @@ -10,6 +10,7 @@ using System.Collections.Immutable; using System.Linq; using Akka.Actor; +using Akka.Actor.Setup; using Akka.Configuration; using Akka.Persistence.TCK.Serialization; using Akka.TestKit; @@ -52,6 +53,18 @@ protected JournalSpec(Config config = null, string actorSystemName = null, ITest { } + protected JournalSpec(ActorSystemSetup setup, string actorSystemName = null, ITestOutputHelper output = null) + : base(setup, actorSystemName ?? "SnapshotStoreSpec", output) + { + _senderProbe = CreateTestProbe(); + } + + protected JournalSpec(ActorSystem system = null, ITestOutputHelper output = null) + : base(system, output) + { + _senderProbe = CreateTestProbe(); + } + protected override bool SupportsSerialization => true; /// diff --git a/src/core/Akka.Persistence.TCK/PluginSpec.cs b/src/core/Akka.Persistence.TCK/PluginSpec.cs index d92f8364748..f82a4856904 100644 --- a/src/core/Akka.Persistence.TCK/PluginSpec.cs +++ b/src/core/Akka.Persistence.TCK/PluginSpec.cs @@ -7,6 +7,7 @@ using System; using Akka.Actor; +using Akka.Actor.Setup; using Akka.Configuration; using Akka.Util.Internal; using Xunit.Abstractions; @@ -27,6 +28,22 @@ protected PluginSpec(Config config = null, string actorSystemName = null, ITestO WriterGuid = Guid.NewGuid().ToString(); } + protected PluginSpec(ActorSystemSetup setup, string actorSystemName = null, ITestOutputHelper output = null) + : base(setup, actorSystemName, output) + { + Extension = Persistence.Instance.Apply(Sys as ExtendedActorSystem); + Pid = "p-" + Counter.IncrementAndGet(); + WriterGuid = Guid.NewGuid().ToString(); + } + + protected PluginSpec(ActorSystem system = null, ITestOutputHelper output = null) + : base(system, output) + { + Extension = Persistence.Instance.Apply(Sys as ExtendedActorSystem); + Pid = "p-" + Counter.IncrementAndGet(); + WriterGuid = Guid.NewGuid().ToString(); + } + protected static Config FromConfig(Config config = null) { return config.IsNullOrEmpty() diff --git a/src/core/Akka.Persistence.TCK/Snapshot/SnapshotStoreSpec.cs b/src/core/Akka.Persistence.TCK/Snapshot/SnapshotStoreSpec.cs index 952e612d509..e19c31cb3ea 100644 --- a/src/core/Akka.Persistence.TCK/Snapshot/SnapshotStoreSpec.cs +++ b/src/core/Akka.Persistence.TCK/Snapshot/SnapshotStoreSpec.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using Akka.Actor; +using Akka.Actor.Setup; using Akka.Configuration; using Akka.Persistence.Fsm; using Akka.Persistence.TCK.Serialization; @@ -56,6 +57,18 @@ protected SnapshotStoreSpec(Config config = null, string actorSystemName = null, _senderProbe = CreateTestProbe(); } + protected SnapshotStoreSpec(ActorSystemSetup setup, string actorSystemName = null, ITestOutputHelper output = null) + : base(setup, actorSystemName ?? "SnapshotStoreSpec", output) + { + _senderProbe = CreateTestProbe(); + } + + protected SnapshotStoreSpec(ActorSystem system = null, ITestOutputHelper output = null) + : base(system, output) + { + _senderProbe = CreateTestProbe(); + } + protected SnapshotStoreSpec(Type snapshotStoreType, string actorSystemName = null) : base(ConfigFromTemplate(snapshotStoreType), actorSystemName) { diff --git a/src/core/Akka.Persistence.TestKit.Tests/Bug4762FixSpec.cs b/src/core/Akka.Persistence.TestKit.Tests/Bug4762FixSpec.cs index 6d78411e5e6..02d9f1fbbda 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/Bug4762FixSpec.cs +++ b/src/core/Akka.Persistence.TestKit.Tests/Bug4762FixSpec.cs @@ -1,4 +1,11 @@ -using System; +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/core/Akka.Persistence.Tests/PersistentActorDeleteFailureSpec.cs b/src/core/Akka.Persistence.Tests/PersistentActorDeleteFailureSpec.cs index e55b1862d14..5692d813420 100644 --- a/src/core/Akka.Persistence.Tests/PersistentActorDeleteFailureSpec.cs +++ b/src/core/Akka.Persistence.Tests/PersistentActorDeleteFailureSpec.cs @@ -115,7 +115,7 @@ public void PersistentActor_should_have_default_warn_logging_be_triggered_when_d { var pref = Sys.ActorOf(Props.Create(() => new DoesNotHandleDeleteFailureActor(Name))); Sys.EventStream.Subscribe(TestActor, typeof (Warning)); - pref.Tell(new DeleteTo(100)); + pref.Tell(new DeleteTo(long.MaxValue)); var message = ExpectMsg().Message.ToString(); message.Contains("Failed to DeleteMessages").ShouldBeTrue(); message.Contains("Boom! Unable to delete events!").ShouldBeTrue(); @@ -126,8 +126,8 @@ public void PersistentActor_should_receive_a_DeleteMessagesFailure_when_deletion { var pref = Sys.ActorOf(Props.Create(() => new HandlesDeleteFailureActor(Name, TestActor))); Sys.EventStream.Subscribe(TestActor, typeof (Warning)); - pref.Tell(new DeleteTo(100)); - ExpectMsg(m => m.ToSequenceNr == 100); + pref.Tell(new DeleteTo(long.MaxValue)); + ExpectMsg(); ExpectNoMsg(TimeSpan.FromMilliseconds(100)); } } diff --git a/src/core/Akka.Persistence.Tests/PersistentActorSpec.Actors.cs b/src/core/Akka.Persistence.Tests/PersistentActorSpec.Actors.cs index e70f063eda6..0719e240990 100644 --- a/src/core/Akka.Persistence.Tests/PersistentActorSpec.Actors.cs +++ b/src/core/Akka.Persistence.Tests/PersistentActorSpec.Actors.cs @@ -39,6 +39,13 @@ protected bool Receiver(object message) throw new ArgumentNullException("Received DeleteMessagesSuccess without anyone asking for delete!"); AskedForDelete.Tell(message); } + else if (message is DeleteMessagesFailure) + { + if (AskedForDelete == null) + throw new ArgumentNullException("Received DeleteMessagesSuccess without anyone asking for delete!"); + AskedForDelete.Tell(message); + } + else return false; return true; } @@ -144,7 +151,7 @@ protected bool UpdateState(object message) if (message is Evt) Events = Events.AddFirst((message as Evt).Data); else if (message is IActorRef) - AskedForDelete = (IActorRef) message; + AskedForDelete = (IActorRef)message; else return false; return true; @@ -292,7 +299,7 @@ public ChangeBehaviorInCommandHandlerFirstActor(string name) : base(name) { } protected override bool ReceiveCommand(object message) { if (CommonBehavior(message)) return true; - + if (message is Cmd) { var cmd = message as Cmd; @@ -324,7 +331,7 @@ public ChangeBehaviorInCommandHandlerLastActor(string name) : base(name) { } protected override bool ReceiveCommand(object message) { if (CommonBehavior(message)) return true; - + if (message is Cmd) { var cmd = message as Cmd; @@ -607,7 +614,7 @@ protected override bool ReceiveCommand(object message) internal class AsyncPersistAndPersistMixedSyncAsyncActor : ExamplePersistentActor { private int _counter = 0; - + public AsyncPersistAndPersistMixedSyncAsyncActor(string name) : base(name) { @@ -883,7 +890,7 @@ protected override bool ReceiveCommand(object message) { if (message is string) { - var s = (string) message; + var s = (string)message; _probe.Tell(s); Persist(s + "-outer-1", outer => { @@ -915,7 +922,7 @@ protected override bool ReceiveCommand(object message) { if (message is string) { - var s = (string) message; + var s = (string)message; _probe.Tell(s); PersistAsync(s + "-outer-1", outer => { @@ -947,7 +954,7 @@ protected override bool ReceiveCommand(object message) { if (message is string) { - var s = (string) message; + var s = (string)message; _probe.Tell(s); Persist(s + "-outer-1", outer => { @@ -979,7 +986,7 @@ protected override bool ReceiveCommand(object message) { if (message is string) { - var s = (string) message; + var s = (string)message; _probe.Tell(s); PersistAsync(s + "-outer-async-1", outer => { @@ -1011,7 +1018,7 @@ protected override bool ReceiveCommand(object message) { if (message is string) { - var s = (string) message; + var s = (string)message; _probe.Tell(s); PersistAsync(s + "-outer-async", outer => { @@ -1069,7 +1076,7 @@ protected override bool ReceiveCommand(object message) { if (message is string) { - var s = (string) message; + var s = (string)message; _probe.Tell(s); Persist(s + "-1", WeMustGoDeeper); return true; @@ -1113,7 +1120,7 @@ protected override bool ReceiveCommand(object message) { if (message is string) { - var s = (string) message; + var s = (string)message; _probe.Tell(s); PersistAsync(s + "-1", WeMustGoDeeper); return true; diff --git a/src/core/Akka.Persistence.Tests/PersistentActorSpec.cs b/src/core/Akka.Persistence.Tests/PersistentActorSpec.cs index 19b5bbe8c48..74cdbbae343 100644 --- a/src/core/Akka.Persistence.Tests/PersistentActorSpec.cs +++ b/src/core/Akka.Persistence.Tests/PersistentActorSpec.cs @@ -11,6 +11,7 @@ using System.Threading; using Akka.Actor; using Akka.TestKit; +using FluentAssertions; using Xunit; namespace Akka.Persistence.Tests @@ -528,14 +529,14 @@ public void PersistentActor_should_allow_deeply_nested_PersistAsync_calls() var got = ReceiveN(nestedPersistAsyncs).Select(m => m.ToString()).OrderBy(m => m).ToArray(); got.ShouldOnlyContainInOrder(Enumerable.Range(1, nestedPersistAsyncs).Select(i => "a-" + i).ToArray()); - + pref.Tell("b"); pref.Tell("c"); - got = ReceiveN(nestedPersistAsyncs*2 + 2).Select(m => m.ToString()).OrderBy(m => m).ToArray(); + got = ReceiveN(nestedPersistAsyncs * 2 + 2).Select(m => m.ToString()).OrderBy(m => m).ToArray(); got.ShouldOnlyContainInOrder( - new [] {"b"} + new[] { "b" } .Union(Enumerable.Range(1, nestedPersistAsyncs).Select(i => "b-" + i)) - .Union(new [] {"c"}) + .Union(new[] { "c" }) .Union(Enumerable.Range(1, nestedPersistAsyncs).Select(i => "c-" + i)) .ToArray()); } @@ -604,6 +605,20 @@ public void PersistentActor_should_be_able_to_delete_all_events() ExpectMsg(m => m.Length == 0); } + [Fact] + public void PersistentActor_should_not_be_able_to_delete_higher_seqnr_than_current() + { + var pref = ActorOf(Props.Create(() => new BehaviorOneActor(Name))); + pref.Tell(new Cmd("b")); + pref.Tell(GetState.Instance); + ExpectMsgInOrder("a-1", "a-2", "b-1", "b-2"); + pref.Tell(new Delete(5)); // > current 4 + pref.Tell("boom"); // restart, recover + ExpectMsg(m => m.Cause.Message.Should().Contain("less than or equal to LastSequenceNr")); + pref.Tell(GetState.Instance); + ExpectMsgInOrder("a-1", "a-2", "b-1", "b-2"); + } + [Fact] public void PersistentActor_should_brecover_the_message_which_caused_the_restart() { @@ -617,7 +632,7 @@ public void PersistentActor_should_be_able_to_persist_events_that_happen_during_ { var persistentActor = ActorOf(Props.Create(() => new PersistInRecovery(Name))); persistentActor.Tell(GetState.Instance); - ExpectAnyMsgInOrder(new[]{"a-1", "a-2", "rc-1", "rc-2" }, new[] { "a-1", "a-2", "rc-1", "rc-2", "rc-3" }); + ExpectAnyMsgInOrder(new[] { "a-1", "a-2", "rc-1", "rc-2" }, new[] { "a-1", "a-2", "rc-1", "rc-2", "rc-3" }); persistentActor.Tell(new Cmd("invalid")); persistentActor.Tell(GetState.Instance); ExpectMsgInOrder("a-1", "a-2", "rc-1", "rc-2", "rc-3", "invalid"); diff --git a/src/core/Akka.Persistence.Tests/Serialization/PersistenceMessageSerializerSpec.cs b/src/core/Akka.Persistence.Tests/Serialization/PersistenceMessageSerializerSpec.cs index 9b49ebd5267..9c8c9f3f6d8 100644 --- a/src/core/Akka.Persistence.Tests/Serialization/PersistenceMessageSerializerSpec.cs +++ b/src/core/Akka.Persistence.Tests/Serialization/PersistenceMessageSerializerSpec.cs @@ -46,6 +46,23 @@ public void MessageSerializer_should_serialize_manifest_provided_by_EventAdapter back.Manifest.Should().Be(p1.Manifest); } + [Fact] + public void MessageSerializer_should_serialize_events_with_no_manifest_and_null_type() + { + var p1 = new Persistent(new MyPayload("a"), sender: TestActor); + var bytes = _serializer.ToBinary(p1); + var back = _serializer.FromBinary(bytes, null); + + back.Should().BeOfType(); + var persisted = (Persistent)back; + persisted.Payload.Should().BeOfType(); + persisted.Sender.Should().BeEquivalentTo(TestActor); + var payload = (MyPayload)persisted.Payload; + + // Yes, the data isn't "a" but ".a.", the custom serializer added these dots. + payload.Data.ShouldBe(".a."); + } + [Fact] public void MessageSerializer_should_serialize_state_change_event() { diff --git a/src/core/Akka.Persistence/Eventsourced.Recovery.cs b/src/core/Akka.Persistence/Eventsourced.Recovery.cs index 810087382cd..1171ce271eb 100644 --- a/src/core/Akka.Persistence/Eventsourced.Recovery.cs +++ b/src/core/Akka.Persistence/Eventsourced.Recovery.cs @@ -208,8 +208,9 @@ private EventsourcedState Recovering(Receive recoveryBehavior, TimeSpan timeout) case RecoverySuccess success: timeoutCancelable.Cancel(); OnReplaySuccess(); - _sequenceNr = success.HighestSequenceNr; - LastSequenceNr = success.HighestSequenceNr; + var highestSeqNr = Math.Max(success.HighestSequenceNr, LastSequenceNr); + _sequenceNr = highestSeqNr; + LastSequenceNr = highestSeqNr; recoveryRunning = false; try { @@ -255,6 +256,9 @@ private EventsourcedState Recovering(Receive recoveryBehavior, TimeSpan timeout) eventSeenInInterval = false; } break; + case RecoveryTick tick when tick.Snapshot: + // snapshot tick, ignore + break; default: StashInternally(message); break; diff --git a/src/core/Akka.Persistence/Eventsourced.cs b/src/core/Akka.Persistence/Eventsourced.cs index 33d9db1f58f..0e92a7fc666 100644 --- a/src/core/Akka.Persistence/Eventsourced.cs +++ b/src/core/Akka.Persistence/Eventsourced.cs @@ -448,11 +448,19 @@ public void DeferAsync(TEvent evt, Action handler) /// Permanently deletes all persistent messages with sequence numbers less than or equal . /// If the delete is successful a will be sent to the actor. /// If the delete fails a will be sent to the actor. + /// + /// The given must be less than or equal to , otherwise + /// is sent to the actor without performing the delete. All persistent + /// messages may be deleted without specifying the actual sequence number by using + /// as the . /// /// Upper sequence number bound of persistent messages to be deleted. public void DeleteMessages(long toSequenceNr) { - Journal.Tell(new DeleteMessagesTo(PersistenceId, toSequenceNr, Self)); + if (toSequenceNr == long.MaxValue || toSequenceNr <= LastSequenceNr) + Journal.Tell(new DeleteMessagesTo(PersistenceId, toSequenceNr == long.MaxValue ? LastSequenceNr : toSequenceNr, Self)); + else + Self.Tell(new DeleteMessagesFailure(new InvalidOperationException($"toSequenceNr [{toSequenceNr}] must be less than or equal to LastSequenceNr [{LastSequenceNr}]"), toSequenceNr)); } /// diff --git a/src/core/Akka.Persistence/JournalProtocol.cs b/src/core/Akka.Persistence/JournalProtocol.cs index e75a26d849d..5a6a462e85b 100644 --- a/src/core/Akka.Persistence/JournalProtocol.cs +++ b/src/core/Akka.Persistence/JournalProtocol.cs @@ -70,7 +70,7 @@ public bool Equals(DeleteMessagesSuccess other) /// Reply message to failed request. /// [Serializable] - public sealed class DeleteMessagesFailure : IEquatable + public sealed class DeleteMessagesFailure : IEquatable, INoSerializationVerificationNeeded //serialization verification temporary disabled because of Cause serialization issues { /// /// Initializes a new instance of the class. @@ -134,7 +134,7 @@ public sealed class DeleteMessagesTo : IJournalRequest, IEquatable class. /// /// Requesting persistent actor id. - /// Sequence number where replay should end (inclusive). + /// Sequence number where replay should end (inclusive). may be used to delete all persistent messages. /// Requesting persistent actor. /// /// This exception is thrown when the specified is undefined. diff --git a/src/core/Akka.Persistence/Serialization/PersistenceMessageSerializer.cs b/src/core/Akka.Persistence/Serialization/PersistenceMessageSerializer.cs index 03355ca3a93..46f3cfd8e11 100644 --- a/src/core/Akka.Persistence/Serialization/PersistenceMessageSerializer.cs +++ b/src/core/Akka.Persistence/Serialization/PersistenceMessageSerializer.cs @@ -150,6 +150,7 @@ private PersistentFSMSnapshot GetPersistentFSMSnapshot(object obj) public override object FromBinary(byte[] bytes, Type type) { + if(type == null) return GetPersistentRepresentation(PersistentMessage.Parser.ParseFrom(bytes)); if (type == typeof(Persistent)) return GetPersistentRepresentation(PersistentMessage.Parser.ParseFrom(bytes)); if (type == typeof(IPersistentRepresentation)) return GetPersistentRepresentation(PersistentMessage.Parser.ParseFrom(bytes)); if (type == typeof(AtomicWrite)) return GetAtomicWrite(bytes); diff --git a/src/core/Akka.Persistence/Serialization/Proto/Persistence.g.cs b/src/core/Akka.Persistence/Serialization/Proto/Persistence.g.cs index 319060fb9fa..9b13bec8062 100644 --- a/src/core/Akka.Persistence/Serialization/Proto/Persistence.g.cs +++ b/src/core/Akka.Persistence/Serialization/Proto/Persistence.g.cs @@ -1,10 +1,3 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2021 Lightbend Inc. -// Copyright (C) 2013-2021 .NET Foundation -// -//----------------------------------------------------------------------- - // Generated by the protocol buffer compiler. DO NOT EDIT! // source: Persistence.proto #pragma warning disable 1591, 0612, 3021 diff --git a/src/core/Akka.Remote.TestKit/CommandLine.cs b/src/core/Akka.Remote.TestKit/CommandLine.cs index 001d296edd7..738424cdf49 100644 --- a/src/core/Akka.Remote.TestKit/CommandLine.cs +++ b/src/core/Akka.Remote.TestKit/CommandLine.cs @@ -6,6 +6,7 @@ //----------------------------------------------------------------------- using System; +using System.Collections.Generic; using System.Collections.Specialized; using Akka.Configuration; @@ -27,34 +28,69 @@ namespace Akka.Remote.TestKit /// public class CommandLine { - private static readonly Lazy Values = new Lazy(() => + private static readonly StringDictionary Values; + + static CommandLine() { - var dictionary = new StringDictionary(); - foreach (var arg in Environment.GetCommandLineArgs()) + Values = new StringDictionary(); + + // Detect and fix PowerShell command line input. + // PowerShell splits command line arguments on '.' + var args = Environment.GetCommandLineArgs(); + var fixedArgs = new List(); + for (var i = 1; i < args.Length - 1; ++i) + { + if (args[i].Equals("-Dmultinode") && args[i + 1].StartsWith(".")) + { + fixedArgs.Add(args[i] + args[i+1]); + ++i; + } + } + if(fixedArgs.Count == 0) + fixedArgs.AddRange(args); + + foreach (var arg in fixedArgs) { - if (!arg.StartsWith("-D")) continue; + if (!arg.StartsWith("-D")) + { + var a = arg.Trim().ToLowerInvariant(); + if (a.Equals("-h") || a.Equals("--help")) + { + ShowHelp = true; + return; + } + if (a.Equals("-v") || a.Equals("--version")) + { + ShowVersion = true; + return; + } + continue; + } + var tokens = arg.Substring(2).Split('='); if (tokens.Length == 2) { - dictionary.Add(tokens[0], tokens[1]); + Values.Add(tokens[0], tokens[1]); } else { throw new ConfigurationException($"Command line parameter '{arg}' should follow the pattern [-Dmultinode.=]."); } } - return dictionary; - }); + } + + public static bool ShowHelp { get; private set; } + public static bool ShowVersion { get; private set; } public static string GetProperty(string key) { - return Values.Value[key]; + return Values[key]; } public static string GetPropertyOrDefault(string key, string defaultStr) { - return Values.Value.ContainsKey(key) ? Values.Value[key] : defaultStr; + return Values.ContainsKey(key) ? Values[key] : defaultStr; } public static int GetInt32(string key) @@ -64,7 +100,7 @@ public static int GetInt32(string key) public static int GetInt32OrDefault(string key, int defaultInt) { - return Values.Value.ContainsKey(key) ? GetInt32(key) : defaultInt; + return Values.ContainsKey(key) ? GetInt32(key) : defaultInt; } } } diff --git a/src/core/Akka.Remote.TestKit/MultiNodeSpec.cs b/src/core/Akka.Remote.TestKit/MultiNodeSpec.cs index e13e484da6d..d6ef4b7cca5 100644 --- a/src/core/Akka.Remote.TestKit/MultiNodeSpec.cs +++ b/src/core/Akka.Remote.TestKit/MultiNodeSpec.cs @@ -168,7 +168,7 @@ internal ImmutableList Deployments(RoleName node) return deployments == null ? _allDeploy : deployments.AddRange(_allDeploy); } - internal ImmutableList Roles + public ImmutableList Roles { get { return _roles; } } @@ -508,7 +508,6 @@ public int InitialParticipants /// public void RunOn(Action thunk, params RoleName[] nodes) { - if (nodes.Length == 0) throw new ArgumentException("No node given to run on."); if (IsNode(nodes)) thunk(); } @@ -518,7 +517,6 @@ public void RunOn(Action thunk, params RoleName[] nodes) /// public async Task RunOnAsync(Func thunkAsync, params RoleName[] nodes) { - if (nodes.Length == 0) throw new ArgumentException("No node given to run on."); if (IsNode(nodes)) await thunkAsync(); } diff --git a/src/core/Akka.Remote.TestKit/Proto/TestConductorProtocol.g.cs b/src/core/Akka.Remote.TestKit/Proto/TestConductorProtocol.g.cs index 002202826cc..384afe88874 100644 --- a/src/core/Akka.Remote.TestKit/Proto/TestConductorProtocol.g.cs +++ b/src/core/Akka.Remote.TestKit/Proto/TestConductorProtocol.g.cs @@ -1,10 +1,3 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2021 Lightbend Inc. -// Copyright (C) 2013-2021 .NET Foundation -// -//----------------------------------------------------------------------- - // Generated by the protocol buffer compiler. DO NOT EDIT! // source: TestConductorProtocol.proto #pragma warning disable 1591, 0612, 3021 diff --git a/src/core/Akka.Remote.Tests/Akka.Remote.Tests.csproj b/src/core/Akka.Remote.Tests/Akka.Remote.Tests.csproj index 738a2c19cdb..2f5b6dd7a88 100644 --- a/src/core/Akka.Remote.Tests/Akka.Remote.Tests.csproj +++ b/src/core/Akka.Remote.Tests/Akka.Remote.Tests.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/src/core/Akka.Remote.Tests/BugFixes/BugFix4384Spec.cs b/src/core/Akka.Remote.Tests/BugFixes/BugFix4384Spec.cs index 555923f7531..092dc05420e 100644 --- a/src/core/Akka.Remote.Tests/BugFixes/BugFix4384Spec.cs +++ b/src/core/Akka.Remote.Tests/BugFixes/BugFix4384Spec.cs @@ -1,9 +1,9 @@ -// //----------------------------------------------------------------------- -// // -// // Copyright (C) 2009-2021 Lightbend Inc. -// // Copyright (C) 2013-2021 .NET Foundation -// // -// //----------------------------------------------------------------------- +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- using System; using System.Linq; @@ -131,4 +131,4 @@ private static int GetFreeTcpPort() return port; } } -} \ No newline at end of file +} diff --git a/src/core/Akka.Remote.Tests/Serialization/BugFix4399Spec.cs b/src/core/Akka.Remote.Tests/Serialization/BugFix4399Spec.cs index 8f401a31e7e..bb3b765c409 100644 --- a/src/core/Akka.Remote.Tests/Serialization/BugFix4399Spec.cs +++ b/src/core/Akka.Remote.Tests/Serialization/BugFix4399Spec.cs @@ -1,4 +1,11 @@ -using System; +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/core/Akka.Remote/Configuration/Remote.conf b/src/core/Akka.Remote/Configuration/Remote.conf index 364108afb2c..246a6f1ddca 100644 --- a/src/core/Akka.Remote/Configuration/Remote.conf +++ b/src/core/Akka.Remote/Configuration/Remote.conf @@ -578,23 +578,20 @@ akka { ### Default dispatcher for the remoting subsystem - ### Default dispatcher for the remoting subsystem - default-remote-dispatcher { - type = ForkJoinDispatcher - executor = fork-join-executor - dedicated-thread-pool { - # Fixed number of threads to have in this threadpool - thread-count = 4 + executor = fork-join-executor + fork-join-executor { + parallelism-min = 2 + parallelism-factor = 0.5 + parallelism-max = 16 } } backoff-remote-dispatcher { - type = ForkJoinDispatcher - executor = fork-join-executor - dedicated-thread-pool { - # Fixed number of threads to have in this threadpool - thread-count = 4 + executor = fork-join-executor + fork-join-executor { + parallelism-min = 2 + parallelism-max = 2 } } } diff --git a/src/core/Akka.Remote/DefaultFailureDetectorRegistry.cs b/src/core/Akka.Remote/DefaultFailureDetectorRegistry.cs index 77ba678d34f..21ce673219f 100644 --- a/src/core/Akka.Remote/DefaultFailureDetectorRegistry.cs +++ b/src/core/Akka.Remote/DefaultFailureDetectorRegistry.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using Akka.Util; namespace Akka.Remote @@ -30,11 +31,11 @@ public DefaultFailureDetectorRegistry(Func factory) private readonly Func _factory; - private AtomicReference> _resourceToFailureDetector = new AtomicReference>(new Dictionary()); + private AtomicReference> _resourceToFailureDetector = new AtomicReference>(ImmutableDictionary.Empty); private readonly object _failureDetectorCreationLock = new object(); - private Dictionary ResourceToFailureDetector + private ImmutableDictionary ResourceToFailureDetector { get { return _resourceToFailureDetector; } set { _resourceToFailureDetector = value; } @@ -83,15 +84,23 @@ public void Heartbeat(T resource) { // First check for non-existing key wa outside the lock, and a second thread might just have released the lock // when this one acquired it, so the second check is needed (double-check locking pattern) - var oldTable = new Dictionary(ResourceToFailureDetector); + var oldTable = ResourceToFailureDetector; if (oldTable.TryGetValue(resource, out failureDetector)) failureDetector.HeartBeat(); else { var newDetector = _factory(); + + switch (newDetector) + { + case PhiAccrualFailureDetector phi: + phi.Address = resource.ToString(); + break; + } + newDetector.HeartBeat(); - oldTable.Add(resource, newDetector); - ResourceToFailureDetector = oldTable; + var newTable = oldTable.Add(resource, newDetector); + ResourceToFailureDetector = newTable; } } } @@ -108,9 +117,8 @@ public void Remove(T resource) var oldTable = ResourceToFailureDetector; if (oldTable.ContainsKey(resource)) { - var newTable = new Dictionary(oldTable); - newTable.Remove(resource); //if we won the race then update else try again - if (_resourceToFailureDetector.CompareAndSet(oldTable, newTable)) continue; + var newTable = oldTable.Remove(resource); //if we won the race then update else try again + if (!_resourceToFailureDetector.CompareAndSet(oldTable, newTable)) continue; } break; } @@ -125,7 +133,7 @@ public void Reset() { var oldTable = ResourceToFailureDetector; // if we won the race then update else try again - if (_resourceToFailureDetector.CompareAndSet(oldTable, new Dictionary())) continue; + if (!_resourceToFailureDetector.CompareAndSet(oldTable, ImmutableDictionary.Empty)) continue; break; } } diff --git a/src/core/Akka.Remote/Endpoint.cs b/src/core/Akka.Remote/Endpoint.cs index 686ff486319..77ee4275174 100644 --- a/src/core/Akka.Remote/Endpoint.cs +++ b/src/core/Akka.Remote/Endpoint.cs @@ -1139,7 +1139,7 @@ protected override void PostStop() } _buffer.Clear(); - if (_handle != null) _handle.Disassociate(_stopReason); + _handle?.Disassociate(_stopReason); EventPublisher.NotifyListeners(new DisassociatedEvent(LocalAddress, RemoteAddress, Inbound)); } @@ -1219,6 +1219,12 @@ private void Handoff() BecomeWritingOrSendBufferedMessages(); }); Receive(send => EnqueueInBuffer(send)); + + // Ignore outgoing acks during take over, since we might have + // replaced the handle with a connection to a new, restarted, system + // and the ack might be targeted to the old incarnation. + // Relates to https://github.com/akka/akka/pull/20093 + Receive(_ => { }); } /// @@ -1227,56 +1233,50 @@ private void Handoff() /// TBD protected override void Unhandled(object message) { - if (message is Terminated) + switch (message) { - var t = message as Terminated; - if (_reader == null || t.ActorRef.Equals(_reader)) + case Terminated t: { - PublishAndThrow(new EndpointDisassociatedException("Disassociated"), LogLevel.DebugLevel); + if (_reader == null || t.ActorRef.Equals(_reader)) + { + PublishAndThrow(new EndpointDisassociatedException("Disassociated"), LogLevel.DebugLevel); + } + + break; } - } - else if (message is StopReading) - { - var stop = message as StopReading; - if (_reader != null) - { + case StopReading stop when _reader != null: _reader.Tell(stop, stop.ReplyTo); - } - else - { + break; + case StopReading stop: // initializing, buffer and take care of it later when buffer is sent - EnqueueInBuffer(message); + EnqueueInBuffer(stop); + break; + case TakeOver takeover: + // Shutdown old reader + _handle.Disassociate("the association was replaced by a new one", _log); + _handle = takeover.ProtocolHandle; + takeover.ReplyTo.Tell(new TookOver(Self, _handle)); + Become(Handoff); + break; + case FlushAndStop _: + _stopReason = DisassociateInfo.Shutdown; + Context.Stop(Self); + break; + case OutboundAck ack: + { + _lastAck = ack.Ack; + if (_ackDeadline.IsOverdue) + TrySendPureAck(); + break; } - } - else if (message is TakeOver) - { - var takeover = message as TakeOver; - - // Shutdown old reader - _handle.Disassociate(); - _handle = takeover.ProtocolHandle; - takeover.ReplyTo.Tell(new TookOver(Self, _handle)); - Become(Handoff); - } - else if (message is FlushAndStop) - { - _stopReason = DisassociateInfo.Shutdown; - Context.Stop(Self); - } - else if (message is OutboundAck) - { - var ack = message as OutboundAck; - _lastAck = ack.Ack; - if (_ackDeadline.IsOverdue) - TrySendPureAck(); - } - else if (message is AckIdleCheckTimer || message is FlushAndStopTimeout || message is BackoffTimer) - { - //ignore - } - else - { - base.Unhandled(message); + case AckIdleCheckTimer _: + case FlushAndStopTimeout _: + case BackoffTimer _: + //ignore + break; + default: + base.Unhandled(message); + break; } } diff --git a/src/core/Akka.Remote/PhiAccrualFailureDetector.cs b/src/core/Akka.Remote/PhiAccrualFailureDetector.cs index 0f60c0d766a..772ba123d45 100644 --- a/src/core/Akka.Remote/PhiAccrualFailureDetector.cs +++ b/src/core/Akka.Remote/PhiAccrualFailureDetector.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Akka.Actor; using Akka.Configuration; @@ -35,12 +36,12 @@ namespace Akka.Remote /// public class PhiAccrualFailureDetector : FailureDetector { - private double _threshold; - private int _maxSampleSize; + private readonly double _threshold; + private readonly int _maxSampleSize; private TimeSpan _minStdDeviation; private TimeSpan _acceptableHeartbeatPause; private TimeSpan _firstHeartbeatEstimate; - private Clock _clock; + private readonly Clock _clock; /// /// Procedural constructor for PhiAccrualDetector @@ -90,12 +91,13 @@ public PhiAccrualFailureDetector(Config config, EventStream ev) _acceptableHeartbeatPause = config.GetTimeSpan("acceptable-heartbeat-pause", null); _firstHeartbeatEstimate = config.GetTimeSpan("heartbeat-interval", null); state = new State(FirstHeartBeat, null); + EventStream = ev ?? Option.None; } /// - /// TBD + /// Protected constructor to be used for sub-classing only. /// - /// TBD + /// The clock used fo marking time. protected PhiAccrualFailureDetector(Clock clock) { _clock = clock ?? DefaultClock; @@ -116,6 +118,13 @@ private HeartbeatHistory FirstHeartBeat } } + private Option EventStream { get; } + + /// + /// Address introduced as a mutable property in order to avoid shuffling around API signatures + /// + public string Address { get; set; } = "N/A"; + /// /// Uses volatile memory and immutability for lockless concurrency. /// @@ -145,7 +154,7 @@ public State(HeartbeatHistory history, long? timeStamp) private AtomicReference _state; - private State state + internal State state { get { return _state; } set { _state = value; } @@ -174,7 +183,7 @@ public override void HeartBeat() { var timestamp = _clock(); var oldState = state; - HeartbeatHistory newHistory = null; + HeartbeatHistory newHistory; if (!oldState.TimeStamp.HasValue) { @@ -187,11 +196,20 @@ public override void HeartBeat() //this is a known connection var interval = timestamp - oldState.TimeStamp.Value; //don't use the first heartbeat after failure for the history, since a long pause will skew the stats - if (IsTimeStampAvailable(timestamp)) newHistory = (oldState.History + interval); + if (IsTimeStampAvailable(timestamp)) + { + if (interval >= (AcceptableHeartbeatPauseMillis / 3 * 2) && EventStream.HasValue) + { + EventStream.Value.Publish(new Warning(ToString(), GetType(), + $"heartbeat interval is growing too large for address {Address}: {interval} millis")); + } + newHistory = (oldState.History + interval); + } else newHistory = oldState.History; } var newState = new State(newHistory, timestamp); + //if we won the race then update else try again if(!_state.CompareAndSet(oldState, newState)) HeartBeat(); } @@ -259,14 +277,14 @@ internal double Phi(long timeDiff, double mean, double stdDeviation) return -Math.Log10(1.0d - 1.0d/(1.0d + e)); } - private long MinStdDeviationMillis + private double MinStdDeviationMillis { - get { return (long)_minStdDeviation.TotalMilliseconds; } + get { return _minStdDeviation.TotalMilliseconds; } } - private long AcceptableHeartbeatPauseMillis + private double AcceptableHeartbeatPauseMillis { - get { return (long)_acceptableHeartbeatPause.TotalMilliseconds; } + get { return _acceptableHeartbeatPause.TotalMilliseconds; } } private double EnsureValidStdDeviation(double stdDeviation) @@ -284,20 +302,19 @@ private double EnsureValidStdDeviation(double stdDeviation) /// The stats (mean, variance, stdDeviation) are not defined for empty /// , i.e. throws Exception /// - internal class HeartbeatHistory + internal readonly struct HeartbeatHistory { - private int _maxSampleSize; - private List _intervals; - private long _intervalSum; - private long _squaredIntervalSum; + private readonly int _maxSampleSize; + private readonly long _intervalSum; + private readonly long _squaredIntervalSum; /// - /// TBD + /// Creates a new instance. /// - /// TBD - /// TBD - /// TBD - /// TBD + /// The maximum number of samples to retain. Older ones are dropped once intervals exceeds this value. + /// The range of recorded time intervals. + /// The sum of the recorded time intervals. + /// The squared sum of the intervals. /// /// This exception is thrown for the following reasons: ///
    @@ -306,10 +323,10 @@ internal class HeartbeatHistory ///
  • The specified is less than zero.
  • ///
///
- public HeartbeatHistory(int maxSampleSize, List intervals, long intervalSum, long squaredIntervalSum) + public HeartbeatHistory(int maxSampleSize, ImmutableList intervals, long intervalSum, long squaredIntervalSum) { _maxSampleSize = maxSampleSize; - _intervals = intervals; + Intervals = intervals; _intervalSum = intervalSum; _squaredIntervalSum = squaredIntervalSum; @@ -321,29 +338,13 @@ public HeartbeatHistory(int maxSampleSize, List intervals, long intervalSu throw new ArgumentOutOfRangeException(nameof(squaredIntervalSum), $"squaredIntervalSum must be >= 0, got {squaredIntervalSum}"); } - /// - /// TBD - /// - public double Mean - { - get { return ((double)_intervalSum / _intervals.Count); } - } + public double Mean => ((double)_intervalSum / Intervals.Count); - /// - /// TBD - /// - public double Variance - { - get { return ((double)_squaredIntervalSum / _intervals.Count) - (Mean * Mean); } - } + public double Variance => ((double)_squaredIntervalSum / Intervals.Count) - (Mean * Mean); - /// - /// TBD - /// - public double StdDeviation - { - get { return Math.Sqrt(Variance); } - } + public double StdDeviation => Math.Sqrt(Variance); + + public ImmutableList Intervals { get; } /// /// Increments the . @@ -353,9 +354,9 @@ public double StdDeviation /// A new heartbeat history instance with the added interval. public static HeartbeatHistory operator +(HeartbeatHistory history, long interval) { - if (history._intervals.Count < history._maxSampleSize) + if (history.Intervals.Count < history._maxSampleSize) { - return new HeartbeatHistory(history._maxSampleSize, history._intervals.Concat(new[] { interval }).ToList(), + return new HeartbeatHistory(history._maxSampleSize, history.Intervals.Add(interval), history._intervalSum + interval, history._squaredIntervalSum + Pow2(interval)); } else @@ -366,7 +367,8 @@ public double StdDeviation private static HeartbeatHistory DropOldest(HeartbeatHistory history) { - return new HeartbeatHistory(history._maxSampleSize, history._intervals.Skip(1).ToList(), history._intervalSum - history._intervals.First(), history._squaredIntervalSum - Pow2(history._intervals.First())); + return new HeartbeatHistory(history._maxSampleSize, history.Intervals.RemoveAt(0), history._intervalSum - history.Intervals.First(), + history._squaredIntervalSum - Pow2(history.Intervals.First())); } private static long Pow2(long x) @@ -382,11 +384,11 @@ private static long Pow2(long x) /// The stats (mean, variance, stdDeviation) are not defined for empty /// HeartbeatHistory and will throw DivideByZero exceptions /// - /// TBD - /// TBD + /// The maximum number of samples to include in this history. + /// A new instance. public static HeartbeatHistory Apply(int maxSampleSize) { - return new HeartbeatHistory(maxSampleSize, new List(), 0L, 0L); + return new HeartbeatHistory(maxSampleSize, ImmutableList.Empty, 0L, 0L); } #endregion diff --git a/src/core/Akka.Remote/RemoteWatcher.cs b/src/core/Akka.Remote/RemoteWatcher.cs index 186b8c0d854..ce8a8e81231 100644 --- a/src/core/Akka.Remote/RemoteWatcher.cs +++ b/src/core/Akka.Remote/RemoteWatcher.cs @@ -128,18 +128,10 @@ private Heartbeat() { } - private static readonly Heartbeat _instance = new Heartbeat(); - /// /// TBD /// - public static Heartbeat Instance - { - get - { - return _instance; - } - } + public static Heartbeat Instance { get; } = new Heartbeat(); } /// @@ -174,18 +166,11 @@ public int AddressUid public class HeartbeatTick { private HeartbeatTick() { } - private static readonly HeartbeatTick _instance = new HeartbeatTick(); /// /// TBD /// - public static HeartbeatTick Instance - { - get - { - return _instance; - } - } + public static HeartbeatTick Instance { get; } = new HeartbeatTick(); } /// @@ -194,18 +179,11 @@ public static HeartbeatTick Instance public class ReapUnreachableTick { private ReapUnreachableTick() { } - private static readonly ReapUnreachableTick _instance = new ReapUnreachableTick(); /// /// TBD /// - public static ReapUnreachableTick Instance - { - get - { - return _instance; - } - } + public static ReapUnreachableTick Instance { get; } = new ReapUnreachableTick(); } /// @@ -375,8 +353,7 @@ TimeSpan heartbeatExpectedResponseAfter { _failureDetector = failureDetector; _heartbeatExpectedResponseAfter = heartbeatExpectedResponseAfter; - var systemProvider = Context.System.AsInstanceOf().Provider as IRemoteActorRefProvider; - if (systemProvider != null) _remoteProvider = systemProvider; + if (Context.System.AsInstanceOf().Provider is IRemoteActorRefProvider systemProvider) _remoteProvider = systemProvider; else throw new ConfigurationException( $"ActorSystem {Context.System} needs to have a 'RemoteActorRefProvider' enabled in the configuration, current uses {Context.System.AsInstanceOf().Provider.GetType().FullName}"); @@ -430,42 +407,55 @@ protected override void PostStop() /// TBD protected override void OnReceive(object message) { - if (message is HeartbeatTick) SendHeartbeat(); - else if (message is Heartbeat) ReceiveHeartbeat(); - else if (message is HeartbeatRsp) ReceiveHeartbeatRsp(((HeartbeatRsp)message).AddressUid); - else if (message is ReapUnreachableTick) ReapUnreachable(); - else if (message is ExpectedFirstHeartbeat) TriggerFirstHeartbeat(((ExpectedFirstHeartbeat)message).From); - else if (message is WatchRemote) + switch (message) { - var watchRemote = (WatchRemote)message; - AddWatching(watchRemote.Watchee, watchRemote.Watcher); - } - else if (message is UnwatchRemote) - { - var unwatchRemote = (UnwatchRemote)message; - RemoveWatch(unwatchRemote.Watchee, unwatchRemote.Watcher); - } - else if (message is Terminated) - { - var t = (Terminated)message; - ProcessTerminated(t.ActorRef.AsInstanceOf(), t.ExistenceConfirmed, t.AddressTerminated); - } - // test purpose - else if (message is Stats) - { - var watchSet = ImmutableHashSet.Create(Watching.SelectMany(pair => + case HeartbeatTick _: + SendHeartbeat(); + break; + case Heartbeat _: + ReceiveHeartbeat(); + break; + case HeartbeatRsp rsp: + ReceiveHeartbeatRsp(rsp.AddressUid); + break; + case ReapUnreachableTick _: + ReapUnreachable(); + break; + case ExpectedFirstHeartbeat heartbeat: + TriggerFirstHeartbeat(heartbeat.From); + break; + case WatchRemote watchRemote: { - var list = new List<(IActorRef, IActorRef)>(pair.Value.Count); - var wee = pair.Key; - list.AddRange(pair.Value.Select(wer => ((IActorRef)wee, (IActorRef)wer))); - return list; - }).ToArray()); - Sender.Tell(new Stats(watchSet.Count(), WatchingNodes.Count, watchSet, - ImmutableHashSet.Create(WatchingNodes.ToArray()))); - } - else - { - Unhandled(message); + AddWatching(watchRemote.Watchee, watchRemote.Watcher); + break; + } + case UnwatchRemote unwatchRemote: + { + RemoveWatch(unwatchRemote.Watchee, unwatchRemote.Watcher); + break; + } + // test purpose + case Terminated t: + { + ProcessTerminated(t.ActorRef.AsInstanceOf(), t.ExistenceConfirmed, t.AddressTerminated); + break; + } + case Stats _: + { + var watchSet = ImmutableHashSet.Create(Watching.SelectMany(pair => + { + var list = new List<(IActorRef, IActorRef)>(pair.Value.Count); + var wee = pair.Key; + list.AddRange(pair.Value.Select(wer => ((IActorRef)wee, (IActorRef)wer))); + return list; + }).ToArray()); + Sender.Tell(new Stats(watchSet.Count(), WatchingNodes.Count, watchSet, + ImmutableHashSet.Create(WatchingNodes.ToArray()))); + break; + } + default: + Unhandled(message); + break; } } @@ -485,12 +475,7 @@ private void ReceiveHeartbeatRsp(int uid) if (WatcheeByNodes.ContainsKey(from) && !Unreachable.Contains(from)) { - if (_addressUids.TryGetValue(from, out int addressUid)) - { - if (addressUid != uid) - ReWatch(from); - } - else + if (!_addressUids.TryGetValue(from, out int addressUid) || addressUid != uid) ReWatch(from); _addressUids[from] = uid; @@ -704,9 +689,6 @@ private void ReWatch(Address address) } } - /// - /// TBD - /// protected readonly ILoggingAdapter Log = Context.GetLogger(); } } diff --git a/src/core/Akka.Remote/Serialization/Proto/ContainerFormats.g.cs b/src/core/Akka.Remote/Serialization/Proto/ContainerFormats.g.cs index 4fe4f0ec2a0..54870fe038a 100644 --- a/src/core/Akka.Remote/Serialization/Proto/ContainerFormats.g.cs +++ b/src/core/Akka.Remote/Serialization/Proto/ContainerFormats.g.cs @@ -1,16 +1,8 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2021 Lightbend Inc. -// Copyright (C) 2013-2021 .NET Foundation -// -//----------------------------------------------------------------------- - // Generated by the protocol buffer compiler. DO NOT EDIT! // source: ContainerFormats.proto #pragma warning disable 1591, 0612, 3021 #region Designer generated code -using Akka.Annotations; using pb = global::Google.Protobuf; using pbc = global::Google.Protobuf.Collections; using pbr = global::Google.Protobuf.Reflection; diff --git a/src/core/Akka.Remote/Serialization/Proto/SystemMessageFormats.g.cs b/src/core/Akka.Remote/Serialization/Proto/SystemMessageFormats.g.cs index 6fe7321a72c..3296a5c2666 100644 --- a/src/core/Akka.Remote/Serialization/Proto/SystemMessageFormats.g.cs +++ b/src/core/Akka.Remote/Serialization/Proto/SystemMessageFormats.g.cs @@ -1,10 +1,3 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2021 Lightbend Inc. -// Copyright (C) 2013-2021 .NET Foundation -// -//----------------------------------------------------------------------- - // Generated by the protocol buffer compiler. DO NOT EDIT! // source: SystemMessageFormats.proto #pragma warning disable 1591, 0612, 3021 diff --git a/src/core/Akka.Remote/Serialization/Proto/WireFormats.g.cs b/src/core/Akka.Remote/Serialization/Proto/WireFormats.g.cs index 1749d099636..e2dedaae083 100644 --- a/src/core/Akka.Remote/Serialization/Proto/WireFormats.g.cs +++ b/src/core/Akka.Remote/Serialization/Proto/WireFormats.g.cs @@ -1,10 +1,3 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2021 Lightbend Inc. -// Copyright (C) 2013-2021 .NET Foundation -// -//----------------------------------------------------------------------- - // Generated by the protocol buffer compiler. DO NOT EDIT! // source: WireFormats.proto #pragma warning disable 1591, 0612, 3021 diff --git a/src/core/Akka.Streams.Tests/Dsl/FlowIntersperseSpec.cs b/src/core/Akka.Streams.Tests/Dsl/FlowIntersperseSpec.cs index af0372c4e44..c950002fdd0 100644 --- a/src/core/Akka.Streams.Tests/Dsl/FlowIntersperseSpec.cs +++ b/src/core/Akka.Streams.Tests/Dsl/FlowIntersperseSpec.cs @@ -102,6 +102,32 @@ public void A_Intersperse_must_surround_single_element_stream_with_start_and_end probe.ExpectSubscription(); probe.ToStrict(TimeSpan.FromSeconds(1)).Aggregate((s, s1) => s + s1).Should().Be("[1]"); } + + [Fact] + public void A_Intersperse_must_not_surround_empty_stream_with_null_start_and_stop() + { + var probe = + Source.From(new int[0]) + .Select(x => x.ToString()) + .Intersperse(",") + .RunWith(this.SinkProbe(), Materializer); + + probe.ExpectSubscription(); + probe.ToStrict(TimeSpan.FromSeconds(1)).Count().Should().Be(0); + } + + [Fact] + public void A_Intersperse_must_not_surround_single_element_stream_with_null_start_and_stop() + { + var probe = + Source.From(new int[]{1}) + .Select(x => x.ToString()) + .Intersperse(",") + .RunWith(this.SinkProbe(), Materializer); + + probe.ExpectSubscription(); + probe.ToStrict(TimeSpan.FromSeconds(1)).Aggregate((s, s1) => s + s1).Should().Be("1"); + } [Fact] public void A_Intersperse_must__complete_the_stage_when_the_Source_has_been_completed() diff --git a/src/core/Akka.Streams.Tests/Dsl/UnfoldResourceSourceSpec.cs b/src/core/Akka.Streams.Tests/Dsl/UnfoldResourceSourceSpec.cs index 317bb9ec6c8..7615a63a062 100644 --- a/src/core/Akka.Streams.Tests/Dsl/UnfoldResourceSourceSpec.cs +++ b/src/core/Akka.Streams.Tests/Dsl/UnfoldResourceSourceSpec.cs @@ -266,6 +266,48 @@ public void A_UnfoldResourceSource_must_fail_when_close_throws_exception() }, Materializer); } + [Fact] + public void A_UnfoldResourceSource_must_not_close_the_resource_twice_when_read_fails() + { + var closedCounter = new AtomicCounter(0); + var testException = new TestException("failing read"); + + var probe = Source.UnfoldResource( + () => 23, // the best resource there is + _ => throw testException, + _ => closedCounter.IncrementAndGet() + ).RunWith(this.SinkProbe(), Materializer); + + probe.Request(1); + probe.ExpectError().Should().Be(testException); + closedCounter.Current.Should().Be(1); + } + + [Fact] + public void A_UnfoldResourceSource_must_not_close_the_resource_twice_when_read_fails_and_then_close_fails() + { + var closedCounter = new AtomicCounter(0); + var testException = new TestException("boom"); + + var probe = Source.UnfoldResource( + () => 23, // the best resource there is + _ => throw new TestException("failing read"), + _ => + { + closedCounter.IncrementAndGet(); + if (closedCounter.Current == 1) throw testException; + } + ).RunWith(this.SinkProbe(), Materializer); + + EventFilter.Exception().Expect(1, () => + { + probe.Request(1); + probe.ExpectError().Should().Be(testException); + }); + + closedCounter.Current.Should().Be(1); + } + protected override void AfterAll() { base.AfterAll(); diff --git a/src/core/Akka.Streams.Tests/IO/FileSinkSpec.cs b/src/core/Akka.Streams.Tests/IO/FileSinkSpec.cs index 700eb5bda4e..6b38d1cdcbc 100644 --- a/src/core/Akka.Streams.Tests/IO/FileSinkSpec.cs +++ b/src/core/Akka.Streams.Tests/IO/FileSinkSpec.cs @@ -19,6 +19,7 @@ using Akka.Streams.IO; using Akka.Streams.TestKit.Tests; using Akka.TestKit; +using Akka.Tests.Shared.Internals; using Akka.Util.Internal; using FluentAssertions; using Xunit; @@ -31,6 +32,7 @@ public class FileSinkSpec : AkkaSpec private readonly ActorMaterializer _materializer; private readonly List _testLines = new List(); private readonly List _testByteStrings; + private readonly TimeSpan _expectTimeout = TimeSpan.FromSeconds(10); public FileSinkSpec(ITestOutputHelper helper) : base(Utils.UnboundedMailboxConfig, helper) { @@ -55,40 +57,48 @@ public FileSinkSpec(ITestOutputHelper helper) : base(Utils.UnboundedMailboxConfi [Fact] public void SynchronousFileSink_should_write_lines_to_a_file() { - this.AssertAllStagesStopped(() => - { - TargetFile(f => + Within(_expectTimeout, () => { - var completion = Source.From(_testByteStrings).RunWith(FileIO.ToFile(f), _materializer); + TargetFile(f => + { + var completion = Source.From(_testByteStrings) + .RunWith(FileIO.ToFile(f), _materializer); - completion.Wait(TimeSpan.FromSeconds(3)).Should().BeTrue(); - var result = completion.Result; - result.Count.Should().Be(6006); - CheckFileContent(f, _testLines.Aggregate((s, s1) => s + s1)); + completion.AwaitResult(Remaining); + var result = completion.Result; + result.Count.Should().Be(6006); + + AwaitAssert( + () => CheckFileContent(f, _testLines.Aggregate((s, s1) => s + s1)), + Remaining); + }, _materializer); }); - }, _materializer); } [Fact] public void SynchronousFileSink_should_create_new_file_if_not_exists() { - this.AssertAllStagesStopped(() => + Within(_expectTimeout, () => { TargetFile(f => { - var completion = Source.From(_testByteStrings).RunWith(FileIO.ToFile(f), _materializer); - completion.Wait(TimeSpan.FromSeconds(3)).Should().BeTrue(); + var completion = Source.From(_testByteStrings) + .RunWith(FileIO.ToFile(f), _materializer); + + completion.AwaitResult(Remaining); var result = completion.Result; result.Count.Should().Be(6006); - CheckFileContent(f, _testLines.Aggregate((s, s1) => s + s1)); - }, false); - }, _materializer); + AwaitAssert( + () => CheckFileContent(f, _testLines.Aggregate((s, s1) => s + s1)), + Remaining); + }, _materializer, false); + }); } [Fact] public void SynchronousFileSink_should_write_into_existing_file_without_wiping_existing_data() { - this.AssertAllStagesStopped(() => + Within(_expectTimeout, () => { TargetFile(f => { @@ -97,28 +107,31 @@ Task Write(IEnumerable lines) => Source.From(lines) .RunWith(FileIO.ToFile(f, FileMode.OpenOrCreate), _materializer); var completion1 = Write(_testLines); - completion1.Wait(TimeSpan.FromSeconds(3)).Should().BeTrue(); + completion1.AwaitResult(Remaining); var lastWrite = new string[100]; for (var i = 0; i < 100; i++) lastWrite[i] = "x"; var completion2 = Write(lastWrite); - completion2.Wait(TimeSpan.FromSeconds(3)).Should().BeTrue(); + completion2.AwaitResult(Remaining); var result = completion2.Result; var lastWriteString = new string(lastWrite.SelectMany(x => x).ToArray()); result.Count.Should().Be(lastWriteString.Length); var testLinesString = new string(_testLines.SelectMany(x => x).ToArray()); - CheckFileContent(f, lastWriteString + testLinesString.Substring(100)); - }); - }, _materializer); + + AwaitAssert( + () => CheckFileContent(f, lastWriteString + testLinesString.Substring(100)), + Remaining); + }, _materializer); + }); } [Fact] public void SynchronousFileSink_should_by_default_replace_the_existing_file() { - this.AssertAllStagesStopped(() => + Within(_expectTimeout, () => { TargetFile(f => { @@ -126,21 +139,26 @@ Task Write(List lines) => Source.From(lines).Select(ByteString.FromString) .RunWith(FileIO.ToFile(f), _materializer); - Write(_testLines).AwaitResult(); - + var task1 = Write(_testLines); + task1.AwaitResult(Remaining); var lastWrite = Enumerable.Range(0, 100).Select(_ => "x").ToList(); - var result = Write(lastWrite).AwaitResult(); + + var task2 = Write(lastWrite); + var result = task2.AwaitResult(Remaining); result.Count.Should().Be(lastWrite.Count); - CheckFileContent(f, string.Join("", lastWrite)); - }); - }, _materializer); + + AwaitAssert( + () => CheckFileContent(f, string.Join("", lastWrite)), + Remaining); + }, _materializer); + }); } [Fact] public void SynchronousFileSink_should_allow_appending_to_file() { - this.AssertAllStagesStopped(() => + Within(_expectTimeout, () => { TargetFile(f => { @@ -149,7 +167,7 @@ Task Write(List lines) => Source.From(lines) .RunWith(FileIO.ToFile(f, fileMode: FileMode.Append), _materializer); var completion1 = Write(_testLines); - completion1.Wait(TimeSpan.FromSeconds(3)).Should().BeTrue(); + completion1.AwaitResult(Remaining); var result1 = completion1.Result; var lastWrite = new List(); @@ -157,7 +175,7 @@ Task Write(List lines) => Source.From(lines) lastWrite.Add("x"); var completion2 = Write(lastWrite); - completion2.Wait(TimeSpan.FromSeconds(3)).Should().BeTrue(); + completion2.AwaitResult(Remaining); var result2 = completion2.Result; var lastWriteString = new string(lastWrite.SelectMany(x => x).ToArray()); @@ -166,15 +184,18 @@ Task Write(List lines) => Source.From(lines) f.Length.Should().Be(result1.Count + result2.Count); //NOTE: no new line at the end of the file - does JVM/linux appends new line at the end of the file in append mode? - CheckFileContent(f, testLinesString + lastWriteString); - }); - }, _materializer); + AwaitAssert( + () => CheckFileContent(f, testLinesString + lastWriteString), + Remaining); + }, _materializer); + }); + } [Fact] public void SynchronousFileSink_should_allow_writing_from_specific_position_to_the_file() { - this.AssertAllStagesStopped(() => + Within(_expectTimeout, () => { TargetFile(f => { @@ -197,30 +218,37 @@ public void SynchronousFileSink_should_allow_writing_from_specific_position_to_t Task Write(List lines, long pos) => Source.From(lines) .Select(ByteString.FromString) - .RunWith(FileIO.ToFile(f, fileMode: FileMode.OpenOrCreate, startPosition: pos), _materializer); + .RunWith( + FileIO.ToFile(f, fileMode: FileMode.OpenOrCreate, startPosition: pos), + _materializer); var completion1 = Write(_testLines, 0); - var result1 = completion1.AwaitResult(); + completion1.AwaitResult(Remaining); var completion2 = Write(testLinesPart2, startPosition); - var result2 = completion2.AwaitResult(); + var result2 = completion2.AwaitResult(Remaining); f.Length.ShouldBe(startPosition + result2.Count); - CheckFileContent(f, testLinesCommon.Join("") + testLinesPart2.Join("")); - }); - }, _materializer); + + AwaitAssert( + () => CheckFileContent(f, testLinesCommon.Join("") + testLinesPart2.Join("")), + Remaining); + }, _materializer); + }); } [Fact] public void SynchronousFileSink_should_use_dedicated_blocking_io_dispatcher_by_default() { - this.AssertAllStagesStopped(() => + Within(_expectTimeout, () => { + // This is technically incorrect, we're (ab)using TargetFile() just to provide + // the necessary FileInfo, ignoring the fact that we're using a different + // materializer, because we will shut down the system before we're exiting anyway. TargetFile(f => { var sys = ActorSystem.Create("FileSinkSpec-dispatcher-testing-1", Utils.UnboundedMailboxConfig); var materializer = ActorMaterializer.Create(sys); - try { //hack for Iterator.continually @@ -241,25 +269,27 @@ public void SynchronousFileSink_should_use_dedicated_blocking_io_dispatcher_by_d { Shutdown(sys); } - }); - }, _materializer); + }, _materializer); + }); } // FIXME: overriding dispatcher should be made available with dispatcher alias support in materializer (#17929) [Fact(Skip = "overriding dispatcher should be made available with dispatcher alias support in materializer")] public void SynchronousFileSink_should_allow_overriding_the_dispatcher_using_Attributes() { - this.AssertAllStagesStopped(() => + Within(_expectTimeout, () => { + // This is technically incorrect, we're (ab)using TargetFile() just to provide + // the necessary FileInfo, ignoring the fact that we're using a different + // materializer, because we will shut down the system before we're exiting anyway. TargetFile(f => { var sys = ActorSystem.Create("FileSinkSpec-dispatcher-testing-2", Utils.UnboundedMailboxConfig); var materializer = ActorMaterializer.Create(sys); - try { //hack for Iterator.continually - Source.FromEnumerator(() => Enumerable.Repeat(_testByteStrings.Head(), Int32.MaxValue).GetEnumerator()) + Source.FromEnumerator(() => Enumerable.Repeat(_testByteStrings.Head(), int.MaxValue).GetEnumerator()) .To(FileIO.ToFile(f)) .WithAttributes(ActorAttributes.CreateDispatcher("akka.actor.default-dispatcher")); //.Run(materializer); @@ -272,63 +302,76 @@ public void SynchronousFileSink_should_allow_overriding_the_dispatcher_using_Att { Shutdown(sys); } - }); - }, _materializer); + }, _materializer); + }); } [Fact] public void SynchronousFileSink_should_write_single_line_to_a_file_from_lazy_sink() { - this.AssertAllStagesStopped(() => + Within(_expectTimeout, () => { TargetFile(f => { var lazySink = Sink.LazySink( - (ByteString _) => Task.FromResult(FileIO.ToFile(f)), + (ByteString _) => Task.FromResult(FileIO.ToFile(f)), () => Task.FromResult(IOResult.Success(0))) - .MapMaterializedValue(t => t.AwaitResult()); + .MapMaterializedValue(t => t.AwaitResult()); var completion = Source.From(new []{_testByteStrings.Head()}) .RunWith(lazySink, _materializer); - completion.AwaitResult(); - CheckFileContent(f, _testLines.Head()); - }); - }, _materializer); + completion.AwaitResult(Remaining); + AwaitAssert( + () => CheckFileContent(f, _testLines.Head()), + Remaining); + }, _materializer); + }); } [Fact] public void SynchronousFileSink_should_write_each_element_if_auto_flush_is_set() { - this.AssertAllStagesStopped(() => + Within(TimeSpan.FromSeconds(10), () => { TargetFile(f => { var (actor, task) = Source.ActorRef(64, OverflowStrategy.DropNew) .Select(ByteString.FromString) - .ToMaterialized(FileIO.ToFile(f, fileMode: FileMode.OpenOrCreate, startPosition: 0, autoFlush:true), (a, t) => (a, t)) + .ToMaterialized( + FileIO.ToFile(f, fileMode: FileMode.OpenOrCreate, startPosition: 0, autoFlush:true), + Keep.Both) .Run(_materializer); + Watch(actor); actor.Tell("a\n"); actor.Tell("b\n"); - // wait for flush - Thread.Sleep(100); - CheckFileContent(f, "a\nb\n"); + AwaitAssert(() => + { + CheckFileContent(f, "a\nb\n"); + }, Remaining); actor.Tell("a\n"); actor.Tell("b\n"); actor.Tell(new Status.Success(NotUsed.Instance)); - task.Wait(TimeSpan.FromSeconds(3)).Should().BeTrue(); + + // We still have to wait for the task to complete, because the signal + // came from the FileSink actor, not the source actor. + task.AwaitResult(Remaining); + ExpectTerminated(actor, Remaining); f.Length.ShouldBe(8); CheckFileContent(f, "a\nb\na\nb\n"); - }); - }, _materializer); + }, _materializer); + }); } - private static void TargetFile(Action block, bool create = true) + private void TargetFile( + Action block, + ActorMaterializer materializer, + bool create = true) { var targetFile = new FileInfo(Path.Combine(Path.GetTempPath(), "synchronous-file-sink.tmp")); @@ -343,6 +386,10 @@ private static void TargetFile(Action block, bool create = true) } finally { + // this is the proverbial stream kill switch, make sure that all streams + // are dead so that the file handle would be released + this.AssertAllStagesStopped(() => { }, materializer); + //give the system enough time to shutdown and release the file handle Thread.Sleep(500); targetFile.Delete(); diff --git a/src/core/Akka.Streams/Implementation/Fusing/Ops.cs b/src/core/Akka.Streams/Implementation/Fusing/Ops.cs index 40b1865fcc9..833c704bde2 100644 --- a/src/core/Akka.Streams/Implementation/Fusing/Ops.cs +++ b/src/core/Akka.Streams/Implementation/Fusing/Ops.cs @@ -1518,7 +1518,7 @@ public override void OnPush() public override void OnUpstreamFinish() { - _logic.EmitMultiple(_stage.Outlet, new[] { _stage._start, _stage._end }); + if (_stage.InjectStartEnd) _logic.EmitMultiple(_stage.Outlet, new[] { _stage._start, _stage._end }); _logic.CompleteStage(); } } diff --git a/src/core/Akka.Streams/Implementation/IO/FileSubscriber.cs b/src/core/Akka.Streams/Implementation/IO/FileSubscriber.cs index a9e4c005061..dfcc731ffd5 100644 --- a/src/core/Akka.Streams/Implementation/IO/FileSubscriber.cs +++ b/src/core/Akka.Streams/Implementation/IO/FileSubscriber.cs @@ -130,10 +130,19 @@ protected override bool Receive(object message) { var byteString = (ByteString) next.Element; var bytes = byteString.ToArray(); - _chan.Write(bytes, 0, bytes.Length); - _bytesWritten += bytes.Length; - if(_autoFlush) - _chan.Flush(true); + try + { + _chan.Write(bytes, 0, bytes.Length); + _bytesWritten += bytes.Length; + if (_autoFlush) + _chan.Flush(true); + } + catch (Exception ex) + { + _log.Error(ex, $"Tearing down FileSink({_f.FullName}) due to write error."); + _completionPromise.TrySetResult(IOResult.Failed(_bytesWritten, ex)); + Context.Stop(Self); + } } catch (Exception ex) { @@ -161,7 +170,16 @@ protected override bool Receive(object message) return true; case var msg when _flushCommand != null && ReferenceEquals(_flushCommand, msg): - _chan.Flush(); + try + { + _chan.Flush(); + } + catch (Exception ex) + { + _log.Error(ex, $"Tearing down FileSink({_f.FullName}). File flush failed."); + _completionPromise.TrySetResult(IOResult.Failed(_bytesWritten, ex)); + Context.Stop(Self); + } return true; } diff --git a/src/core/Akka.Streams/Implementation/Sources.cs b/src/core/Akka.Streams/Implementation/Sources.cs index 41a480bf812..f94b0037b62 100644 --- a/src/core/Akka.Streams/Implementation/Sources.cs +++ b/src/core/Akka.Streams/Implementation/Sources.cs @@ -441,6 +441,7 @@ public override void OnPull() switch (directive) { case Directive.Stop: + _open = false; _stage._close(_blockingStream); FailStage(ex); stop = true; @@ -467,6 +468,7 @@ public override void PreStart() private void RestartState() { + _open = false; _stage._close(_blockingStream); _blockingStream = _stage._create(); _open = true; diff --git a/src/core/Akka.Streams/Serialization/Proto/StreamRefMessages.g.cs b/src/core/Akka.Streams/Serialization/Proto/StreamRefMessages.g.cs index 71959d40e84..3854e88d887 100644 --- a/src/core/Akka.Streams/Serialization/Proto/StreamRefMessages.g.cs +++ b/src/core/Akka.Streams/Serialization/Proto/StreamRefMessages.g.cs @@ -1,10 +1,3 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2021 Lightbend Inc. -// Copyright (C) 2013-2021 .NET Foundation -// -//----------------------------------------------------------------------- - // Generated by the protocol buffer compiler. DO NOT EDIT! // source: StreamRefMessages.proto #pragma warning disable 1591, 0612, 3021 @@ -14,47 +7,43 @@ using pbc = global::Google.Protobuf.Collections; using pbr = global::Google.Protobuf.Reflection; using scg = global::System.Collections.Generic; -namespace Akka.Streams.Serialization.Proto.Msg -{ - - /// Holder for reflection information generated from StreamRefMessages.proto - internal static partial class StreamRefMessagesReflection - { - - #region Descriptor - /// File descriptor for StreamRefMessages.proto - public static pbr::FileDescriptor Descriptor - { - get { return descriptor; } - } - private static pbr::FileDescriptor descriptor; - - static StreamRefMessagesReflection() - { - byte[] descriptorData = global::System.Convert.FromBase64String( - string.Concat( - "ChdTdHJlYW1SZWZNZXNzYWdlcy5wcm90bxIkQWtrYS5TdHJlYW1zLlNlcmlh", - "bGl6YXRpb24uUHJvdG8uTXNnIh0KCUV2ZW50VHlwZRIQCgh0eXBlTmFtZRgB", - "IAEoCSKQAQoHU2lua1JlZhJBCgl0YXJnZXRSZWYYASABKAsyLi5Ba2thLlN0", - "cmVhbXMuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuQWN0b3JSZWYSQgoJZXZl", - "bnRUeXBlGAIgASgLMi8uQWtrYS5TdHJlYW1zLlNlcmlhbGl6YXRpb24uUHJv", - "dG8uTXNnLkV2ZW50VHlwZSKSAQoJU291cmNlUmVmEkEKCW9yaWdpblJlZhgB", - "IAEoCzIuLkFra2EuU3RyZWFtcy5TZXJpYWxpemF0aW9uLlByb3RvLk1zZy5B", - "Y3RvclJlZhJCCglldmVudFR5cGUYAiABKAsyLy5Ba2thLlN0cmVhbXMuU2Vy", - "aWFsaXphdGlvbi5Qcm90by5Nc2cuRXZlbnRUeXBlIhgKCEFjdG9yUmVmEgwK", - "BHBhdGgYASABKAkiUQoHUGF5bG9hZBIXCg9lbmNsb3NlZE1lc3NhZ2UYASAB", - "KAwSFAoMc2VyaWFsaXplcklkGAIgASgFEhcKD21lc3NhZ2VNYW5pZmVzdBgD", - "IAEoDCJZChRPblN1YnNjcmliZUhhbmRzaGFrZRJBCgl0YXJnZXRSZWYYASAB", - "KAsyLi5Ba2thLlN0cmVhbXMuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuQWN0", - "b3JSZWYiIQoQQ3VtdWxhdGl2ZURlbWFuZBINCgVzZXFOchgBIAEoAyJgCg9T", - "ZXF1ZW5jZWRPbk5leHQSDQoFc2VxTnIYASABKAMSPgoHcGF5bG9hZBgCIAEo", - "CzItLkFra2EuU3RyZWFtcy5TZXJpYWxpemF0aW9uLlByb3RvLk1zZy5QYXls", - "b2FkIiQKE1JlbW90ZVN0cmVhbUZhaWx1cmUSDQoFY2F1c2UYASABKAwiJgoV", - "UmVtb3RlU3RyZWFtQ29tcGxldGVkEg0KBXNlcU5yGAEgASgDQgJIAWIGcHJv", - "dG8z")); - descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, - new pbr::FileDescriptor[] { }, - new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { +namespace Akka.Streams.Serialization.Proto.Msg { + + /// Holder for reflection information generated from StreamRefMessages.proto + internal static partial class StreamRefMessagesReflection { + + #region Descriptor + /// File descriptor for StreamRefMessages.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static StreamRefMessagesReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "ChdTdHJlYW1SZWZNZXNzYWdlcy5wcm90bxIkQWtrYS5TdHJlYW1zLlNlcmlh", + "bGl6YXRpb24uUHJvdG8uTXNnIh0KCUV2ZW50VHlwZRIQCgh0eXBlTmFtZRgB", + "IAEoCSKQAQoHU2lua1JlZhJBCgl0YXJnZXRSZWYYASABKAsyLi5Ba2thLlN0", + "cmVhbXMuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuQWN0b3JSZWYSQgoJZXZl", + "bnRUeXBlGAIgASgLMi8uQWtrYS5TdHJlYW1zLlNlcmlhbGl6YXRpb24uUHJv", + "dG8uTXNnLkV2ZW50VHlwZSKSAQoJU291cmNlUmVmEkEKCW9yaWdpblJlZhgB", + "IAEoCzIuLkFra2EuU3RyZWFtcy5TZXJpYWxpemF0aW9uLlByb3RvLk1zZy5B", + "Y3RvclJlZhJCCglldmVudFR5cGUYAiABKAsyLy5Ba2thLlN0cmVhbXMuU2Vy", + "aWFsaXphdGlvbi5Qcm90by5Nc2cuRXZlbnRUeXBlIhgKCEFjdG9yUmVmEgwK", + "BHBhdGgYASABKAkiUQoHUGF5bG9hZBIXCg9lbmNsb3NlZE1lc3NhZ2UYASAB", + "KAwSFAoMc2VyaWFsaXplcklkGAIgASgFEhcKD21lc3NhZ2VNYW5pZmVzdBgD", + "IAEoDCJZChRPblN1YnNjcmliZUhhbmRzaGFrZRJBCgl0YXJnZXRSZWYYASAB", + "KAsyLi5Ba2thLlN0cmVhbXMuU2VyaWFsaXphdGlvbi5Qcm90by5Nc2cuQWN0", + "b3JSZWYiIQoQQ3VtdWxhdGl2ZURlbWFuZBINCgVzZXFOchgBIAEoAyJgCg9T", + "ZXF1ZW5jZWRPbk5leHQSDQoFc2VxTnIYASABKAMSPgoHcGF5bG9hZBgCIAEo", + "CzItLkFra2EuU3RyZWFtcy5TZXJpYWxpemF0aW9uLlByb3RvLk1zZy5QYXls", + "b2FkIiQKE1JlbW90ZVN0cmVhbUZhaWx1cmUSDQoFY2F1c2UYASABKAwiJgoV", + "UmVtb3RlU3RyZWFtQ29tcGxldGVkEg0KBXNlcU5yGAEgASgDQgJIAWIGcHJv", + "dG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Streams.Serialization.Proto.Msg.EventType), global::Akka.Streams.Serialization.Proto.Msg.EventType.Parser, new[]{ "TypeName" }, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Streams.Serialization.Proto.Msg.SinkRef), global::Akka.Streams.Serialization.Proto.Msg.SinkRef.Parser, new[]{ "TargetRef", "EventType" }, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Streams.Serialization.Proto.Msg.SourceRef), global::Akka.Streams.Serialization.Proto.Msg.SourceRef.Parser, new[]{ "OriginRef", "EventType" }, null, null, null), @@ -65,1651 +54,1359 @@ static StreamRefMessagesReflection() new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Streams.Serialization.Proto.Msg.SequencedOnNext), global::Akka.Streams.Serialization.Proto.Msg.SequencedOnNext.Parser, new[]{ "SeqNr", "Payload" }, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Streams.Serialization.Proto.Msg.RemoteStreamFailure), global::Akka.Streams.Serialization.Proto.Msg.RemoteStreamFailure.Parser, new[]{ "Cause" }, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::Akka.Streams.Serialization.Proto.Msg.RemoteStreamCompleted), global::Akka.Streams.Serialization.Proto.Msg.RemoteStreamCompleted.Parser, new[]{ "SeqNr" }, null, null, null) - })); - } - #endregion - + })); } - #region Messages - internal sealed partial class EventType : pb::IMessage - { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new EventType()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + #endregion - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor - { - get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[0]; } - } + } + #region Messages + internal sealed partial class EventType : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new EventType()); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor - { - get { return Descriptor; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[0]; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public EventType() - { - OnConstruction(); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } - partial void OnConstruction(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public EventType() { + OnConstruction(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public EventType(EventType other) : this() - { - typeName_ = other.typeName_; - } + partial void OnConstruction(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public EventType Clone() - { - return new EventType(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public EventType(EventType other) : this() { + typeName_ = other.typeName_; + } - /// Field number for the "typeName" field. - public const int TypeNameFieldNumber = 1; - private string typeName_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string TypeName - { - get { return typeName_; } - set - { - typeName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public EventType Clone() { + return new EventType(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) - { - return Equals(other as EventType); - } + /// Field number for the "typeName" field. + public const int TypeNameFieldNumber = 1; + private string typeName_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string TypeName { + get { return typeName_; } + set { + typeName_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(EventType other) - { - if (ReferenceEquals(other, null)) - { - return false; - } - if (ReferenceEquals(other, this)) - { - return true; - } - if (TypeName != other.TypeName) return false; - return true; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as EventType); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() - { - int hash = 1; - if (TypeName.Length != 0) hash ^= TypeName.GetHashCode(); - return hash; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(EventType other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (TypeName != other.TypeName) return false; + return true; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() - { - return pb::JsonFormatter.ToDiagnosticString(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (TypeName.Length != 0) hash ^= TypeName.GetHashCode(); + return hash; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) - { - if (TypeName.Length != 0) - { - output.WriteRawTag(10); - output.WriteString(TypeName); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() - { - int size = 0; - if (TypeName.Length != 0) - { - size += 1 + pb::CodedOutputStream.ComputeStringSize(TypeName); - } - return size; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (TypeName.Length != 0) { + output.WriteRawTag(10); + output.WriteString(TypeName); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(EventType other) - { - if (other == null) - { - return; - } - if (other.TypeName.Length != 0) - { - TypeName = other.TypeName; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (TypeName.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(TypeName); + } + return size; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) - { - uint tag; - while ((tag = input.ReadTag()) != 0) - { - switch (tag) - { - default: - input.SkipLastField(); - break; - case 10: - { - TypeName = input.ReadString(); - break; - } - } - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(EventType other) { + if (other == null) { + return; + } + if (other.TypeName.Length != 0) { + TypeName = other.TypeName; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + TypeName = input.ReadString(); + break; + } + } + } } - internal sealed partial class SinkRef : pb::IMessage - { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SinkRef()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor - { - get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[1]; } - } + internal sealed partial class SinkRef : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SinkRef()); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor - { - get { return Descriptor; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[1]; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public SinkRef() - { - OnConstruction(); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } - partial void OnConstruction(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public SinkRef() { + OnConstruction(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public SinkRef(SinkRef other) : this() - { - TargetRef = other.targetRef_ != null ? other.TargetRef.Clone() : null; - EventType = other.eventType_ != null ? other.EventType.Clone() : null; - } + partial void OnConstruction(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public SinkRef Clone() - { - return new SinkRef(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public SinkRef(SinkRef other) : this() { + TargetRef = other.targetRef_ != null ? other.TargetRef.Clone() : null; + EventType = other.eventType_ != null ? other.EventType.Clone() : null; + } - /// Field number for the "targetRef" field. - public const int TargetRefFieldNumber = 1; - private global::Akka.Streams.Serialization.Proto.Msg.ActorRef targetRef_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public global::Akka.Streams.Serialization.Proto.Msg.ActorRef TargetRef - { - get { return targetRef_; } - set - { - targetRef_ = value; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public SinkRef Clone() { + return new SinkRef(this); + } - /// Field number for the "eventType" field. - public const int EventTypeFieldNumber = 2; - private global::Akka.Streams.Serialization.Proto.Msg.EventType eventType_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public global::Akka.Streams.Serialization.Proto.Msg.EventType EventType - { - get { return eventType_; } - set - { - eventType_ = value; - } - } + /// Field number for the "targetRef" field. + public const int TargetRefFieldNumber = 1; + private global::Akka.Streams.Serialization.Proto.Msg.ActorRef targetRef_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Akka.Streams.Serialization.Proto.Msg.ActorRef TargetRef { + get { return targetRef_; } + set { + targetRef_ = value; + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) - { - return Equals(other as SinkRef); - } + /// Field number for the "eventType" field. + public const int EventTypeFieldNumber = 2; + private global::Akka.Streams.Serialization.Proto.Msg.EventType eventType_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Akka.Streams.Serialization.Proto.Msg.EventType EventType { + get { return eventType_; } + set { + eventType_ = value; + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(SinkRef other) - { - if (ReferenceEquals(other, null)) - { - return false; - } - if (ReferenceEquals(other, this)) - { - return true; - } - if (!object.Equals(TargetRef, other.TargetRef)) return false; - if (!object.Equals(EventType, other.EventType)) return false; - return true; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as SinkRef); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() - { - int hash = 1; - if (targetRef_ != null) hash ^= TargetRef.GetHashCode(); - if (eventType_ != null) hash ^= EventType.GetHashCode(); - return hash; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(SinkRef other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(TargetRef, other.TargetRef)) return false; + if (!object.Equals(EventType, other.EventType)) return false; + return true; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() - { - return pb::JsonFormatter.ToDiagnosticString(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (targetRef_ != null) hash ^= TargetRef.GetHashCode(); + if (eventType_ != null) hash ^= EventType.GetHashCode(); + return hash; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) - { - if (targetRef_ != null) - { - output.WriteRawTag(10); - output.WriteMessage(TargetRef); - } - if (eventType_ != null) - { - output.WriteRawTag(18); - output.WriteMessage(EventType); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() - { - int size = 0; - if (targetRef_ != null) - { - size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetRef); - } - if (eventType_ != null) - { - size += 1 + pb::CodedOutputStream.ComputeMessageSize(EventType); - } - return size; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (targetRef_ != null) { + output.WriteRawTag(10); + output.WriteMessage(TargetRef); + } + if (eventType_ != null) { + output.WriteRawTag(18); + output.WriteMessage(EventType); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(SinkRef other) - { - if (other == null) - { - return; - } - if (other.targetRef_ != null) - { - if (targetRef_ == null) - { - targetRef_ = new global::Akka.Streams.Serialization.Proto.Msg.ActorRef(); - } - TargetRef.MergeFrom(other.TargetRef); - } - if (other.eventType_ != null) - { - if (eventType_ == null) - { - eventType_ = new global::Akka.Streams.Serialization.Proto.Msg.EventType(); - } - EventType.MergeFrom(other.EventType); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (targetRef_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetRef); + } + if (eventType_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EventType); + } + return size; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) - { - uint tag; - while ((tag = input.ReadTag()) != 0) - { - switch (tag) - { - default: - input.SkipLastField(); - break; - case 10: - { - if (targetRef_ == null) - { - targetRef_ = new global::Akka.Streams.Serialization.Proto.Msg.ActorRef(); - } - input.ReadMessage(targetRef_); - break; - } - case 18: - { - if (eventType_ == null) - { - eventType_ = new global::Akka.Streams.Serialization.Proto.Msg.EventType(); - } - input.ReadMessage(eventType_); - break; - } - } - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(SinkRef other) { + if (other == null) { + return; + } + if (other.targetRef_ != null) { + if (targetRef_ == null) { + targetRef_ = new global::Akka.Streams.Serialization.Proto.Msg.ActorRef(); + } + TargetRef.MergeFrom(other.TargetRef); + } + if (other.eventType_ != null) { + if (eventType_ == null) { + eventType_ = new global::Akka.Streams.Serialization.Proto.Msg.EventType(); + } + EventType.MergeFrom(other.EventType); + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (targetRef_ == null) { + targetRef_ = new global::Akka.Streams.Serialization.Proto.Msg.ActorRef(); + } + input.ReadMessage(targetRef_); + break; + } + case 18: { + if (eventType_ == null) { + eventType_ = new global::Akka.Streams.Serialization.Proto.Msg.EventType(); + } + input.ReadMessage(eventType_); + break; + } + } + } } - internal sealed partial class SourceRef : pb::IMessage - { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SourceRef()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor - { - get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[2]; } - } + internal sealed partial class SourceRef : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SourceRef()); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor - { - get { return Descriptor; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[2]; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public SourceRef() - { - OnConstruction(); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } - partial void OnConstruction(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public SourceRef() { + OnConstruction(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public SourceRef(SourceRef other) : this() - { - OriginRef = other.originRef_ != null ? other.OriginRef.Clone() : null; - EventType = other.eventType_ != null ? other.EventType.Clone() : null; - } + partial void OnConstruction(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public SourceRef Clone() - { - return new SourceRef(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public SourceRef(SourceRef other) : this() { + OriginRef = other.originRef_ != null ? other.OriginRef.Clone() : null; + EventType = other.eventType_ != null ? other.EventType.Clone() : null; + } - /// Field number for the "originRef" field. - public const int OriginRefFieldNumber = 1; - private global::Akka.Streams.Serialization.Proto.Msg.ActorRef originRef_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public global::Akka.Streams.Serialization.Proto.Msg.ActorRef OriginRef - { - get { return originRef_; } - set - { - originRef_ = value; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public SourceRef Clone() { + return new SourceRef(this); + } - /// Field number for the "eventType" field. - public const int EventTypeFieldNumber = 2; - private global::Akka.Streams.Serialization.Proto.Msg.EventType eventType_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public global::Akka.Streams.Serialization.Proto.Msg.EventType EventType - { - get { return eventType_; } - set - { - eventType_ = value; - } - } + /// Field number for the "originRef" field. + public const int OriginRefFieldNumber = 1; + private global::Akka.Streams.Serialization.Proto.Msg.ActorRef originRef_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Akka.Streams.Serialization.Proto.Msg.ActorRef OriginRef { + get { return originRef_; } + set { + originRef_ = value; + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) - { - return Equals(other as SourceRef); - } + /// Field number for the "eventType" field. + public const int EventTypeFieldNumber = 2; + private global::Akka.Streams.Serialization.Proto.Msg.EventType eventType_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Akka.Streams.Serialization.Proto.Msg.EventType EventType { + get { return eventType_; } + set { + eventType_ = value; + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(SourceRef other) - { - if (ReferenceEquals(other, null)) - { - return false; - } - if (ReferenceEquals(other, this)) - { - return true; - } - if (!object.Equals(OriginRef, other.OriginRef)) return false; - if (!object.Equals(EventType, other.EventType)) return false; - return true; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as SourceRef); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() - { - int hash = 1; - if (originRef_ != null) hash ^= OriginRef.GetHashCode(); - if (eventType_ != null) hash ^= EventType.GetHashCode(); - return hash; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(SourceRef other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(OriginRef, other.OriginRef)) return false; + if (!object.Equals(EventType, other.EventType)) return false; + return true; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() - { - return pb::JsonFormatter.ToDiagnosticString(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (originRef_ != null) hash ^= OriginRef.GetHashCode(); + if (eventType_ != null) hash ^= EventType.GetHashCode(); + return hash; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) - { - if (originRef_ != null) - { - output.WriteRawTag(10); - output.WriteMessage(OriginRef); - } - if (eventType_ != null) - { - output.WriteRawTag(18); - output.WriteMessage(EventType); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() - { - int size = 0; - if (originRef_ != null) - { - size += 1 + pb::CodedOutputStream.ComputeMessageSize(OriginRef); - } - if (eventType_ != null) - { - size += 1 + pb::CodedOutputStream.ComputeMessageSize(EventType); - } - return size; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (originRef_ != null) { + output.WriteRawTag(10); + output.WriteMessage(OriginRef); + } + if (eventType_ != null) { + output.WriteRawTag(18); + output.WriteMessage(EventType); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(SourceRef other) - { - if (other == null) - { - return; - } - if (other.originRef_ != null) - { - if (originRef_ == null) - { - originRef_ = new global::Akka.Streams.Serialization.Proto.Msg.ActorRef(); - } - OriginRef.MergeFrom(other.OriginRef); - } - if (other.eventType_ != null) - { - if (eventType_ == null) - { - eventType_ = new global::Akka.Streams.Serialization.Proto.Msg.EventType(); - } - EventType.MergeFrom(other.EventType); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (originRef_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(OriginRef); + } + if (eventType_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(EventType); + } + return size; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) - { - uint tag; - while ((tag = input.ReadTag()) != 0) - { - switch (tag) - { - default: - input.SkipLastField(); - break; - case 10: - { - if (originRef_ == null) - { - originRef_ = new global::Akka.Streams.Serialization.Proto.Msg.ActorRef(); - } - input.ReadMessage(originRef_); - break; - } - case 18: - { - if (eventType_ == null) - { - eventType_ = new global::Akka.Streams.Serialization.Proto.Msg.EventType(); - } - input.ReadMessage(eventType_); - break; - } - } - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(SourceRef other) { + if (other == null) { + return; + } + if (other.originRef_ != null) { + if (originRef_ == null) { + originRef_ = new global::Akka.Streams.Serialization.Proto.Msg.ActorRef(); + } + OriginRef.MergeFrom(other.OriginRef); + } + if (other.eventType_ != null) { + if (eventType_ == null) { + eventType_ = new global::Akka.Streams.Serialization.Proto.Msg.EventType(); + } + EventType.MergeFrom(other.EventType); + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (originRef_ == null) { + originRef_ = new global::Akka.Streams.Serialization.Proto.Msg.ActorRef(); + } + input.ReadMessage(originRef_); + break; + } + case 18: { + if (eventType_ == null) { + eventType_ = new global::Akka.Streams.Serialization.Proto.Msg.EventType(); + } + input.ReadMessage(eventType_); + break; + } + } + } } - internal sealed partial class ActorRef : pb::IMessage - { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ActorRef()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor - { - get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[3]; } - } + internal sealed partial class ActorRef : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new ActorRef()); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor - { - get { return Descriptor; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[3]; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public ActorRef() - { - OnConstruction(); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } - partial void OnConstruction(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ActorRef() { + OnConstruction(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public ActorRef(ActorRef other) : this() - { - path_ = other.path_; - } + partial void OnConstruction(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public ActorRef Clone() - { - return new ActorRef(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ActorRef(ActorRef other) : this() { + path_ = other.path_; + } - /// Field number for the "path" field. - public const int PathFieldNumber = 1; - private string path_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string Path - { - get { return path_; } - set - { - path_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public ActorRef Clone() { + return new ActorRef(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) - { - return Equals(other as ActorRef); - } + /// Field number for the "path" field. + public const int PathFieldNumber = 1; + private string path_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string Path { + get { return path_; } + set { + path_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(ActorRef other) - { - if (ReferenceEquals(other, null)) - { - return false; - } - if (ReferenceEquals(other, this)) - { - return true; - } - if (Path != other.Path) return false; - return true; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as ActorRef); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() - { - int hash = 1; - if (Path.Length != 0) hash ^= Path.GetHashCode(); - return hash; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(ActorRef other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Path != other.Path) return false; + return true; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() - { - return pb::JsonFormatter.ToDiagnosticString(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Path.Length != 0) hash ^= Path.GetHashCode(); + return hash; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) - { - if (Path.Length != 0) - { - output.WriteRawTag(10); - output.WriteString(Path); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() - { - int size = 0; - if (Path.Length != 0) - { - size += 1 + pb::CodedOutputStream.ComputeStringSize(Path); - } - return size; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Path.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Path); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(ActorRef other) - { - if (other == null) - { - return; - } - if (other.Path.Length != 0) - { - Path = other.Path; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Path.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Path); + } + return size; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) - { - uint tag; - while ((tag = input.ReadTag()) != 0) - { - switch (tag) - { - default: - input.SkipLastField(); - break; - case 10: - { - Path = input.ReadString(); - break; - } - } - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(ActorRef other) { + if (other == null) { + return; + } + if (other.Path.Length != 0) { + Path = other.Path; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + Path = input.ReadString(); + break; + } + } + } } - internal sealed partial class Payload : pb::IMessage - { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Payload()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor - { - get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[4]; } - } + internal sealed partial class Payload : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Payload()); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor - { - get { return Descriptor; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[4]; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public Payload() - { - OnConstruction(); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } - partial void OnConstruction(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Payload() { + OnConstruction(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public Payload(Payload other) : this() - { - enclosedMessage_ = other.enclosedMessage_; - serializerId_ = other.serializerId_; - messageManifest_ = other.messageManifest_; - } + partial void OnConstruction(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public Payload Clone() - { - return new Payload(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Payload(Payload other) : this() { + enclosedMessage_ = other.enclosedMessage_; + serializerId_ = other.serializerId_; + messageManifest_ = other.messageManifest_; + } - /// Field number for the "enclosedMessage" field. - public const int EnclosedMessageFieldNumber = 1; - private pb::ByteString enclosedMessage_ = pb::ByteString.Empty; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pb::ByteString EnclosedMessage - { - get { return enclosedMessage_; } - set - { - enclosedMessage_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Payload Clone() { + return new Payload(this); + } - /// Field number for the "serializerId" field. - public const int SerializerIdFieldNumber = 2; - private int serializerId_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int SerializerId - { - get { return serializerId_; } - set - { - serializerId_ = value; - } - } + /// Field number for the "enclosedMessage" field. + public const int EnclosedMessageFieldNumber = 1; + private pb::ByteString enclosedMessage_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString EnclosedMessage { + get { return enclosedMessage_; } + set { + enclosedMessage_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } - /// Field number for the "messageManifest" field. - public const int MessageManifestFieldNumber = 3; - private pb::ByteString messageManifest_ = pb::ByteString.Empty; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pb::ByteString MessageManifest - { - get { return messageManifest_; } - set - { - messageManifest_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } + /// Field number for the "serializerId" field. + public const int SerializerIdFieldNumber = 2; + private int serializerId_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int SerializerId { + get { return serializerId_; } + set { + serializerId_ = value; + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) - { - return Equals(other as Payload); - } + /// Field number for the "messageManifest" field. + public const int MessageManifestFieldNumber = 3; + private pb::ByteString messageManifest_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString MessageManifest { + get { return messageManifest_; } + set { + messageManifest_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(Payload other) - { - if (ReferenceEquals(other, null)) - { - return false; - } - if (ReferenceEquals(other, this)) - { - return true; - } - if (EnclosedMessage != other.EnclosedMessage) return false; - if (SerializerId != other.SerializerId) return false; - if (MessageManifest != other.MessageManifest) return false; - return true; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Payload); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() - { - int hash = 1; - if (EnclosedMessage.Length != 0) hash ^= EnclosedMessage.GetHashCode(); - if (SerializerId != 0) hash ^= SerializerId.GetHashCode(); - if (MessageManifest.Length != 0) hash ^= MessageManifest.GetHashCode(); - return hash; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Payload other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (EnclosedMessage != other.EnclosedMessage) return false; + if (SerializerId != other.SerializerId) return false; + if (MessageManifest != other.MessageManifest) return false; + return true; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() - { - return pb::JsonFormatter.ToDiagnosticString(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (EnclosedMessage.Length != 0) hash ^= EnclosedMessage.GetHashCode(); + if (SerializerId != 0) hash ^= SerializerId.GetHashCode(); + if (MessageManifest.Length != 0) hash ^= MessageManifest.GetHashCode(); + return hash; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) - { - if (EnclosedMessage.Length != 0) - { - output.WriteRawTag(10); - output.WriteBytes(EnclosedMessage); - } - if (SerializerId != 0) - { - output.WriteRawTag(16); - output.WriteInt32(SerializerId); - } - if (MessageManifest.Length != 0) - { - output.WriteRawTag(26); - output.WriteBytes(MessageManifest); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() - { - int size = 0; - if (EnclosedMessage.Length != 0) - { - size += 1 + pb::CodedOutputStream.ComputeBytesSize(EnclosedMessage); - } - if (SerializerId != 0) - { - size += 1 + pb::CodedOutputStream.ComputeInt32Size(SerializerId); - } - if (MessageManifest.Length != 0) - { - size += 1 + pb::CodedOutputStream.ComputeBytesSize(MessageManifest); - } - return size; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (EnclosedMessage.Length != 0) { + output.WriteRawTag(10); + output.WriteBytes(EnclosedMessage); + } + if (SerializerId != 0) { + output.WriteRawTag(16); + output.WriteInt32(SerializerId); + } + if (MessageManifest.Length != 0) { + output.WriteRawTag(26); + output.WriteBytes(MessageManifest); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(Payload other) - { - if (other == null) - { - return; - } - if (other.EnclosedMessage.Length != 0) - { - EnclosedMessage = other.EnclosedMessage; - } - if (other.SerializerId != 0) - { - SerializerId = other.SerializerId; - } - if (other.MessageManifest.Length != 0) - { - MessageManifest = other.MessageManifest; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (EnclosedMessage.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(EnclosedMessage); + } + if (SerializerId != 0) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(SerializerId); + } + if (MessageManifest.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(MessageManifest); + } + return size; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) - { - uint tag; - while ((tag = input.ReadTag()) != 0) - { - switch (tag) - { - default: - input.SkipLastField(); - break; - case 10: - { - EnclosedMessage = input.ReadBytes(); - break; - } - case 16: - { - SerializerId = input.ReadInt32(); - break; - } - case 26: - { - MessageManifest = input.ReadBytes(); - break; - } - } - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Payload other) { + if (other == null) { + return; + } + if (other.EnclosedMessage.Length != 0) { + EnclosedMessage = other.EnclosedMessage; + } + if (other.SerializerId != 0) { + SerializerId = other.SerializerId; + } + if (other.MessageManifest.Length != 0) { + MessageManifest = other.MessageManifest; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + EnclosedMessage = input.ReadBytes(); + break; + } + case 16: { + SerializerId = input.ReadInt32(); + break; + } + case 26: { + MessageManifest = input.ReadBytes(); + break; + } + } + } } - internal sealed partial class OnSubscribeHandshake : pb::IMessage - { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new OnSubscribeHandshake()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor - { - get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[5]; } - } + internal sealed partial class OnSubscribeHandshake : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new OnSubscribeHandshake()); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor - { - get { return Descriptor; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[5]; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public OnSubscribeHandshake() - { - OnConstruction(); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } - partial void OnConstruction(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public OnSubscribeHandshake() { + OnConstruction(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public OnSubscribeHandshake(OnSubscribeHandshake other) : this() - { - TargetRef = other.targetRef_ != null ? other.TargetRef.Clone() : null; - } + partial void OnConstruction(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public OnSubscribeHandshake Clone() - { - return new OnSubscribeHandshake(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public OnSubscribeHandshake(OnSubscribeHandshake other) : this() { + TargetRef = other.targetRef_ != null ? other.TargetRef.Clone() : null; + } - /// Field number for the "targetRef" field. - public const int TargetRefFieldNumber = 1; - private global::Akka.Streams.Serialization.Proto.Msg.ActorRef targetRef_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public global::Akka.Streams.Serialization.Proto.Msg.ActorRef TargetRef - { - get { return targetRef_; } - set - { - targetRef_ = value; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public OnSubscribeHandshake Clone() { + return new OnSubscribeHandshake(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) - { - return Equals(other as OnSubscribeHandshake); - } + /// Field number for the "targetRef" field. + public const int TargetRefFieldNumber = 1; + private global::Akka.Streams.Serialization.Proto.Msg.ActorRef targetRef_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Akka.Streams.Serialization.Proto.Msg.ActorRef TargetRef { + get { return targetRef_; } + set { + targetRef_ = value; + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(OnSubscribeHandshake other) - { - if (ReferenceEquals(other, null)) - { - return false; - } - if (ReferenceEquals(other, this)) - { - return true; - } - if (!object.Equals(TargetRef, other.TargetRef)) return false; - return true; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as OnSubscribeHandshake); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() - { - int hash = 1; - if (targetRef_ != null) hash ^= TargetRef.GetHashCode(); - return hash; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(OnSubscribeHandshake other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(TargetRef, other.TargetRef)) return false; + return true; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() - { - return pb::JsonFormatter.ToDiagnosticString(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (targetRef_ != null) hash ^= TargetRef.GetHashCode(); + return hash; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) - { - if (targetRef_ != null) - { - output.WriteRawTag(10); - output.WriteMessage(TargetRef); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() - { - int size = 0; - if (targetRef_ != null) - { - size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetRef); - } - return size; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (targetRef_ != null) { + output.WriteRawTag(10); + output.WriteMessage(TargetRef); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(OnSubscribeHandshake other) - { - if (other == null) - { - return; - } - if (other.targetRef_ != null) - { - if (targetRef_ == null) - { - targetRef_ = new global::Akka.Streams.Serialization.Proto.Msg.ActorRef(); - } - TargetRef.MergeFrom(other.TargetRef); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (targetRef_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(TargetRef); + } + return size; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) - { - uint tag; - while ((tag = input.ReadTag()) != 0) - { - switch (tag) - { - default: - input.SkipLastField(); - break; - case 10: - { - if (targetRef_ == null) - { - targetRef_ = new global::Akka.Streams.Serialization.Proto.Msg.ActorRef(); - } - input.ReadMessage(targetRef_); - break; - } - } - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(OnSubscribeHandshake other) { + if (other == null) { + return; + } + if (other.targetRef_ != null) { + if (targetRef_ == null) { + targetRef_ = new global::Akka.Streams.Serialization.Proto.Msg.ActorRef(); + } + TargetRef.MergeFrom(other.TargetRef); + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + if (targetRef_ == null) { + targetRef_ = new global::Akka.Streams.Serialization.Proto.Msg.ActorRef(); + } + input.ReadMessage(targetRef_); + break; + } + } + } } - internal sealed partial class CumulativeDemand : pb::IMessage - { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CumulativeDemand()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor - { - get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[6]; } - } + internal sealed partial class CumulativeDemand : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CumulativeDemand()); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor - { - get { return Descriptor; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[6]; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public CumulativeDemand() - { - OnConstruction(); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } - partial void OnConstruction(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public CumulativeDemand() { + OnConstruction(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public CumulativeDemand(CumulativeDemand other) : this() - { - seqNr_ = other.seqNr_; - } + partial void OnConstruction(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public CumulativeDemand Clone() - { - return new CumulativeDemand(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public CumulativeDemand(CumulativeDemand other) : this() { + seqNr_ = other.seqNr_; + } - /// Field number for the "seqNr" field. - public const int SeqNrFieldNumber = 1; - private long seqNr_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public long SeqNr - { - get { return seqNr_; } - set - { - seqNr_ = value; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public CumulativeDemand Clone() { + return new CumulativeDemand(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) - { - return Equals(other as CumulativeDemand); - } + /// Field number for the "seqNr" field. + public const int SeqNrFieldNumber = 1; + private long seqNr_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long SeqNr { + get { return seqNr_; } + set { + seqNr_ = value; + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(CumulativeDemand other) - { - if (ReferenceEquals(other, null)) - { - return false; - } - if (ReferenceEquals(other, this)) - { - return true; - } - if (SeqNr != other.SeqNr) return false; - return true; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as CumulativeDemand); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() - { - int hash = 1; - if (SeqNr != 0L) hash ^= SeqNr.GetHashCode(); - return hash; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(CumulativeDemand other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (SeqNr != other.SeqNr) return false; + return true; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() - { - return pb::JsonFormatter.ToDiagnosticString(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (SeqNr != 0L) hash ^= SeqNr.GetHashCode(); + return hash; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) - { - if (SeqNr != 0L) - { - output.WriteRawTag(8); - output.WriteInt64(SeqNr); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() - { - int size = 0; - if (SeqNr != 0L) - { - size += 1 + pb::CodedOutputStream.ComputeInt64Size(SeqNr); - } - return size; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (SeqNr != 0L) { + output.WriteRawTag(8); + output.WriteInt64(SeqNr); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(CumulativeDemand other) - { - if (other == null) - { - return; - } - if (other.SeqNr != 0L) - { - SeqNr = other.SeqNr; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (SeqNr != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(SeqNr); + } + return size; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) - { - uint tag; - while ((tag = input.ReadTag()) != 0) - { - switch (tag) - { - default: - input.SkipLastField(); - break; - case 8: - { - SeqNr = input.ReadInt64(); - break; - } - } - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(CumulativeDemand other) { + if (other == null) { + return; + } + if (other.SeqNr != 0L) { + SeqNr = other.SeqNr; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 8: { + SeqNr = input.ReadInt64(); + break; + } + } + } } - internal sealed partial class SequencedOnNext : pb::IMessage - { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SequencedOnNext()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor - { - get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[7]; } - } + internal sealed partial class SequencedOnNext : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new SequencedOnNext()); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor - { - get { return Descriptor; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[7]; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public SequencedOnNext() - { - OnConstruction(); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } - partial void OnConstruction(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public SequencedOnNext() { + OnConstruction(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public SequencedOnNext(SequencedOnNext other) : this() - { - seqNr_ = other.seqNr_; - Payload = other.payload_ != null ? other.Payload.Clone() : null; - } + partial void OnConstruction(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public SequencedOnNext Clone() - { - return new SequencedOnNext(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public SequencedOnNext(SequencedOnNext other) : this() { + seqNr_ = other.seqNr_; + Payload = other.payload_ != null ? other.Payload.Clone() : null; + } - /// Field number for the "seqNr" field. - public const int SeqNrFieldNumber = 1; - private long seqNr_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public long SeqNr - { - get { return seqNr_; } - set - { - seqNr_ = value; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public SequencedOnNext Clone() { + return new SequencedOnNext(this); + } - /// Field number for the "payload" field. - public const int PayloadFieldNumber = 2; - private global::Akka.Streams.Serialization.Proto.Msg.Payload payload_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public global::Akka.Streams.Serialization.Proto.Msg.Payload Payload - { - get { return payload_; } - set - { - payload_ = value; - } - } + /// Field number for the "seqNr" field. + public const int SeqNrFieldNumber = 1; + private long seqNr_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long SeqNr { + get { return seqNr_; } + set { + seqNr_ = value; + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) - { - return Equals(other as SequencedOnNext); - } + /// Field number for the "payload" field. + public const int PayloadFieldNumber = 2; + private global::Akka.Streams.Serialization.Proto.Msg.Payload payload_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::Akka.Streams.Serialization.Proto.Msg.Payload Payload { + get { return payload_; } + set { + payload_ = value; + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(SequencedOnNext other) - { - if (ReferenceEquals(other, null)) - { - return false; - } - if (ReferenceEquals(other, this)) - { - return true; - } - if (SeqNr != other.SeqNr) return false; - if (!object.Equals(Payload, other.Payload)) return false; - return true; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as SequencedOnNext); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() - { - int hash = 1; - if (SeqNr != 0L) hash ^= SeqNr.GetHashCode(); - if (payload_ != null) hash ^= Payload.GetHashCode(); - return hash; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(SequencedOnNext other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (SeqNr != other.SeqNr) return false; + if (!object.Equals(Payload, other.Payload)) return false; + return true; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() - { - return pb::JsonFormatter.ToDiagnosticString(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (SeqNr != 0L) hash ^= SeqNr.GetHashCode(); + if (payload_ != null) hash ^= Payload.GetHashCode(); + return hash; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) - { - if (SeqNr != 0L) - { - output.WriteRawTag(8); - output.WriteInt64(SeqNr); - } - if (payload_ != null) - { - output.WriteRawTag(18); - output.WriteMessage(Payload); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() - { - int size = 0; - if (SeqNr != 0L) - { - size += 1 + pb::CodedOutputStream.ComputeInt64Size(SeqNr); - } - if (payload_ != null) - { - size += 1 + pb::CodedOutputStream.ComputeMessageSize(Payload); - } - return size; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (SeqNr != 0L) { + output.WriteRawTag(8); + output.WriteInt64(SeqNr); + } + if (payload_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Payload); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(SequencedOnNext other) - { - if (other == null) - { - return; - } - if (other.SeqNr != 0L) - { - SeqNr = other.SeqNr; - } - if (other.payload_ != null) - { - if (payload_ == null) - { - payload_ = new global::Akka.Streams.Serialization.Proto.Msg.Payload(); - } - Payload.MergeFrom(other.Payload); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (SeqNr != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(SeqNr); + } + if (payload_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Payload); + } + return size; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) - { - uint tag; - while ((tag = input.ReadTag()) != 0) - { - switch (tag) - { - default: - input.SkipLastField(); - break; - case 8: - { - SeqNr = input.ReadInt64(); - break; - } - case 18: - { - if (payload_ == null) - { - payload_ = new global::Akka.Streams.Serialization.Proto.Msg.Payload(); - } - input.ReadMessage(payload_); - break; - } - } - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(SequencedOnNext other) { + if (other == null) { + return; + } + if (other.SeqNr != 0L) { + SeqNr = other.SeqNr; + } + if (other.payload_ != null) { + if (payload_ == null) { + payload_ = new global::Akka.Streams.Serialization.Proto.Msg.Payload(); + } + Payload.MergeFrom(other.Payload); + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 8: { + SeqNr = input.ReadInt64(); + break; + } + case 18: { + if (payload_ == null) { + payload_ = new global::Akka.Streams.Serialization.Proto.Msg.Payload(); + } + input.ReadMessage(payload_); + break; + } + } + } } - internal sealed partial class RemoteStreamFailure : pb::IMessage - { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RemoteStreamFailure()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor - { - get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[8]; } - } + internal sealed partial class RemoteStreamFailure : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RemoteStreamFailure()); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor - { - get { return Descriptor; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[8]; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public RemoteStreamFailure() - { - OnConstruction(); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } - partial void OnConstruction(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public RemoteStreamFailure() { + OnConstruction(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public RemoteStreamFailure(RemoteStreamFailure other) : this() - { - cause_ = other.cause_; - } + partial void OnConstruction(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public RemoteStreamFailure Clone() - { - return new RemoteStreamFailure(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public RemoteStreamFailure(RemoteStreamFailure other) : this() { + cause_ = other.cause_; + } - /// Field number for the "cause" field. - public const int CauseFieldNumber = 1; - private pb::ByteString cause_ = pb::ByteString.Empty; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pb::ByteString Cause - { - get { return cause_; } - set - { - cause_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public RemoteStreamFailure Clone() { + return new RemoteStreamFailure(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) - { - return Equals(other as RemoteStreamFailure); - } + /// Field number for the "cause" field. + public const int CauseFieldNumber = 1; + private pb::ByteString cause_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString Cause { + get { return cause_; } + set { + cause_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(RemoteStreamFailure other) - { - if (ReferenceEquals(other, null)) - { - return false; - } - if (ReferenceEquals(other, this)) - { - return true; - } - if (Cause != other.Cause) return false; - return true; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as RemoteStreamFailure); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() - { - int hash = 1; - if (Cause.Length != 0) hash ^= Cause.GetHashCode(); - return hash; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(RemoteStreamFailure other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Cause != other.Cause) return false; + return true; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() - { - return pb::JsonFormatter.ToDiagnosticString(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (Cause.Length != 0) hash ^= Cause.GetHashCode(); + return hash; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) - { - if (Cause.Length != 0) - { - output.WriteRawTag(10); - output.WriteBytes(Cause); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() - { - int size = 0; - if (Cause.Length != 0) - { - size += 1 + pb::CodedOutputStream.ComputeBytesSize(Cause); - } - return size; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (Cause.Length != 0) { + output.WriteRawTag(10); + output.WriteBytes(Cause); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(RemoteStreamFailure other) - { - if (other == null) - { - return; - } - if (other.Cause.Length != 0) - { - Cause = other.Cause; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (Cause.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Cause); + } + return size; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) - { - uint tag; - while ((tag = input.ReadTag()) != 0) - { - switch (tag) - { - default: - input.SkipLastField(); - break; - case 10: - { - Cause = input.ReadBytes(); - break; - } - } - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(RemoteStreamFailure other) { + if (other == null) { + return; + } + if (other.Cause.Length != 0) { + Cause = other.Cause; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 10: { + Cause = input.ReadBytes(); + break; + } + } + } } - internal sealed partial class RemoteStreamCompleted : pb::IMessage - { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RemoteStreamCompleted()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor - { - get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[9]; } - } + internal sealed partial class RemoteStreamCompleted : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new RemoteStreamCompleted()); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor - { - get { return Descriptor; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::Akka.Streams.Serialization.Proto.Msg.StreamRefMessagesReflection.Descriptor.MessageTypes[9]; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public RemoteStreamCompleted() - { - OnConstruction(); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } - partial void OnConstruction(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public RemoteStreamCompleted() { + OnConstruction(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public RemoteStreamCompleted(RemoteStreamCompleted other) : this() - { - seqNr_ = other.seqNr_; - } + partial void OnConstruction(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public RemoteStreamCompleted Clone() - { - return new RemoteStreamCompleted(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public RemoteStreamCompleted(RemoteStreamCompleted other) : this() { + seqNr_ = other.seqNr_; + } - /// Field number for the "seqNr" field. - public const int SeqNrFieldNumber = 1; - private long seqNr_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public long SeqNr - { - get { return seqNr_; } - set - { - seqNr_ = value; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public RemoteStreamCompleted Clone() { + return new RemoteStreamCompleted(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) - { - return Equals(other as RemoteStreamCompleted); - } + /// Field number for the "seqNr" field. + public const int SeqNrFieldNumber = 1; + private long seqNr_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long SeqNr { + get { return seqNr_; } + set { + seqNr_ = value; + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(RemoteStreamCompleted other) - { - if (ReferenceEquals(other, null)) - { - return false; - } - if (ReferenceEquals(other, this)) - { - return true; - } - if (SeqNr != other.SeqNr) return false; - return true; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as RemoteStreamCompleted); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() - { - int hash = 1; - if (SeqNr != 0L) hash ^= SeqNr.GetHashCode(); - return hash; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(RemoteStreamCompleted other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (SeqNr != other.SeqNr) return false; + return true; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() - { - return pb::JsonFormatter.ToDiagnosticString(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (SeqNr != 0L) hash ^= SeqNr.GetHashCode(); + return hash; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) - { - if (SeqNr != 0L) - { - output.WriteRawTag(8); - output.WriteInt64(SeqNr); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() - { - int size = 0; - if (SeqNr != 0L) - { - size += 1 + pb::CodedOutputStream.ComputeInt64Size(SeqNr); - } - return size; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (SeqNr != 0L) { + output.WriteRawTag(8); + output.WriteInt64(SeqNr); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(RemoteStreamCompleted other) - { - if (other == null) - { - return; - } - if (other.SeqNr != 0L) - { - SeqNr = other.SeqNr; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (SeqNr != 0L) { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(SeqNr); + } + return size; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) - { - uint tag; - while ((tag = input.ReadTag()) != 0) - { - switch (tag) - { - default: - input.SkipLastField(); - break; - case 8: - { - SeqNr = input.ReadInt64(); - break; - } - } - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(RemoteStreamCompleted other) { + if (other == null) { + return; + } + if (other.SeqNr != 0L) { + SeqNr = other.SeqNr; + } + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + input.SkipLastField(); + break; + case 8: { + SeqNr = input.ReadInt64(); + break; + } + } + } } - #endregion + } + + #endregion } diff --git a/src/core/Akka.Tests.Performance/Actor/ActorSelectionSpecs.cs b/src/core/Akka.Tests.Performance/Actor/ActorSelectionSpecs.cs index d6017c18313..b6104b3aef7 100644 --- a/src/core/Akka.Tests.Performance/Actor/ActorSelectionSpecs.cs +++ b/src/core/Akka.Tests.Performance/Actor/ActorSelectionSpecs.cs @@ -66,6 +66,7 @@ public void Setup(BenchmarkContext context) [PerfBenchmark(Description = "Tests the message delivery throughput of NEW ActorSelections to NEW actors", NumberOfIterations = 13, RunMode = RunMode.Throughput, RunTimeMilliseconds = 1000, TestMode = TestMode.Measurement)] [CounterMeasurement(ActorSelectionCounterName)] + [MemoryMeasurement(MemoryMetric.TotalBytesAllocated)] public void New_ActorSelection_on_new_actor_throughput(BenchmarkContext context) { var actorRef = System.ActorOf(_oneMessageBenchmarkProps); // create a new actor every time @@ -77,6 +78,7 @@ public void New_ActorSelection_on_new_actor_throughput(BenchmarkContext context) [PerfBenchmark(Description = "Tests the message delivery throughput of REUSABLE ActorSelections to PRE-EXISTING actors", NumberOfIterations = 13, RunMode = RunMode.Iterations, TestMode = TestMode.Measurement)] [CounterMeasurement(ActorSelectionCounterName)] + [MemoryMeasurement(MemoryMetric.TotalBytesAllocated)] public void Reused_ActorSelection_on_pre_existing_actor_throughput(BenchmarkContext context) { var actorSelection = System.ActorSelection(_receiverActorPath); @@ -91,6 +93,7 @@ public void Reused_ActorSelection_on_pre_existing_actor_throughput(BenchmarkCont [PerfBenchmark(Description = "Tests the message delivery throughput of NEW ActorSelections to PRE-EXISTING actors. This is really a stress test.", NumberOfIterations = 13, RunMode = RunMode.Iterations, TestMode = TestMode.Measurement)] [CounterMeasurement(ActorSelectionCounterName)] + [MemoryMeasurement(MemoryMetric.TotalBytesAllocated)] public void New_ActorSelection_on_pre_existing_actor_throughput(BenchmarkContext context) { for (var i = 0; i < NumberOfMessages;) @@ -104,6 +107,7 @@ public void New_ActorSelection_on_pre_existing_actor_throughput(BenchmarkContext [PerfBenchmark(Description = "Tests the throughput of resolving an ActorSelection on a pre-existing actor via ResolveOne", NumberOfIterations = 13, RunMode = RunMode.Throughput, RunTimeMilliseconds = 1000, TestMode = TestMode.Measurement)] [CounterMeasurement(ActorSelectionCounterName)] + [MemoryMeasurement(MemoryMetric.TotalBytesAllocated)] public void ActorSelection_ResolveOne_throughput(BenchmarkContext context) { var actorRef= System.ActorSelection(_receiverActorPath).ResolveOne(TimeSpan.FromSeconds(2)).Result; // send that actor a message via selection @@ -113,6 +117,7 @@ public void ActorSelection_ResolveOne_throughput(BenchmarkContext context) [PerfBenchmark(Description = "Continuously creates actors and attempts to resolve them immediately. Used to surface race conditions.", NumberOfIterations = 13, RunMode = RunMode.Throughput, RunTimeMilliseconds = 1000, TestMode = TestMode.Measurement)] [CounterMeasurement(ActorSelectionCounterName)] + [MemoryMeasurement(MemoryMetric.TotalBytesAllocated)] public void ActorSelection_ResolveOne_stress_test(BenchmarkContext context) { var actorRef = System.ActorOf(_oneMessageBenchmarkProps); // create a new actor every time diff --git a/src/core/Akka.Tests.Performance/Dispatch/ForkJoinDispatcherThroughputSpec.cs b/src/core/Akka.Tests.Performance/Dispatch/ForkJoinDispatcherThroughputSpec.cs index 4401921e266..8771a3129d5 100644 --- a/src/core/Akka.Tests.Performance/Dispatch/ForkJoinDispatcherThroughputSpec.cs +++ b/src/core/Akka.Tests.Performance/Dispatch/ForkJoinDispatcherThroughputSpec.cs @@ -45,4 +45,17 @@ protected override MessageDispatcherConfigurator Configurator() return new DispatcherConfigurator(DispatcherConfiguration, Prereqs); } } + + public class ChannelDispatcherExecutorThroughputSpec : WarmDispatcherThroughputSpecBase + { + public static Config DispatcherConfiguration => ConfigurationFactory.ParseString(@" + id = PerfTest + executor = channel-executor + "); + + protected override MessageDispatcherConfigurator Configurator() + { + return new DispatcherConfigurator(DispatcherConfiguration, Prereqs); + } + } } diff --git a/src/core/Akka.Tests.Shared.Internals/Akka.Tests.Shared.Internals.csproj b/src/core/Akka.Tests.Shared.Internals/Akka.Tests.Shared.Internals.csproj index 9e398362397..465234b72e3 100644 --- a/src/core/Akka.Tests.Shared.Internals/Akka.Tests.Shared.Internals.csproj +++ b/src/core/Akka.Tests.Shared.Internals/Akka.Tests.Shared.Internals.csproj @@ -14,6 +14,7 @@ + diff --git a/src/core/Akka.Tests.Shared.Internals/Helpers/FSharpDelegateHelper.cs b/src/core/Akka.Tests.Shared.Internals/Helpers/FSharpDelegateHelper.cs index b26ec5be7b1..c684eb0b3e7 100644 --- a/src/core/Akka.Tests.Shared.Internals/Helpers/FSharpDelegateHelper.cs +++ b/src/core/Akka.Tests.Shared.Internals/Helpers/FSharpDelegateHelper.cs @@ -5,7 +5,6 @@ // //----------------------------------------------------------------------- -#if FSCHECK using System; using Microsoft.FSharp.Core; @@ -38,4 +37,3 @@ public static FSharpFunc>> Create Sys.ActorSelection("/user/a/**/d").Tell(new Identify(4), probe.Ref); + illegalDoubleWildCard.Should().Throw(); } [Fact] diff --git a/src/core/Akka.Tests/Actor/DeadLetterSupressionSpec.cs b/src/core/Akka.Tests/Actor/DeadLetterSupressionSpec.cs index 8e0a3604dee..ed1ef87e6ec 100644 --- a/src/core/Akka.Tests/Actor/DeadLetterSupressionSpec.cs +++ b/src/core/Akka.Tests/Actor/DeadLetterSupressionSpec.cs @@ -82,6 +82,19 @@ public void Must_suppress_message_from_default_dead_letters_logging_sent_to_dead allDeadLetter.Recipient.Should().Be(deadActor); allListener.ExpectNoMsg(200.Milliseconds()); + + // unwrap for ActorSelection + Sys.ActorSelection(deadActor.Path).Tell(new SuppressedMessage()); + Sys.ActorSelection(deadActor.Path).Tell(new NormalMessage()); + + // the recipient ref isn't the same as deadActor here so only checking the message + deadLetter = deadListener.ExpectMsg();// + deadLetter.Message.Should().BeOfType(); + suppressedDeadLetter = suppressedListener.ExpectMsg(); + suppressedDeadLetter.Message.Should().BeOfType(); + + deadListener.ExpectNoMsg(200.Milliseconds()); + suppressedListener.ExpectNoMsg(200.Milliseconds()); } [Fact] @@ -123,6 +136,22 @@ public void Must_suppress_message_from_default_dead_letters_logging_sent_to_dead deadListener.ExpectNoMsg(TimeSpan.Zero); suppressedListener.ExpectNoMsg(TimeSpan.Zero); allListener.ExpectNoMsg(TimeSpan.Zero); + + // unwrap for ActorSelection + Sys.ActorSelection(Sys.DeadLetters.Path).Tell(new SuppressedMessage()); + Sys.ActorSelection(Sys.DeadLetters.Path).Tell(new NormalMessage()); + + deadLetter = deadListener.ExpectMsg(); + deadLetter.Message.Should().BeOfType(); + deadLetter.Sender.Should().Be(TestActor); + deadLetter.Recipient.Should().Be(Sys.DeadLetters); + suppressedDeadLetter = suppressedListener.ExpectMsg(); + suppressedDeadLetter.Message.Should().BeOfType(); + suppressedDeadLetter.Sender.Should().Be(TestActor); + suppressedDeadLetter.Recipient.Should().Be(Sys.DeadLetters); + + deadListener.ExpectNoMsg(200.Milliseconds()); + suppressedListener.ExpectNoMsg(200.Milliseconds()); } } } diff --git a/src/core/Akka.Tests/Actor/DeadLetterSuspensionSpec.cs b/src/core/Akka.Tests/Actor/DeadLetterSuspensionSpec.cs index 35af70cf1df..96b7aff19e9 100644 --- a/src/core/Akka.Tests/Actor/DeadLetterSuspensionSpec.cs +++ b/src/core/Akka.Tests/Actor/DeadLetterSuspensionSpec.cs @@ -8,6 +8,7 @@ using System.Threading; using Akka.Actor; using Akka.Configuration; +using Akka.Event; using Akka.TestKit; using Xunit; @@ -15,12 +16,46 @@ namespace Akka.Tests.Actor { public class DeadLetterSuspensionSpec : AkkaSpec { + private class Dropping : ActorBase + { + public static Props Props() => Akka.Actor.Props.Create(() => new Dropping()); + + protected override bool Receive(object message) + { + switch (message) + { + case int n: + Context.System.EventStream.Publish(new Dropped(n, "Don't like numbers", Self)); + return true; + } + return false; + } + } + + private class Unandled : ActorBase + { + public static Props Props() => Akka.Actor.Props.Create(() => new Unandled()); + + protected override bool Receive(object message) + { + switch (message) + { + case int n: + Unhandled(n); + return true; + } + return false; + } + } + private static readonly Config Config = ConfigurationFactory.ParseString(@" akka.loglevel = INFO - akka.log-dead-letters = 3 + akka.log-dead-letters = 4 akka.log-dead-letters-suspend-duration = 2s"); private readonly IActorRef _deadActor; + private readonly IActorRef _droppingActor; + private readonly IActorRef _unhandledActor; public DeadLetterSuspensionSpec() : base(Config) @@ -29,10 +64,20 @@ public DeadLetterSuspensionSpec() Watch(_deadActor); _deadActor.Tell(PoisonPill.Instance); ExpectTerminated(_deadActor); + + _droppingActor = Sys.ActorOf(Dropping.Props(), "droppingActor"); + _unhandledActor = Sys.ActorOf(Unandled.Props(), "unhandledActor"); } private string ExpectedDeadLettersLogMessage(int count) => - $"Message [{count.GetType().Name}] from {TestActor.Path} to {_deadActor.Path} was not delivered. [{count}] dead letters encountered"; + $"Message [{count.GetType().Name}] from {TestActor} to {_deadActor} was not delivered. [{count}] dead letters encountered"; + + private string ExpectedDroppedLogMessage(int count) => + $"Message [{count.GetType().Name}] to {_droppingActor} was dropped. Don't like numbers. [{count}] dead letters encountered"; + + private string ExpectedUnhandledLogMessage(int count) => + $"Message [{count.GetType().Name}] from {TestActor} to {_unhandledActor} was unhandled. [{count}] dead letters encountered"; + [Fact] public void Must_suspend_dead_letters_logging_when_reaching_akka_log_dead_letters_and_then_re_enable() @@ -42,28 +87,31 @@ public void Must_suspend_dead_letters_logging_when_reaching_akka_log_dead_letter .Expect(1, () => _deadActor.Tell(1)); EventFilter - .Info(start: ExpectedDeadLettersLogMessage(2)) - .Expect(1, () => _deadActor.Tell(2)); + .Info(start: ExpectedDroppedLogMessage(2)) + .Expect(1, () => _droppingActor.Tell(2)); EventFilter - .Info(start: ExpectedDeadLettersLogMessage(3) + ", no more dead letters will be logged in next") - .Expect(1, () => _deadActor.Tell(3)); + .Info(start: ExpectedUnhandledLogMessage(3)) + .Expect(1, () => _unhandledActor.Tell(3)); - _deadActor.Tell(4); + EventFilter + .Info(start: ExpectedDeadLettersLogMessage(4) + ", no more dead letters will be logged in next") + .Expect(1, () => _deadActor.Tell(4)); _deadActor.Tell(5); + _droppingActor.Tell(6); // let suspend-duration elapse Thread.Sleep(2050); // re-enabled EventFilter - .Info(start: ExpectedDeadLettersLogMessage(6) + ", of which 2 were not logged") - .Expect(1, () => _deadActor.Tell(6)); + .Info(start: ExpectedDeadLettersLogMessage(7) + ", of which 2 were not logged") + .Expect(1, () => _deadActor.Tell(7)); // reset count EventFilter .Info(start: ExpectedDeadLettersLogMessage(1)) - .Expect(1, () => _deadActor.Tell(7)); + .Expect(1, () => _deadActor.Tell(8)); } } } diff --git a/src/core/Akka.Tests/Actor/Dispatch/ActorModelSpec.cs b/src/core/Akka.Tests/Actor/Dispatch/ActorModelSpec.cs index 69c7a8cf287..6388d94f569 100644 --- a/src/core/Akka.Tests/Actor/Dispatch/ActorModelSpec.cs +++ b/src/core/Akka.Tests/Actor/Dispatch/ActorModelSpec.cs @@ -21,12 +21,17 @@ using Akka.Util; using Akka.Util.Internal; using Xunit; +using Xunit.Abstractions; namespace Akka.Tests.Actor.Dispatch { public abstract class ActorModelSpec : AkkaSpec { - protected ActorModelSpec(Config hocon) : base(hocon) { } + private readonly ITestOutputHelper _testOutputHelper; + protected ActorModelSpec(Config hocon, ITestOutputHelper output = null) : base(hocon, output) + { + _testOutputHelper = output; + } interface IActorModelMessage : INoSerializationVerificationNeeded { } @@ -168,6 +173,13 @@ private DoubleStop() { } public static readonly DoubleStop Instance = new DoubleStop(); } + private class GetStats : IActorModelMessage + { + private GetStats(){} + + public static readonly GetStats Instance = new GetStats(); + } + sealed class ThrowException : IActorModelMessage { public ThrowException(Exception e) @@ -184,7 +196,7 @@ public ThrowException(Exception e) class DispatcherActor : ReceiveActor { private Switch _busy = new Switch(false); - + private readonly ILoggingAdapter _log = Context.GetLogger(); private MessageDispatcherInterceptor _interceptor = Context.Dispatcher.AsInstanceOf(); private void Ack() @@ -222,6 +234,11 @@ public DispatcherActor() Receive(interrupt => { Ack(); Sender.Tell(interrupt.Expect); _busy.SwitchOff(); }); Receive(throwEx => { Ack(); _busy.SwitchOff(); throw throwEx.E; }, throwEx => true); Receive(doubleStop => { Ack(); Context.Stop(Self); Context.Stop(Self); _busy.SwitchOff(); }); + Receive(stats => { + Ack(); + Sender.Tell(_interceptor.GetStats(Self)); + _busy.SwitchOff(); + }); } } @@ -474,7 +491,7 @@ public void A_dispatcher_must_process_messages_one_at_a_time() AssertRefDefaultZero(a, registers: 1, msgsReceived: 3, msgsProcessed: 3, unregisters: 1, dispatcher: dispatcher); } - [Fact(Skip = "Racy on Azure DevOps")] + [Fact] public void A_dispatcher_must_handle_queuing_from_multiple_threads() { var dispatcher = InterceptedDispatcher(); @@ -487,14 +504,26 @@ public void A_dispatcher_must_handle_queuing_from_multiple_threads() { foreach (var c in Enumerable.Range(1, 20)) { - a.Tell(new WaitAck(1, counter)); + a.Tell(new CountDown(counter)); } }); } - AssertCountdown(counter, (int)Dilated(TimeSpan.FromSeconds(3.0)).TotalMilliseconds, "Should process 200 messages"); - AssertRefDefaultZero(a, dispatcher, registers: 1, msgsReceived: 200, msgsProcessed: 200); - Sys.Stop(a); + try + { + AssertCountdown(counter, (int)Dilated(TimeSpan.FromSeconds(3.0)).TotalMilliseconds, + "Should process 200 messages"); + AssertRefDefaultZero(a, dispatcher, registers: 1, msgsReceived: 200, msgsProcessed: 200); + } + finally + { + var stats = a.Ask(GetStats.Instance).Result; + _testOutputHelper.WriteLine("Observed stats: {0}", stats); + + Sys.Stop(a); + } + + } [Fact] @@ -643,7 +672,7 @@ public class DispatcherModelSpec : ActorModelSpec "; - public DispatcherModelSpec() : base(DispatcherHocon) { } + public DispatcherModelSpec(ITestOutputHelper output) : base(DispatcherHocon, output) { } protected override MessageDispatcherInterceptor InterceptedDispatcher() { diff --git a/src/core/Akka.Tests/Event/EventStreamSpec.cs b/src/core/Akka.Tests/Event/EventStreamSpec.cs index c7f9cc45d8f..9347b5ac306 100644 --- a/src/core/Akka.Tests/Event/EventStreamSpec.cs +++ b/src/core/Akka.Tests/Event/EventStreamSpec.cs @@ -296,6 +296,7 @@ private static string GetDebugUnhandledMessagesConfig() akka { actor.serialize-messages = off actor.debug.unhandled = on + log-dead-letters = off stdout-loglevel = DEBUG loglevel = DEBUG loggers = [""%logger%""] diff --git a/src/core/Akka.Tests/IO/TcpIntegrationSpec.cs b/src/core/Akka.Tests/IO/TcpIntegrationSpec.cs index 67937d68e57..cb8abc1c4d3 100644 --- a/src/core/Akka.Tests/IO/TcpIntegrationSpec.cs +++ b/src/core/Akka.Tests/IO/TcpIntegrationSpec.cs @@ -45,6 +45,8 @@ class AckWithValue : Tcp.Event public TcpIntegrationSpec(ITestOutputHelper output) : base($@"akka.loglevel = DEBUG + akka.actor.serialize-creators = on + akka.actor.serialize-messages = on akka.io.tcp.trace-logging = true akka.io.tcp.write-commands-queue-max-size = {InternalConnectionActorMaxQueueSize}", output: output) { } @@ -190,7 +192,7 @@ public void The_TCP_transport_implementation_should_properly_support_connecting_ var targetAddress = new DnsEndPoint("localhost", boundMsg.LocalAddress.AsInstanceOf().Port); var clientHandler = CreateTestProbe(); Sys.Tcp().Tell(new Tcp.Connect(targetAddress), clientHandler); - clientHandler.ExpectMsg(TimeSpan.FromMinutes(10)); + clientHandler.ExpectMsg(TimeSpan.FromSeconds(3)); var clientEp = clientHandler.Sender; clientEp.Tell(new Tcp.Register(clientHandler)); serverHandler.ExpectMsg(); diff --git a/src/core/Akka.Tests/IO/TcpListenerSpec.cs b/src/core/Akka.Tests/IO/TcpListenerSpec.cs index e5db0fe2674..02c40edbedb 100644 --- a/src/core/Akka.Tests/IO/TcpListenerSpec.cs +++ b/src/core/Akka.Tests/IO/TcpListenerSpec.cs @@ -19,12 +19,14 @@ namespace Akka.Tests.IO public class TcpListenerSpec : AkkaSpec { public TcpListenerSpec() - : base(@"akka.io.tcp.register-timeout = 500ms + : base(@" + akka.actor.serialize-creators = on + akka.actor.serialize-messages = on + akka.io.tcp.register-timeout = 500ms akka.io.tcp.max-received-message-size = 1024 akka.io.tcp.direct-buffer-size = 512 akka.actor.serialize-creators = on - akka.io.tcp.batch-accept-limit = 2 - ") + akka.io.tcp.batch-accept-limit = 2") { } [Fact] diff --git a/src/core/Akka.Tests/IO/UdpConnectedIntegrationSpec.cs b/src/core/Akka.Tests/IO/UdpConnectedIntegrationSpec.cs index e948193f219..3f79b8a8d9c 100644 --- a/src/core/Akka.Tests/IO/UdpConnectedIntegrationSpec.cs +++ b/src/core/Akka.Tests/IO/UdpConnectedIntegrationSpec.cs @@ -24,6 +24,8 @@ public class UdpConnectedIntegrationSpec : AkkaSpec public UdpConnectedIntegrationSpec(ITestOutputHelper output) : base(@" + akka.actor.serialize-creators = on + akka.actor.serialize-messages = on akka.io.udp-connected.nr-of-selectors = 1 akka.io.udp-connected.direct-buffer-pool-limit = 100 akka.io.udp-connected.direct-buffer-size = 1024 diff --git a/src/core/Akka.Tests/IO/UdpIntegrationSpec.cs b/src/core/Akka.Tests/IO/UdpIntegrationSpec.cs index a3af3b3d769..a4c69741bdb 100644 --- a/src/core/Akka.Tests/IO/UdpIntegrationSpec.cs +++ b/src/core/Akka.Tests/IO/UdpIntegrationSpec.cs @@ -27,6 +27,8 @@ public class UdpIntegrationSpec : AkkaSpec public UdpIntegrationSpec(ITestOutputHelper output) : base(@" + akka.actor.serialize-creators = on + akka.actor.serialize-messages = on akka.io.udp.max-channels = unlimited akka.io.udp.nr-of-selectors = 1 akka.io.udp.direct-buffer-pool-limit = 100 diff --git a/src/core/Akka.Tests/IO/UdpListenerSpec.cs b/src/core/Akka.Tests/IO/UdpListenerSpec.cs index 7895c6b4944..7021126af1d 100644 --- a/src/core/Akka.Tests/IO/UdpListenerSpec.cs +++ b/src/core/Akka.Tests/IO/UdpListenerSpec.cs @@ -13,17 +13,21 @@ using Akka.IO; using Akka.TestKit; using Xunit; +using Xunit.Abstractions; using UdpListener = Akka.IO.UdpListener; namespace Akka.Tests.IO { public class UdpListenerSpec : AkkaSpec { - public UdpListenerSpec() - : base(@"akka.io.udp.max-channels = unlimited + public UdpListenerSpec(ITestOutputHelper output) + : base(@" + akka.actor.serialize-creators = on + akka.actor.serialize-messages = on + akka.io.udp.max-channels = unlimited akka.io.udp.nr-of-selectors = 1 akka.io.udp.direct-buffer-pool-limit = 100 - akka.io.udp.direct-buffer-size = 1024") + akka.io.udp.direct-buffer-size = 1024", output) { } [Fact] diff --git a/src/core/Akka.Tests/Pattern/RetrySpec.cs b/src/core/Akka.Tests/Pattern/RetrySpec.cs new file mode 100644 index 00000000000..205ce368f16 --- /dev/null +++ b/src/core/Akka.Tests/Pattern/RetrySpec.cs @@ -0,0 +1,204 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2020 Lightbend Inc. +// Copyright (C) 2013-2020 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Threading.Tasks; +using Akka.TestKit; +using Xunit; +using static Akka.Pattern.RetrySupport; + +namespace Akka.Tests.Pattern +{ + public class RetrySpec : AkkaSpec + { + [Fact] + public Task Pattern_Retry_must_run_a_successful_task_immediately() + { + var retried = Retry(() => Task.FromResult(5), 5, TimeSpan.FromSeconds(1), Sys.Scheduler); + + return WithinAsync(TimeSpan.FromSeconds(3), async () => + { + var remaining = await retried; + Assert.Equal(5, remaining); + }); + } + + [Fact] + public Task Pattern_Retry_must_run_a_successful_task_only_once() + { + var counter = 0; + var retried = Retry(() => + { + counter++; + return Task.FromResult(counter); + }, 5, TimeSpan.FromSeconds(1), Sys.Scheduler); + + return WithinAsync(TimeSpan.FromSeconds(3), async () => + { + var remaining = await retried; + Assert.Equal(1, remaining); + }); + } + + [Fact] + public Task Pattern_Retry_must_eventually_return_a_failure_for_a_task_that_will_never_succeed() + { + var retried = Retry(() => Task.FromException(new InvalidOperationException("Mexico")), 5, TimeSpan.FromMilliseconds(100), Sys.Scheduler); + + return WithinAsync(TimeSpan.FromSeconds(3), async () => + { + var exception = await Assert.ThrowsAsync(() => retried); + Assert.Equal("Mexico", exception.Message); + }); + } + + [Fact] + public Task Pattern_Retry_must_return_a_success_for_a_task_that_succeeds_eventually() + { + var failCount = 0; + + Task Attempt() + { + if (failCount < 5) + { + failCount += 1; + return Task.FromException(new InvalidOperationException(failCount.ToString())); + } + else + { + return Task.FromResult(5); + } + } + + var retried = Retry(() => Attempt(), 10, TimeSpan.FromMilliseconds(100), Sys.Scheduler); + + return WithinAsync(TimeSpan.FromSeconds(3), async () => + { + var remaining = await retried; + Assert.Equal(5, remaining); + }); + } + + [Fact] + public Task Pattern_Retry_must_return_a_failure_for_a_task_that_would_have_succeeded_but_retries_were_exhausted() + { + var failCount = 0; + + Task Attempt() + { + if (failCount < 10) + { + failCount += 1; + return Task.FromException(new InvalidOperationException(failCount.ToString())); + } + else + { + return Task.FromResult(5); + } + } + + var retried = Retry(() => Attempt(), 5, TimeSpan.FromMilliseconds(100), Sys.Scheduler); + + return WithinAsync(TimeSpan.FromSeconds(3), async () => + { + var exception = await Assert.ThrowsAsync(() => retried); + Assert.Equal("6", exception.Message); + }); + } + + [Fact] + public Task Pattern_Retry_must_return_a_failure_for_a_task_that_would_have_succeeded_but_retries_were_exhausted_with_delay_function() + { + var failCount = 0; + var attemptedCount = 0; + + Task Attempt() + { + if (failCount < 10) + { + failCount += 1; + return Task.FromException(new InvalidOperationException(failCount.ToString())); + } + else + { + return Task.FromResult(5); + } + } + + var retried = Retry(() => Attempt(), 5, attempted => + { + attemptedCount = attempted; + return TimeSpan.FromMilliseconds(100 + attempted); + }, Sys.Scheduler); + + return WithinAsync(TimeSpan.FromSeconds(3), async () => + { + var exception = await Assert.ThrowsAsync(() => retried); + Assert.Equal("6", exception.Message); + Assert.Equal(5, attemptedCount); + }); + } + + [Fact] + public Task Pattern_Retry_can_be_attempted_without_any_delay() + { + var failCount = 0; + + Task Attempt() + { + if (failCount < 1000) + { + failCount += 1; + return Task.FromException(new InvalidOperationException(failCount.ToString())); + } + else + { + return Task.FromResult(1); + } + } + + var start = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + var retried = Retry(() => Attempt(), 999); + + return WithinAsync(TimeSpan.FromSeconds(1), async () => + { + var exception = await Assert.ThrowsAsync(() => retried); + Assert.Equal("1000", exception.Message); + + var elapse = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - start; + Assert.True(elapse <= 100); + }); + } + + [Fact] + public Task Pattern_Retry_must_handle_thrown_exceptions_in_same_way_as_failed_task() + { + var failCount = 0; + + Task Attempt() + { + if (failCount < 5) + { + failCount += 1; + return Task.FromException(new InvalidOperationException(failCount.ToString())); + } + else + { + return Task.FromResult(5); + } + } + + var retried = Retry(() => Attempt(), 10, TimeSpan.FromMilliseconds(100), Sys.Scheduler); + + return WithinAsync(TimeSpan.FromSeconds(3), async () => + { + var remaining = await retried; + Assert.Equal(5, remaining); + }); + } + } +} diff --git a/src/core/Akka.Tests/Serialization/NewtonSoftJsonSerializerSetupSpec.cs b/src/core/Akka.Tests/Serialization/NewtonSoftJsonSerializerSetupSpec.cs new file mode 100644 index 00000000000..e798cbefdfd --- /dev/null +++ b/src/core/Akka.Tests/Serialization/NewtonSoftJsonSerializerSetupSpec.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Akka.Actor; +using Akka.Actor.Setup; +using Akka.Configuration; +using Akka.Serialization; +using Akka.TestKit; +using Akka.TestKit.Configs; +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using Xunit; +using Xunit.Abstractions; + +namespace Akka.Tests.Serialization +{ + public class NewtonSoftJsonSerializerSetupSpec : AkkaSpec + { + internal class DummyContractResolver : DefaultContractResolver + { } + + public static NewtonSoftJsonSerializerSetup SerializationSettings = NewtonSoftJsonSerializerSetup.Create( + settings => + { + settings.ReferenceLoopHandling = ReferenceLoopHandling.Error; + settings.MissingMemberHandling = MissingMemberHandling.Error; + settings.NullValueHandling = NullValueHandling.Include; + settings.Converters = new List { new DummyConverter() }; + settings.ObjectCreationHandling = ObjectCreationHandling.Auto; + settings.ContractResolver = new DummyContractResolver(); + }); + + public static readonly BootstrapSetup Bootstrap = BootstrapSetup.Create().WithConfig(TestConfigs.DefaultConfig); + + public static readonly ActorSystemSetup ActorSystemSettings = ActorSystemSetup.Create(SerializationSettings, Bootstrap); + + public NewtonSoftJsonSerializerSetupSpec(ITestOutputHelper output) + : base(ActorSystem.Create("SerializationSettingsSpec", ActorSystemSettings), output) { } + + + [Fact] + public void Setup_should_be_used_inside_Json_serializer() + { + var serializer = (NewtonSoftJsonSerializer) Sys.Serialization.FindSerializerForType(typeof(object)); + var settings = serializer.Settings; + settings.ReferenceLoopHandling.Should().Be(ReferenceLoopHandling.Error); + settings.MissingMemberHandling.Should().Be(MissingMemberHandling.Error); + settings.NullValueHandling.Should().Be(NullValueHandling.Include); + settings.Converters.Any(c => c is DummyConverter).Should().Be(true); + } + + [Fact] + public void Setup_should_not_change_mandatory_settings() + { + var serializer = (NewtonSoftJsonSerializer) Sys.Serialization.FindSerializerForType(typeof(object)); + var settings = serializer.Settings; + settings.ContractResolver.Should().BeOfType(); + settings.ObjectCreationHandling.Should().Be(ObjectCreationHandling.Replace); + settings.Converters.Any(c => c is NewtonSoftJsonSerializer.SurrogateConverter).Should().Be(true); + settings.Converters.Any(c => c is DiscriminatedUnionConverter).Should().Be(true); + } + } +} diff --git a/src/core/Akka/Actor/ActorCell.Children.cs b/src/core/Akka/Actor/ActorCell.Children.cs index 0affbfe94f6..cc38364269d 100644 --- a/src/core/Akka/Actor/ActorCell.Children.cs +++ b/src/core/Akka/Actor/ActorCell.Children.cs @@ -10,8 +10,10 @@ using System.Collections.Immutable; using System.Text; using System.Runtime.CompilerServices; +using System.Runtime.Serialization; using System.Threading; using Akka.Actor.Internal; +using Akka.Dispatch.SysMsg; using Akka.Serialization; using Akka.Util; using Akka.Util.Internal; @@ -452,6 +454,7 @@ private IInternalActorRef MakeChild(Props props, string name, bool async, bool s if (_systemImpl.Settings.SerializeAllCreators && !systemService && !(props.Deploy.Scope is LocalScope)) { var oldInfo = Serialization.Serialization.CurrentTransportInformation; + object propArgument = null; try { if (oldInfo == null) @@ -465,17 +468,24 @@ private IInternalActorRef MakeChild(Props props, string name, bool async, bool s { if (argument != null && !(argument is INoSerializationVerificationNeeded)) { + propArgument = argument; var serializer = ser.FindSerializerFor(argument); var bytes = serializer.ToBinary(argument); var ms = Serialization.Serialization.ManifestFor(serializer, argument); - if(ser.Deserialize(bytes, serializer.Identifier, ms) == null) + if (ser.Deserialize(bytes, serializer.Identifier, ms) == null) throw new ArgumentException( - $"Pre-creation serialization check failed at [${_self.Path}/{name}]", - nameof(name)); + $"Pre-creation serialization check failed at [${_self.Path}/{name}]", + nameof(name)); } } } } + catch (Exception e) + { + throw new SerializationException( + $"Failed to serialize and deserialize actor props argument of type {propArgument?.GetType()} for actor type [{props.Type}].", + e); + } finally { Serialization.Serialization.CurrentTransportInformation = oldInfo; diff --git a/src/core/Akka/Actor/ActorCell.cs b/src/core/Akka/Actor/ActorCell.cs index 349ea7420c8..13b5a00e6b7 100644 --- a/src/core/Akka/Actor/ActorCell.cs +++ b/src/core/Akka/Actor/ActorCell.cs @@ -13,6 +13,7 @@ using Akka.Dispatch.SysMsg; using Akka.Event; using System.Reflection; +using System.Runtime.Serialization; using Akka.Serialization; using Akka.Util; using Assert = System.Diagnostics.Debug; @@ -518,7 +519,16 @@ private Envelope SerializeAndDeserialize(Envelope envelope) if (unwrapped is INoSerializationVerificationNeeded) return envelope; - var deserializedMsg = SerializeAndDeserializePayload(unwrapped); + object deserializedMsg; + try + { + deserializedMsg = SerializeAndDeserializePayload(unwrapped); + } + catch (Exception e) + { + throw new SerializationException($"Failed to serialize and deserialize payload object [{unwrapped.GetType()}]. Envelope: [{envelope}], Actor type: [{Actor.GetType()}]", e); + } + if (deadLetter != null) return new Envelope(new DeadLetter(deserializedMsg, deadLetter.Sender, deadLetter.Recipient), envelope.Sender); return new Envelope(deserializedMsg, envelope.Sender); diff --git a/src/core/Akka/Actor/ActorSelection.cs b/src/core/Akka/Actor/ActorSelection.cs index d9bcea3c6a7..218b3c2838f 100644 --- a/src/core/Akka/Actor/ActorSelection.cs +++ b/src/core/Akka/Actor/ActorSelection.cs @@ -63,10 +63,10 @@ public ActorSelection(IActorRef anchor, SelectionPathElement[] path) /// The anchor. /// The path. public ActorSelection(IActorRef anchor, string path) - : this(anchor, path == "" ? new string[] {} : path.Split('/')) + : this(anchor, path == "" ? new string[] { } : path.Split('/')) { } - + /// /// Initializes a new instance of the class. /// @@ -77,31 +77,34 @@ public ActorSelection(IActorRef anchor, IEnumerable elements) Anchor = anchor; var list = new List(); - var iter = elements.Iterator(); - while(!iter.IsEmpty()) + var count = elements.Count(); // shouldn't have a multiple enumeration issue\ + var i = 0; + foreach (var s in elements) { - var s = iter.Next(); - switch(s) + switch (s) { case null: case "": break; case "**": - if(!iter.IsEmpty()) + if (i < count-1) throw new IllegalActorNameException("Double wildcard can only appear at the last path entry"); - list.Add(new SelectChildRecursive()); + list.Add(SelectChildRecursive.Instance); break; case string e when e.Contains("?") || e.Contains("*"): list.Add(new SelectChildPattern(e)); break; case string e when e == "..": - list.Add(new SelectParent()); + list.Add(SelectParent.Instance); break; default: list.Add(new SelectChildName(s)); break; } + + i++; } + Path = list.ToArray(); } @@ -115,7 +118,7 @@ public void Tell(object message, IActorRef sender = null) if (sender == null && ActorCell.Current != null && ActorCell.Current.Self != null) sender = ActorCell.Current.Self; - DeliverSelection(Anchor as IInternalActorRef, sender, + DeliverSelection(Anchor as IInternalActorRef, sender, new ActorSelectionMessage(message, Path, wildCardFanOut: false)); } @@ -124,7 +127,7 @@ public void Tell(object message, IActorRef sender = null) /// The result is returned as a Task that is completed with the /// if such an actor exists. It is completed with failure if /// no such actor exists or the identification didn't complete within the supplied . - /// + /// /// Under the hood it talks to the actor to verify its existence and acquire its /// /// @@ -141,7 +144,7 @@ public void Tell(object message, IActorRef sender = null) /// The result is returned as a Task that is completed with the /// if such an actor exists. It is completed with failure if /// no such actor exists or the identification didn't complete within the supplied . - /// + /// /// Under the hood it talks to the actor to verify its existence and acquire its /// /// @@ -161,17 +164,17 @@ private async Task InnerResolveOne(TimeSpan timeout, CancellationToke try { var identity = await this.Ask(new Identify(null), timeout, ct).ConfigureAwait(false); - if(identity.Subject == null) + if (identity.Subject == null) throw new ActorNotFoundException("subject was null"); return identity.Subject; } - catch(Exception ex) + catch (Exception ex) { throw new ActorNotFoundException("Exception occurred while resolving ActorSelection", ex); } } - + /// /// INTERNAL API /// Convenience method used by remoting when receiving from a remote @@ -194,12 +197,14 @@ void Rec(IInternalActorRef actorRef) { if (actorRef is ActorRefWithCell refWithCell) { - var emptyRef = new EmptyLocalActorRef( - provider: refWithCell.Provider, - path: anchor.Path / sel.Elements.Select(el => el.ToString()), - eventStream: refWithCell.Underlying.System.EventStream); + EmptyLocalActorRef EmptyRef(){ + return new EmptyLocalActorRef( + provider: refWithCell.Provider, + path: anchor.Path / sel.Elements.Select(el => el.ToString()), + eventStream: refWithCell.Underlying.System.EventStream); + } - switch(iter.Next()) + switch (iter.Next()) { case SelectParent _: var parent = actorRef.Parent; @@ -217,7 +222,7 @@ void Rec(IInternalActorRef actorRef) { // don't send to emptyRef after wildcard fan-out if (!sel.WildCardFanOut) - emptyRef.Tell(sel, sender); + EmptyRef().Tell(sel, sender); } else if (iter.IsEmpty()) { @@ -234,7 +239,7 @@ void Rec(IInternalActorRef actorRef) if (allChildren.Count == 0) return; - var msg = new ActorSelectionMessage(sel.Message, new[] { new SelectChildRecursive() }, true); + var msg = new ActorSelectionMessage(sel.Message, new SelectionPathElement[] { SelectChildRecursive.Instance }, true); foreach (var c in allChildren) { c.Tell(sel.Message, sender); @@ -250,7 +255,7 @@ void Rec(IInternalActorRef actorRef) if (iter.IsEmpty()) { if (matchingChildren.Count == 0 && !sel.WildCardFanOut) - emptyRef.Tell(sel, sender); + EmptyRef().Tell(sel, sender); else { for (var i = 0; i < matchingChildren.Count; i++) @@ -259,9 +264,9 @@ void Rec(IInternalActorRef actorRef) } else { - // don't send to emptyRef after wildcard fan-out + // don't send to emptyRef after wildcard fan-out if (matchingChildren.Count == 0 && !sel.WildCardFanOut) - emptyRef.Tell(sel, sender); + EmptyRef().Tell(sel, sender); else { var message = new ActorSelectionMessage( @@ -269,7 +274,7 @@ void Rec(IInternalActorRef actorRef) elements: iter.ToVector().ToArray(), wildCardFanOut: sel.WildCardFanOut || matchingChildren.Count > 1); - for(var i = 0; i < matchingChildren.Count; i++) + for (var i = 0; i < matchingChildren.Count; i++) DeliverSelection(matchingChildren[i] as IInternalActorRef, sender, message); } } @@ -326,7 +331,7 @@ public override string ToString() /// /// Used to deliver messages via . /// - public class ActorSelectionMessage : IAutoReceivedMessage, IPossiblyHarmful + public class ActorSelectionMessage : IAutoReceivedMessage, IPossiblyHarmful, IWrappedMessage { /// /// Initializes a new instance of the class. @@ -380,7 +385,7 @@ public ActorSelectionMessage Copy(object message = null, SelectionPathElement[] /// /// Class SelectionPathElement. /// - public abstract class SelectionPathElement + public abstract class SelectionPathElement { } @@ -470,10 +475,15 @@ public override bool Equals(object obj) { if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; - if(!(obj is SelectChildRecursive)) return false; + if (!(obj is SelectChildRecursive)) return false; return true; } + /// + /// Use this instead of calling the default constructor + /// + public static readonly SelectChildRecursive Instance = new SelectChildRecursive(); + /// public override int GetHashCode() => "**".GetHashCode(); @@ -487,6 +497,11 @@ public override bool Equals(object obj) /// public class SelectParent : SelectionPathElement { + /// + /// Use this instead of calling the default constructor + /// + public static readonly SelectParent Instance = new SelectParent(); + /// public override bool Equals(object obj) => !ReferenceEquals(obj, null) && obj is SelectParent; diff --git a/src/core/Akka/Actor/ActorSystem.cs b/src/core/Akka/Actor/ActorSystem.cs index 69513bf41f0..4b7819d9055 100644 --- a/src/core/Akka/Actor/ActorSystem.cs +++ b/src/core/Akka/Actor/ActorSystem.cs @@ -93,7 +93,7 @@ internal static ProviderSelection GetProvider(string providerClass) } /// - /// Core boostrap settings for the , which can be created using one of the static factory methods + /// Core bootstrap settings for the , which can be created using one of the static factory methods /// on this class. /// public sealed class BootstrapSetup : Setup.Setup @@ -278,6 +278,10 @@ public static ActorSystem Create(string name) private static ActorSystem CreateAndStartSystem(string name, Config withFallback, ActorSystemSetup setup) { + // allows the ThreadPool to scale up / down dynamically + // by removing minimum thread count, which in our benchmarks + // appears to negatively impact performance + ThreadPool.SetMinThreads(0, 0); var system = new ActorSystemImpl(name, withFallback, setup, Option.None); system.Start(); return system; diff --git a/src/core/Akka/Actor/BuiltInActors.cs b/src/core/Akka/Actor/BuiltInActors.cs index ff3e11f7a0b..a2aa954b9c4 100644 --- a/src/core/Akka/Actor/BuiltInActors.cs +++ b/src/core/Akka/Actor/BuiltInActors.cs @@ -41,9 +41,9 @@ public class GuardianActor : ActorBase, IRequiresMessageQueueTBD protected override bool Receive(object message) { - if(message is Terminated) + if (message is Terminated) Context.Stop(Self); - else if(message is StopChild) + else if (message is StopChild) Context.Stop(((StopChild)message).Child); else Context.System.DeadLetters.Tell(new DeadLetter(message, Sender, Self), Sender); @@ -60,8 +60,8 @@ protected override void PreStart() } /// - /// System guardian. - /// + /// System guardian. + /// /// Root actor for all actors under the /system path. /// public class SystemGuardianActor : ActorBase, IRequiresMessageQueue @@ -87,16 +87,16 @@ public SystemGuardianActor(IActorRef userGuardian) protected override bool Receive(object message) { var terminated = message as Terminated; - if(terminated != null) + if (terminated != null) { var terminatedActor = terminated.ActorRef; - if(_userGuardian.Equals(terminatedActor)) + if (_userGuardian.Equals(terminatedActor)) { // time for the systemGuardian to stop, but first notify all the // termination hooks, they will reply with TerminationHookDone // and when all are done the systemGuardian is stopped Context.Become(Terminating); - foreach(var terminationHook in _terminationHooks) + foreach (var terminationHook in _terminationHooks) { terminationHook.Tell(TerminationHook.Instance); } @@ -110,17 +110,17 @@ protected override bool Receive(object message) } return true; } - + var stopChild = message as StopChild; - if(stopChild != null) + if (stopChild != null) { Context.Stop(stopChild.Child); return true; } var sender = Sender; - + var registerTerminationHook = message as RegisterTerminationHook; - if(registerTerminationHook != null && !ReferenceEquals(sender, Context.System.DeadLetters)) + if (registerTerminationHook != null && !ReferenceEquals(sender, Context.System.DeadLetters)) { _terminationHooks.Add(sender); Context.Watch(sender); @@ -133,7 +133,7 @@ protected override bool Receive(object message) private bool Terminating(object message) { var terminated = message as Terminated; - if(terminated != null) + if (terminated != null) { StopWhenAllTerminationHooksDone(terminated.ActorRef); return true; @@ -141,7 +141,7 @@ private bool Terminating(object message) var sender = Sender; var terminationHookDone = message as TerminationHookDone; - if(terminationHookDone != null) + if (terminationHookDone != null) { StopWhenAllTerminationHooksDone(sender); return true; @@ -158,7 +158,7 @@ private void StopWhenAllTerminationHooksDone(IActorRef terminatedActor) private void StopWhenAllTerminationHooksDone() { - if(_terminationHooks.Count == 0) + if (_terminationHooks.Count == 0) { var actorSystem = Context.System; actorSystem.EventStream.StopDefaultLoggers(actorSystem); @@ -178,6 +178,25 @@ protected override void PreRestart(Exception reason, object message) } } + /// + /// Message envelopes may implement this trait for better logging, such as logging of + /// message class name of the wrapped message instead of the envelope class name. + /// + public interface IWrappedMessage + { + object Message { get; } + } + + public static class WrappedMessage + { + public static object Unwrap(object message) + { + if (message is IWrappedMessage wm) + return Unwrap(wm.Message); + return message; + } + } + /// /// Class DeadLetterActorRef. /// diff --git a/src/core/Akka/Actor/EmptyLocalActorRef.cs b/src/core/Akka/Actor/EmptyLocalActorRef.cs index 52c1194ac65..2c37220d98a 100644 --- a/src/core/Akka/Actor/EmptyLocalActorRef.cs +++ b/src/core/Akka/Actor/EmptyLocalActorRef.cs @@ -59,8 +59,7 @@ public EmptyLocalActorRef(IActorRefProvider provider, ActorPath path, EventStrea protected override void TellInternal(object message, IActorRef sender) { if (message == null) throw new InvalidMessageException("Message is null"); - var d = message as DeadLetter; - if (d != null) SpecialHandle(d.Message, d.Sender); + if (message is DeadLetter d) SpecialHandle(d.Message, d.Sender); else if (!SpecialHandle(message, sender)) { _eventStream.Publish(new DeadLetter(message, sender.IsNobody() ? _provider.DeadLetters : sender, this)); @@ -85,8 +84,7 @@ public override void SendSystemMessage(ISystemMessage message) /// TBD protected virtual bool SpecialHandle(object message, IActorRef sender) { - var watch = message as Watch; - if (watch != null) + if (message is Watch watch) { if (watch.Watchee.Equals(this) && !watch.Watcher.Equals(this)) { @@ -97,37 +95,45 @@ protected virtual bool SpecialHandle(object message, IActorRef sender) if (message is Unwatch) return true; //Just ignore - var identify = message as Identify; - if (identify != null) + if (message is Identify identify) { sender.Tell(new ActorIdentity(identify.MessageId, null)); return true; } - var actorSelectionMessage = message as ActorSelectionMessage; - if (actorSelectionMessage != null) + if (message is ActorSelectionMessage actorSelectionMessage) { - var selectionIdentify = actorSelectionMessage.Message as Identify; - if (selectionIdentify != null) + if (actorSelectionMessage.Message is Identify selectionIdentify) { if (!actorSelectionMessage.WildCardFanOut) sender.Tell(new ActorIdentity(selectionIdentify.MessageId, null)); } else { - _eventStream.Publish(new DeadLetter(actorSelectionMessage.Message, sender.IsNobody() ? _provider.DeadLetters : sender, this)); + if (actorSelectionMessage.Message is IDeadLetterSuppression selectionDeadLetterSuppression) + { + PublishSupressedDeadLetter(selectionDeadLetterSuppression, sender); + } + else + { + _eventStream.Publish(new DeadLetter(actorSelectionMessage.Message, sender.IsNobody() ? _provider.DeadLetters : sender, this)); + } } return true; } - var deadLetterSuppression = message as IDeadLetterSuppression; - if (deadLetterSuppression != null) + if (message is IDeadLetterSuppression deadLetterSuppression) { - _eventStream.Publish(new SuppressedDeadLetter(deadLetterSuppression, sender.IsNobody() ? _provider.DeadLetters : sender, this)); + PublishSupressedDeadLetter(deadLetterSuppression, sender); return true; } return false; } + + private void PublishSupressedDeadLetter(IDeadLetterSuppression msg, IActorRef sender) + { + _eventStream.Publish(new SuppressedDeadLetter(msg, sender.IsNobody() ? _provider.DeadLetters : sender, this)); + } } } diff --git a/src/core/Akka/Actor/Props.cs b/src/core/Akka/Actor/Props.cs index df28f685fb6..61babb75c13 100644 --- a/src/core/Akka/Actor/Props.cs +++ b/src/core/Akka/Actor/Props.cs @@ -814,4 +814,4 @@ public interface IIndirectActorProducer /// The actor to release void Release(ActorBase actor); } -} \ No newline at end of file +} diff --git a/src/core/Akka/Akka.csproj b/src/core/Akka/Akka.csproj index d5af4e1eea7..28b2abd163d 100644 --- a/src/core/Akka/Akka.csproj +++ b/src/core/Akka/Akka.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/core/Akka/Annotations/Attributes.cs b/src/core/Akka/Annotations/Attributes.cs index caec1e69ef4..283dbb3a6de 100644 --- a/src/core/Akka/Annotations/Attributes.cs +++ b/src/core/Akka/Annotations/Attributes.cs @@ -42,7 +42,25 @@ public sealed class InternalApiAttribute : Attribute /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Module, Inherited = true, AllowMultiple = false)] public sealed class ApiMayChangeAttribute : Attribute + { + } + + /// + /// + /// Marks APIs that are designed under a closed-world assumption for and are NOT meant to be + /// extended by user-code. It is fine to extend these classes within Akka itself however. + /// + /// + /// This is most useful for binary compatibility purposes when a set of classes and interfaces + /// assume a "closed world" between them, and gain the ability to add methods to the interfaces + /// without breaking binary compatibility for users of this code. Specifically this assumption may be + /// understood intuitively: as all classes that implement this interface are in this compilation unit + /// artifact, it is impossible to obtain a "old" class with a "new" interface, as they are part of + /// the same dependency. + /// + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = true, AllowMultiple = false)] + public sealed class DoNotInheritAttribute: Attribute { - } } diff --git a/src/core/Akka/Configuration/Pigeon.conf b/src/core/Akka/Configuration/Pigeon.conf index 4b06d433871..a2d17b96a50 100644 --- a/src/core/Akka/Configuration/Pigeon.conf +++ b/src/core/Akka/Configuration/Pigeon.conf @@ -2,34 +2,34 @@ #################################### # Akka Actor Reference Config File # #################################### - + # This is the reference config file that contains all the default settings. # Make your edits/overrides in your application.conf. - + akka { # Akka version, checked against the runtime version of Akka. version = "0.0.1 Akka" - + # Home directory of Akka, modules in the deploy directory will be loaded home = "" - + # Loggers to register at boot time (akka.event.Logging$DefaultLogger logs # to STDOUT) loggers = ["Akka.Event.DefaultLogger"] # Specifies the default loggers dispatcher loggers-dispatcher = "akka.actor.default-dispatcher" - + # Loggers are created and registered synchronously during ActorSystem # start-up, and since they are actors, this timeout is used to bound the # waiting time logger-startup-timeout = 5s - + # You can enable asynchronous loggers creation by setting this to `true`. # This may be useful in cases when ActorSystem creation takes more time # then it should, or for whatever other reason logger-async-start = false - + # Log level used by the configured loggers (see "loggers") as soon # as they have been started; before that, see "stdout-loglevel" # Options: OFF, ERROR, WARNING, INFO, DEBUG @@ -38,22 +38,23 @@ akka { # Suppresses warning about usage of the default (JSON.NET) serializer # which is going to be obsoleted at v1.5 suppress-json-serializer-warning = on #disabled as of 1.3.2, given that the 1.5 timeframe is far out - + # Log level for the very basic logger activated during AkkaApplication startup # Options: OFF, ERROR, WARNING, INFO, DEBUG stdout-loglevel = "WARNING" - + # Log the complete configuration at INFO level when the actor system is started. # This is useful when you are uncertain of what configuration is used. log-config-on-start = off - - # Log at info level when messages are sent to dead letters. + + # Log at info level when messages are sent to dead letters, or published to + # eventStream as `DeadLetter`, `Dropped` or `UnhandledMessage`. # Possible values: # on: all dead letters are logged # off: no logging of dead letters # n: positive integer, number of dead letters that will be logged log-dead-letters = 10 - + # Possibility to turn off logging of dead letters while the actor system # is shutting down. Logging is only done when enabled by 'log-dead-letters' # setting. @@ -63,48 +64,48 @@ akka { # infinite: suspend the logging forever; # or a duration (eg: 5 minutes), after which the logging will be re-enabled. log-dead-letters-suspend-duration = 5 minutes - + # List FQCN of extensions which shall be loaded at actor system startup. # Should be on the format: 'extensions = ["foo", "bar"]' etc. # See the Akka Documentation for more info about Extensions extensions = [] - + # Toggles whether threads created by this ActorSystem should be daemons or not daemonic = off - + # THIS DOES NOT APPLY TO .NET # # JVM shutdown, System.exit(-1), in case of a fatal error, # such as OutOfMemoryError #jvm-exit-on-fatal-error = on - + actor { - + # FQCN of the ActorRefProvider to be used; the below is the built-in default, # another one is akka.remote.RemoteActorRefProvider in the akka-remote bundle. provider = "Akka.Actor.LocalActorRefProvider" - + # The guardian "/user" will use this class to obtain its supervisorStrategy. # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. # In addition to the default there is Akka.Actor.StoppingSupervisorStrategy. guardian-supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" - + # Timeout for ActorSystem.actorOf creation-timeout = 20s - + # Frequency with which stopping actors are prodded in case they had to be # removed from their parents reaper-interval = 5 - + # Serializes and deserializes (non-primitive) messages to ensure immutability, # this is only intended for testing. serialize-messages = off - + # Serializes and deserializes creators (in Props) to ensure that they can be # sent over the network, this is only intended for testing. Purely local deployments # as marked with deploy.scope == LocalScope are exempt from verification. serialize-creators = off - + # Timeout for send operations to top-level actors which are in the process # of being started. This is only relevant if using a bounded mailbox or the # CallingThreadDispatcher for a top-level actor. @@ -112,7 +113,7 @@ akka { # Default timeout for IActorRef.Ask. ask-timeout = infinite - + # THIS DOES NOT APPLY TO .NET # @@ -125,7 +126,7 @@ akka { inbox-size = 1000, default-timeout = 5s } - + # Mapping between ´deployment.router' short names to fully qualified class names router.type-mapping { from-code = "Akka.Routing.NoRouter" @@ -148,18 +149,18 @@ akka { cluster-metrics-adaptive-pool = "Akka.Cluster.Metrics.AdaptiveLoadBalancingPool, Akka.Cluster.Metrics" cluster-metrics-adaptive-group = "Akka.Cluster.Metrics.AdaptiveLoadBalancingGroup, Akka.Cluster.Metrics" } - + deployment { - + # deployment id pattern - on the format: /parent/child etc. default { - + # The id of the dispatcher to use for this actor. # If undefined or empty the dispatcher specified in code # (Props.withDispatcher) is used, or default-dispatcher if not # specified at all. dispatcher = "" - + # The id of the mailbox to use for this actor. # If undefined or empty the default mailbox of the configured dispatcher # is used or if there is no mailbox configuration the mailbox specified @@ -167,7 +168,7 @@ akka { # If there is a mailbox defined in the configured dispatcher then that # overrides this setting. mailbox = "" - + # routing (load-balance) scheme to use # - available: "from-code", "round-robin", "random", "smallest-mailbox", # "scatter-gather", "broadcast" @@ -188,46 +189,46 @@ akka { # - resizer: dynamically resizable number of routees as specified in # resizer below router = "from-code" - + # number of children to create in case of a router; # this setting is ignored if routees.paths is given nr-of-instances = 1 - + # within is the timeout used for routers containing future calls within = 5 s - + # number of virtual nodes per node for consistent-hashing router virtual-nodes-factor = 10 - + routees { # Alternatively to giving nr-of-instances you can specify the full # paths of those actors which should be routed to. This setting takes # precedence over nr-of-instances paths = [] } - + # To use a dedicated dispatcher for the routees of the pool you can - # define the dispatcher configuration inline with the property name + # define the dispatcher configuration inline with the property name # 'pool-dispatcher' in the deployment section of the router. # For example: # pool-dispatcher { # fork-join-executor.parallelism-min = 5 # fork-join-executor.parallelism-max = 5 # } - + # Routers with dynamically resizable number of routees; this feature is # enabled by including (parts of) this section in the deployment resizer { - + enabled = off - + # The fewest number of routees the router should ever have. lower-bound = 1 - + # The most number of routees the router should ever have. # Must be greater than or equal to lower-bound. upper-bound = 10 - + # Threshold used to evaluate if a routee is considered to be busy # (under pressure). Implementation depends on this value (default is 1). # 0: number of routees currently processing a message. @@ -237,12 +238,12 @@ akka { # messages in their mailbox. Note that estimating mailbox size of # default UnboundedMailbox is O(N) operation. pressure-threshold = 1 - + # Percentage to increase capacity whenever all routees are busy. # For example, 0.2 would increase 20% (rounded up), i.e. if current # capacity is 6 it will request an increase of 2 more routees. rampup-rate = 0.2 - + # Minimum fraction of busy routees before backing off. # For example, if this is 0.3, then we'll remove some routees only when # less than 30% of routees are busy, i.e. if current capacity is 10 and @@ -250,13 +251,13 @@ akka { # the capacity is decreased. # Use 0.0 or negative to avoid removal of routees. backoff-threshold = 0.3 - + # Fraction of routees to be removed when the resizer reaches the # backoffThreshold. # For example, 0.1 would decrease 10% (rounded up), i.e. if current # capacity is 9 it will request an decrease of 1 routee. backoff-rate = 0.1 - + # Number of messages between resize operation. # Use 1 to resize before each message. messages-per-resize = 10 @@ -287,7 +288,7 @@ akka { threadtype = background #values can be "background" or "foreground" } } - + default-dispatcher { # Must be one of the following # Dispatcher, PinnedDispatcher, or a FQCN to a class inheriting @@ -337,22 +338,22 @@ akka { # For running in current synchronization contexts current-context-executor{} - + # How long time the dispatcher will wait for new actors until it shuts down shutdown-timeout = 1s - + # Throughput defines the number of messages that are processed in a batch # before the thread is returned to the pool. Set to 1 for as fair as possible. throughput = 30 - + # Throughput deadline for Dispatcher, set to 0 or negative for no deadline throughput-deadline-time = 0ms - + # For BalancingDispatcher: If the balancing dispatcher should attempt to # schedule idle actors using the same dispatcher when a message comes in, # and the dispatchers ExecutorService is not fully busy already. attempt-teamwork = on - + # If this dispatcher requires a specific type of mailbox, specify the # fully-qualified class name here; the actually created mailbox will # be a subtype of this type. The empty string signifies no requirement. @@ -373,7 +374,7 @@ akka { parallelism-max = 64 } } - + default-blocking-io-dispatcher { type = "Dispatcher" executor = "thread-pool-executor" @@ -392,26 +393,26 @@ akka { # constructor with # (akka.actor.ActorSystem.Settings, com.typesafe.config.Config) parameters. mailbox-type = "Akka.Dispatch.UnboundedMailbox" - + # If the mailbox is bounded then it uses this setting to determine its # capacity. The provided value must be positive. # NOTICE: # Up to version 2.1 the mailbox type was determined based on this setting; # this is no longer the case, the type must explicitly be a bounded mailbox. mailbox-capacity = 1000 - + # If the mailbox is bounded then this is the timeout for enqueueing # in case the mailbox is full. Negative values signify infinite # timeout, which should be avoided as it bears the risk of dead-lock. mailbox-push-timeout-time = 10s - + # For Actor with Stash: The default capacity of the stash. # If negative (or zero) then an unbounded stash is used (default) # If positive then a bounded stash is used and the capacity is set using # the property stash-capacity = -1 } - + mailbox { # Mapping between message queue semantics and mailbox configurations. # Used by akka.dispatch.RequiresMessageQueue[T] to enforce different @@ -428,28 +429,28 @@ akka { "Akka.Dispatch.IMultipleConsumerSemantics" = akka.actor.mailbox.unbounded-queue-based "Akka.Event.ILoggerMessageQueueSemantics" = akka.actor.mailbox.logger-queue } - + unbounded-queue-based { # FQCN of the MailboxType, The Class of the FQCN must have a public # constructor with (akka.actor.ActorSystem.Settings, # com.typesafe.config.Config) parameters. mailbox-type = "Akka.Dispatch.UnboundedMailbox" } - + bounded-queue-based { # FQCN of the MailboxType, The Class of the FQCN must have a public # constructor with (akka.actor.ActorSystem.Settings, # com.typesafe.config.Config) parameters. mailbox-type = "Akka.Dispatch.BoundedMailbox" } - + unbounded-deque-based { # FQCN of the MailboxType, The Class of the FQCN must have a public # constructor with (akka.actor.ActorSystem.Settings, # com.typesafe.config.Config) parameters. mailbox-type = "Akka.Dispatch.UnboundedDequeBasedMailbox" } - + bounded-deque-based { # FQCN of the MailboxType, The Class of the FQCN must have a public # constructor with (akka.actor.ActorSystem.Settings, @@ -464,39 +465,39 @@ akka { mailbox-type = "Akka.Event.LoggerMailboxType" } } - + debug { # enable function of Actor.loggable(), which is to log any received message # at DEBUG level, see the “Testing Actor Systems” section of the Akka # Documentation at http://akka.io/docs receive = off - + # enable DEBUG logging of all AutoReceiveMessages (Kill, PoisonPill et.c.) autoreceive = off - + # enable DEBUG logging of actor lifecycle changes lifecycle = off - + # enable DEBUG logging of all LoggingFSMs for events, transitions and timers fsm = off - + # enable DEBUG logging of subscription changes on the eventStream event-stream = off - + # enable DEBUG logging of unhandled messages unhandled = off - + # enable WARN logging of misconfigured routers router-misconfiguration = off } - + # Entries for pluggable serializers and their bindings. serializers { json = "Akka.Serialization.NewtonSoftJsonSerializer, Akka" bytes = "Akka.Serialization.ByteArraySerializer, Akka" } - + # Class to Serializer binding. You only need to specify the name of an # interface or abstract base class of the messages. In case of ambiguity it # is using the most specific configured class, or giving a warning and @@ -519,13 +520,13 @@ akka { "Akka.Serialization.ByteArraySerializer, Akka" = 4 "Akka.Serialization.NewtonSoftJsonSerializer, Akka" = 1 } - + # extra settings that can be custom to a serializer implementation serialization-settings { - + } } - + # Used to set the behavior of the scheduler. # Changing the default values may change the system behavior drastically so make # sure you know what you're doing! See the Scheduler section of the Akka @@ -541,14 +542,14 @@ akka { # tick-duration to a high value will make shutting down the actor system # take longer. tick-duration = 10ms - + # The timer uses a circular wheel of buckets to store the timer tasks. # This should be set such that the majority of scheduled timeouts (for high # scheduling frequency) will be shorter than one rotation of the wheel # (ticks-per-wheel * ticks-duration) # THIS MUST BE A POWER OF TWO! ticks-per-wheel = 512 - + # This setting selects the timer implementation which shall be loaded at # system start-up. # The class given here must implement the akka.actor.Scheduler interface @@ -557,14 +558,14 @@ akka { # 2) akka.event.LoggingAdapter # 3) java.util.concurrent.ThreadFactory implementation = "Akka.Actor.HashedWheelTimerScheduler" - + # When shutting down the scheduler, there will typically be a thread which # needs to be stopped, and this timeout determines how long to wait for # that to happen. In case of timeout the shutdown of the actor system will # proceed without running possibly still enqueued tasks. shutdown-timeout = 5s - } - + } + io { # By default the select loops run on dedicated threads, hence using a @@ -577,8 +578,8 @@ akka { tcp { # Implementation of `Akka.IO.Buffers.IBufferPool` interface. It - # allocates memory is so called segments. Each segment is then cut into - # buffers of equal size (see: `buffers-per-segment`). Those buffers are + # allocates memory is so called segments. Each segment is then cut into + # buffers of equal size (see: `buffers-per-segment`). Those buffers are # then lend to the requestor. They have to be released later on. direct-buffer-pool { @@ -590,8 +591,8 @@ akka { buffer-size = 512 # Number of byte buffers per segment. Every segement is a single continuous - # byte array in memory. Once buffer pool will run out of byte buffers to - # lend it will allocate a next segment of memory. + # byte array in memory. Once buffer pool will run out of byte buffers to + # lend it will allocate a next segment of memory. # Each segments size is equal to `buffer-size` * `buffers-per-segment`. buffers-per-segment = 500 @@ -604,9 +605,9 @@ akka { buffer-pool-limit = 1024 } - # Default implementation of `Akka.IO.Buffers.IBufferPool` interface. + # Default implementation of `Akka.IO.Buffers.IBufferPool` interface. # Instead of maintaining allocated buffers and reusing them - # between different SocketAsyncEventArgs instances, it allocates new + # between different SocketAsyncEventArgs instances, it allocates new # buffer each time when new socket connection is established. disabled-buffer-pool { @@ -619,7 +620,7 @@ akka { } # A buffer pool used to acquire and release byte buffers from the managed - # heap. Once byte buffer is no longer needed is can be released, landing + # heap. Once byte buffer is no longer needed is can be released, landing # on the pool again, to be reused later. This way we can reduce a GC pressure # by reusing the same components instead of recycling them. # NOTE: pooling is disabled by default, to enable use direct-buffer-pool: @@ -643,7 +644,7 @@ akka { # higher numbers decrease latency, lower numbers increase fairness on # the worker-dispatcher batch-accept-limit = 10 - + # The duration a connection actor waits for a `Register` message from # its commander before aborting the connection. register-timeout = 5s @@ -651,7 +652,7 @@ akka { # The maximum number of bytes delivered by a `Received` message. Before # more data is read from the network the connection actor will try to # do other work. - # The purpose of this setting is to impose a smaller limit than the + # The purpose of this setting is to impose a smaller limit than the # configured receive buffer size. When using value 'unlimited' it will # try to read all from the receive buffer. max-received-message-size = unlimited @@ -707,10 +708,10 @@ akka { } udp { - + # Default implementation of `Akka.IO.Buffers.IBufferPool` interface. It - # allocates memory is so called segments. Each segment is then cut into - # buffers of equal size (see: `buffers-per-segment`). Those buffers are + # allocates memory is so called segments. Each segment is then cut into + # buffers of equal size (see: `buffers-per-segment`). Those buffers are # then lend to the requestor. They have to be released later on. direct-buffer-pool { @@ -722,8 +723,8 @@ akka { buffer-size = 512 # Number of byte buffers per segment. Every segement is a single continuous - # byte array in memory. Once buffer pool will run out of byte buffers to - # lend it will allocate a next segment of memory. + # byte array in memory. Once buffer pool will run out of byte buffers to + # lend it will allocate a next segment of memory. # Each segments size is equal to `buffer-size` * `buffers-per-segment`. buffers-per-segment = 500 @@ -737,7 +738,7 @@ akka { } # A buffer pool used to acquire and release byte buffers from the managed - # heap. Once byte buffer is no longer needed is can be released, landing + # heap. Once byte buffer is no longer needed is can be released, landing # on the pool again, to be reused later. This way we can reduce a GC pressure # by reusing the same components instead of recycling them. buffer-pool = "akka.io.udp.direct-buffer-pool" @@ -745,7 +746,7 @@ akka { # The number of selectors to stripe the served channels over; each of # these will use one select loop on the selector-dispatcher. nr-of-socket-async-event-args = 32 - + # Maximum number of open channels supported by this UDP module Generally # UDP does not require a large number of channels, therefore it is # recommended to keep this setting low. @@ -804,8 +805,8 @@ akka { udp-connected { # Default implementation of `Akka.IO.Buffers.IBufferPool` interface. It - # allocates memory is so called segments. Each segment is then cut into - # buffers of equal size (see: `buffers-per-segment`). Those buffers are + # allocates memory is so called segments. Each segment is then cut into + # buffers of equal size (see: `buffers-per-segment`). Those buffers are # then lend to the requestor. They have to be released later on. direct-buffer-pool { @@ -817,8 +818,8 @@ akka { buffer-size = 512 # Number of byte buffers per segment. Every segement is a single continuous - # byte array in memory. Once buffer pool will run out of byte buffers to - # lend it will allocate a next segment of memory. + # byte array in memory. Once buffer pool will run out of byte buffers to + # lend it will allocate a next segment of memory. # Each segments size is equal to `buffer-size` * `buffers-per-segment`. buffers-per-segment = 500 @@ -832,7 +833,7 @@ akka { } # A buffer pool used to acquire and release byte buffers from the managed - # heap. Once byte buffer is no longer needed is can be released, landing + # heap. Once byte buffer is no longer needed is can be released, landing # on the pool again, to be reused later. This way we can reduce a GC pressure # by reusing the same components instead of recycling them. buffer-pool = "akka.io.udp-connected.direct-buffer-pool" @@ -930,22 +931,22 @@ akka { # - JVM shutdown hook will by default run CoordinatedShutdown # - Cluster node will automatically run CoordinatedShutdown when it # sees itself as Exiting - # - A management console or other application specific command can + # - A management console or other application specific command can # run CoordinatedShutdown coordinated-shutdown { - # The timeout that will be used for a phase if not specified with + # The timeout that will be used for a phase if not specified with # 'timeout' in the phase default-phase-timeout = 5 s - + # Terminate the ActorSystem in the last phase actor-system-terminate. terminate-actor-system = on - + # Exit the CLR(Environment.Exit(0)) in the last phase actor-system-terminate - # if this is set to 'on'. It is done after termination of the - # ActorSystem if terminate-actor-system=on, otherwise it is done - # immediately when the last phase is reached. + # if this is set to 'on'. It is done after termination of the + # ActorSystem if terminate-actor-system=on, otherwise it is done + # immediately when the last phase is reached. exit-clr = off - + # Run the coordinated shutdown when the CLR process exits, e.g. # via kill SIGTERM signal (SIGINT ctrl-c doesn't work). run-by-clr-shutdown-hook = on @@ -954,11 +955,11 @@ akka { # Enabling this and disabling terminate-actor-system is not a supported # combination (will throw ConfigurationException at startup). run-by-actor-system-terminate = on - + #//#coordinated-shutdown-phases # CoordinatedShutdown will run the tasks that are added to these - # phases. The phases can be ordered as a DAG by defining the - # dependencies between the phases. + # phases. The phases can be ordered as a DAG by defining the + # dependencies between the phases. # Each phase is defined as a named config section with the # following optional properties: # - timeout=15s: Override the default-phase-timeout for this phase. @@ -969,68 +970,68 @@ akka { # The first pre-defined phase that applications can add tasks to. # Note that more phases can be be added in the application's - # configuration by overriding this phase with an additional + # configuration by overriding this phase with an additional # depends-on. before-service-unbind { } - + # Stop accepting new incoming requests in for example HTTP. service-unbind { depends-on = [before-service-unbind] } - + # Wait for requests that are in progress to be completed. service-requests-done { depends-on = [service-unbind] } - + # Final shutdown of service endpoints. service-stop { depends-on = [service-requests-done] } - + # Phase for custom application tasks that are to be run # after service shutdown and before cluster shutdown. before-cluster-shutdown { depends-on = [service-stop] } - + # Graceful shutdown of the Cluster Sharding regions. cluster-sharding-shutdown-region { timeout = 10 s depends-on = [before-cluster-shutdown] } - + # Emit the leave command for the node that is shutting down. cluster-leave { depends-on = [cluster-sharding-shutdown-region] } - + # Shutdown cluster singletons cluster-exiting { timeout = 10 s depends-on = [cluster-leave] } - + # Wait until exiting has been completed cluster-exiting-done { depends-on = [cluster-exiting] } - + # Shutdown the cluster extension cluster-shutdown { depends-on = [cluster-exiting-done] } - + # Phase for custom application tasks that are to be run # after cluster shutdown and before ActorSystem termination. before-actor-system-terminate { depends-on = [cluster-shutdown] } - + # Last phase. See terminate-actor-system and exit-jvm above. - # Don't add phases that depends on this phase because the - # dispatcher and scheduler of the ActorSystem have been shutdown. + # Don't add phases that depends on this phase because the + # dispatcher and scheduler of the ActorSystem have been shutdown. actor-system-terminate { timeout = 10 s depends-on = [before-actor-system-terminate] diff --git a/src/core/Akka/Dispatch/AbstractDispatcher.cs b/src/core/Akka/Dispatch/AbstractDispatcher.cs index defa523f64a..b18b1f17e05 100644 --- a/src/core/Akka/Dispatch/AbstractDispatcher.cs +++ b/src/core/Akka/Dispatch/AbstractDispatcher.cs @@ -116,6 +116,26 @@ protected ExecutorServiceConfigurator(Config config, IDispatcherPrerequisites pr public IDispatcherPrerequisites Prerequisites { get; private set; } } + internal sealed class ChannelExecutorConfigurator : ExecutorServiceConfigurator + { + public ChannelExecutorConfigurator(Config config, IDispatcherPrerequisites prerequisites) : base(config, prerequisites) + { + var fje = config.GetConfig("fork-join-executor"); + MaxParallelism = ThreadPoolConfig.ScaledPoolSize( + fje.GetInt("parallelism-min"), + fje.GetDouble("parallelism-factor", 1.0D), // the scalar-based factor to scale the threadpool size to + fje.GetInt("parallelism-max")); + } + + public int MaxParallelism {get;} + + public override ExecutorService Produce(string id) + { + Prerequisites.EventStream.Publish(new Debug($"ChannelExecutor-[id]", typeof(FixedConcurrencyTaskScheduler), $"Launched Dispatcher [{id}] with MaxParallelism=[{MaxParallelism}]")); + return new TaskSchedulerExecutor(id, new FixedConcurrencyTaskScheduler(MaxParallelism)); + } + } + /// /// INTERNAL API /// @@ -306,6 +326,8 @@ protected ExecutorServiceConfigurator ConfigureExecutor() return new CurrentSynchronizationContextExecutorServiceFactory(Config, Prerequisites); case "task-executor": return new DefaultTaskSchedulerExecutorConfigurator(Config, Prerequisites); + case "channel-executor": + return new ChannelExecutorConfigurator(Config, Prerequisites); default: Type executorConfiguratorType = Type.GetType(executor); if (executorConfiguratorType == null) diff --git a/src/core/Akka/Dispatch/Dispatchers.cs b/src/core/Akka/Dispatch/Dispatchers.cs index 5043cf40547..2f0231319d1 100644 --- a/src/core/Akka/Dispatch/Dispatchers.cs +++ b/src/core/Akka/Dispatch/Dispatchers.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Akka.Actor; @@ -91,6 +92,86 @@ public PartialTrustThreadPoolExecutorService(string id) : base(id) } } + /// + /// INTERNAL API + /// + /// Used to power + /// + internal sealed class FixedConcurrencyTaskScheduler : TaskScheduler + { + + [ThreadStatic] + private static bool _threadRunning = false; + private ConcurrentQueue _tasks = new ConcurrentQueue(); + + private int _readers = 0; + + public FixedConcurrencyTaskScheduler(int degreeOfParallelism) + { + MaximumConcurrencyLevel = degreeOfParallelism; + } + + + public override int MaximumConcurrencyLevel { get; } + + /// + /// ONLY USED IN DEBUGGER - NO PERF IMPACT. + /// + protected override IEnumerable GetScheduledTasks() + { + return _tasks; + } + + protected override bool TryDequeue(Task task) + { + return false; + } + + protected override void QueueTask(Task task) + { + _tasks.Enqueue(task); + if (_readers < MaximumConcurrencyLevel) + { + var initial = _readers; + var newVale = _readers + 1; + if (initial == Interlocked.CompareExchange(ref _readers, newVale, initial)) + { + // try to start a new worker + ThreadPool.UnsafeQueueUserWorkItem(_ => ReadChannel(), null); + } + } + } + + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + // If this thread isn't already processing a task, we don't support inlining + if (!_threadRunning) return false; + return TryExecuteTask(task); + } + + public void ReadChannel() + { + _threadRunning = true; + try + { + while (_tasks.TryDequeue(out var runnable)) + { + base.TryExecuteTask(runnable); + } + } + catch + { + // suppress exceptions + } + finally + { + Interlocked.Decrement(ref _readers); + + _threadRunning = false; + } + } + } + /// /// INTERNAL API @@ -273,7 +354,7 @@ public MessageDispatcher DefaultGlobalDispatcher internal MessageDispatcher InternalDispatcher { get; } /// - /// The for the default dispatcher. + /// The for the default dispatcher. /// public Config DefaultDispatcherConfig { @@ -336,7 +417,7 @@ public bool HasDispatcher(string id) private MessageDispatcherConfigurator LookupConfigurator(string id) { var depth = 0; - while(depth < MaxDispatcherAliasDepth) + while (depth < MaxDispatcherAliasDepth) { if (_dispatcherConfigurators.TryGetValue(id, out var configurator)) return configurator; @@ -374,7 +455,7 @@ private MessageDispatcherConfigurator LookupConfigurator(string id) /// /// INTERNAL API /// - /// Creates a dispatcher from a . Internal test purpose only. + /// Creates a dispatcher from a . Internal test purpose only. /// /// From(Config.GetConfig(id)); /// diff --git a/src/core/Akka/Dispatch/Mailbox.cs b/src/core/Akka/Dispatch/Mailbox.cs index 22833f51d7a..e253187c76c 100644 --- a/src/core/Akka/Dispatch/Mailbox.cs +++ b/src/core/Akka/Dispatch/Mailbox.cs @@ -382,7 +382,7 @@ private void ProcessMailbox(int left, long deadlineTicks) // not going to bother catching ThreadAbortExceptions here, since they'll get rethrown anyway Actor.Invoke(next); ProcessAllSystemMessages(); - if (left > 1 && (Dispatcher.ThroughputDeadlineTime.HasValue == false || (MonotonicClock.GetTicks() - deadlineTicks) < 0)) + if (left > 0 && (Dispatcher.ThroughputDeadlineTime.HasValue == false || (MonotonicClock.GetTicks() - deadlineTicks) < 0)) { left = left - 1; continue; diff --git a/src/core/Akka/Event/DeadLetter.cs b/src/core/Akka/Event/DeadLetter.cs index f11e4bea15f..6ba4cf377a4 100644 --- a/src/core/Akka/Event/DeadLetter.cs +++ b/src/core/Akka/Event/DeadLetter.cs @@ -20,10 +20,13 @@ public interface IDeadLetterSuppression } /// - /// Represents a message that could not be delivered to it's recipient. + /// Represents a message that could not be delivered to it's recipient. /// This message wraps the original message, the sender and the intended recipient of the message. + /// + /// Subscribe to this class to be notified about all (also the suppressed ones) + /// and . /// - public abstract class AllDeadLetters + public abstract class AllDeadLetters : IWrappedMessage { /// /// Initializes a new instance of the class. @@ -106,4 +109,26 @@ public SuppressedDeadLetter(IDeadLetterSuppression message, IActorRef sender, IA if (recipient == null) throw new ArgumentNullException(nameof(recipient), "SuppressedDeadLetter recipient may not be null"); } } + + /// + /// Envelope that is published on the eventStream wrapped in for every message that is + /// dropped due to overfull queues or routers with no routees. + /// + /// When this message was sent without a sender , `sender` will be , i.e. `null`. + /// + public sealed class Dropped : AllDeadLetters + { + public Dropped(object message, string reason, IActorRef sender, IActorRef recipient) + : base(message, sender, recipient) + { + Reason = reason; + } + + public Dropped(object message, string reason, IActorRef recipient) + : this(message, reason, ActorRefs.NoSender, recipient) + { + } + + public string Reason { get; } + } } diff --git a/src/core/Akka/Event/DeadLetterListener.cs b/src/core/Akka/Event/DeadLetterListener.cs index 33843f87f8e..bcc83eced0e 100644 --- a/src/core/Akka/Event/DeadLetterListener.cs +++ b/src/core/Akka/Event/DeadLetterListener.cs @@ -41,6 +41,8 @@ protected override void PreRestart(Exception reason, object message) protected override void PreStart() { _eventStream.Subscribe(Self, typeof(DeadLetter)); + _eventStream.Subscribe(Self, typeof(Dropped)); + _eventStream.Subscribe(Self, typeof(UnhandledMessage)); } /// @@ -85,10 +87,13 @@ private Receive ReceiveWithAlwaysLogging() { return message => { - if (message is DeadLetter deadLetter) + if (message is AllDeadLetters d) { - IncrementCount(); - LogDeadLetter(deadLetter.Message, deadLetter.Sender, deadLetter.Recipient, ""); + if (!IsWrappedSuppressed(d)) + { + IncrementCount(); + LogDeadLetter(d, ""); + } return true; } return false; @@ -99,17 +104,20 @@ private Receive ReceiveWithMaxCountLogging() { return message => { - if (message is DeadLetter deadLetter) + if (message is AllDeadLetters d) { - IncrementCount(); - if (_count == _maxCount) - { - LogDeadLetter(deadLetter.Message, deadLetter.Sender, deadLetter.Recipient, ", no more dead letters will be logged"); - Context.Stop(Self); - } - else + if (!IsWrappedSuppressed(d)) { - LogDeadLetter(deadLetter.Message, deadLetter.Sender, deadLetter.Recipient, ""); + IncrementCount(); + if (_count == _maxCount) + { + LogDeadLetter(d, ", no more dead letters will be logged"); + Context.Stop(Self); + } + else + { + LogDeadLetter(d, ""); + } } return true; } @@ -121,18 +129,21 @@ private Receive ReceiveWithSuspendLogging(TimeSpan suspendDuration) { return message => { - if (message is DeadLetter deadLetter) + if (message is AllDeadLetters d) { - IncrementCount(); - if (_count == _maxCount) + if (!IsWrappedSuppressed(d)) { - var doneMsg = $", no more dead letters will be logged in next [{suspendDuration}]"; - LogDeadLetter(deadLetter.Message, deadLetter.Sender, deadLetter.Recipient, doneMsg); - Context.Become(ReceiveWhenSuspended(suspendDuration, Deadline.Now + suspendDuration)); - } - else - { - LogDeadLetter(deadLetter.Message, deadLetter.Sender, deadLetter.Recipient, ""); + IncrementCount(); + if (_count == _maxCount) + { + var doneMsg = $", no more dead letters will be logged in next [{suspendDuration}]"; + LogDeadLetter(d, doneMsg); + Context.Become(ReceiveWhenSuspended(suspendDuration, Deadline.Now + suspendDuration)); + } + else + { + LogDeadLetter(d, ""); + } } return true; } @@ -144,15 +155,18 @@ private Receive ReceiveWhenSuspended(TimeSpan suspendDuration, Deadline suspendD { return message => { - if (message is DeadLetter deadLetter) + if (message is AllDeadLetters d) { - IncrementCount(); - if (suspendDeadline.IsOverdue) + if (!IsWrappedSuppressed(d)) { - var doneMsg = $", of which {(_count - _maxCount - 1).ToString()} were not logged. The counter will be reset now"; - LogDeadLetter(deadLetter.Message, deadLetter.Sender, deadLetter.Recipient, doneMsg); - _count = 0; - Context.Become(ReceiveWithSuspendLogging(suspendDuration)); + IncrementCount(); + if (suspendDeadline.IsOverdue) + { + var doneMsg = $", of which {(_count - _maxCount - 1)} were not logged. The counter will be reset now"; + LogDeadLetter(d, doneMsg); + _count = 0; + Context.Become(ReceiveWithSuspendLogging(suspendDuration)); + } } return true; } @@ -160,19 +174,50 @@ private Receive ReceiveWhenSuspended(TimeSpan suspendDuration, Deadline suspendD }; } - private void LogDeadLetter(object message, IActorRef snd, IActorRef recipient, string doneMsg) + private void LogDeadLetter(AllDeadLetters d, string doneMsg) { - var messageType = ReferenceEquals(null, message) ? "null" : message.GetType().Name; - var origin = ReferenceEquals(snd, Context.System.DeadLetters) ? "without sender" : $"from {snd.Path}"; + var origin = IsReal(d.Sender) ? $" from {d.Sender}" : ""; + var unwrapped = WrappedMessage.Unwrap(d.Message); + var messageStr = unwrapped?.GetType().Name ?? "null"; + var wrappedIn = (d.Message is IWrappedMessage) ? $" wrapped in [${d.Message.GetType().Name}]" : ""; + + string logMessage; + switch (d) + { + case Dropped dropped: + var destination = IsReal(d.Recipient) ? $" to {d.Recipient}" : ""; + logMessage = $"Message [{messageStr}]{wrappedIn}{origin}{destination} was dropped. {dropped.Reason}. " + + $"[{_count}] dead letters encountered{doneMsg}. "; + break; + case UnhandledMessage unhandled: + destination = IsReal(d.Recipient) ? $" to {d.Recipient}" : ""; + logMessage = $"Message [{messageStr}]{wrappedIn}{origin}{destination} was unhandled. " + + $"[{_count}] dead letters encountered{doneMsg}. "; + break; + default: + logMessage = $"Message [{messageStr}]{wrappedIn}{origin} to {d.Recipient} was not delivered. " + + $"[{_count}] dead letters encountered{doneMsg}. " + + $"If this is not an expected behavior then {d.Recipient} may have terminated unexpectedly. "; + break; + } _eventStream.Publish(new Info( - recipient.Path.ToString(), - recipient.GetType(), - $"Message [{messageType}] {origin} to {recipient.Path} was not delivered. [{_count.ToString()}] dead letters encountered{doneMsg}. " + - $"If this is not an expected behavior then {recipient.Path} may have terminated unexpectedly. " + + d.Recipient.Path.ToString(), + d.Recipient.GetType(), + logMessage + "This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' " + "and 'akka.log-dead-letters-during-shutdown'.")); } + private bool IsReal(IActorRef snd) + { + return !ReferenceEquals(snd, ActorRefs.NoSender) && !ReferenceEquals(snd, Context.System.DeadLetters) && !(snd is DeadLetterActorRef); + } + + private bool IsWrappedSuppressed(AllDeadLetters d) + { + return d is IWrappedMessage w && w.Message is IDeadLetterSuppression; + } + /// /// This class represents the latest date or time by which an operation should be completed. /// @@ -210,9 +255,9 @@ private void LogDeadLetter(object message, IActorRef snd, IActorRef recipient, s public TimeSpan TimeLeft { get { return When - DateTime.UtcNow; } } #region Overrides - + /// - public override bool Equals(object obj) => + public override bool Equals(object obj) => obj is Deadline deadline && Equals(deadline); /// diff --git a/src/core/Akka/Event/Logging.cs b/src/core/Akka/Event/Logging.cs index 9900557d1b7..05d334b8eac 100644 --- a/src/core/Akka/Event/Logging.cs +++ b/src/core/Akka/Event/Logging.cs @@ -270,7 +270,7 @@ public static LogLevel LogLevelFor(string logLevel) { if (!string.IsNullOrEmpty(logLevel)) { - logLevel = logLevel.ToUpper(); + logLevel = logLevel.ToUpperInvariant(); } switch (logLevel) diff --git a/src/core/Akka/Event/UnhandledMessage.cs b/src/core/Akka/Event/UnhandledMessage.cs index 72cddf1cf34..819f3d11352 100644 --- a/src/core/Akka/Event/UnhandledMessage.cs +++ b/src/core/Akka/Event/UnhandledMessage.cs @@ -10,9 +10,9 @@ namespace Akka.Event { /// - /// This class represents a message that was not handled by the recipient. + /// This message is published to the EventStream whenever an Actor receives a message it doesn't understand /// - public sealed class UnhandledMessage + public sealed class UnhandledMessage : AllDeadLetters, IWrappedMessage { /// /// Initializes a new instance of the class. @@ -21,25 +21,8 @@ public sealed class UnhandledMessage /// The actor that sent the message. /// The actor that was to receive the message. public UnhandledMessage(object message, IActorRef sender, IActorRef recipient) + : base(message, sender, recipient) { - Message = message; - Sender = sender; - Recipient = recipient; } - - /// - /// The original message that could not be handled. - /// - public object Message { get; private set; } - - /// - /// The actor that sent the message. - /// - public IActorRef Sender { get; private set; } - - /// - /// The actor that was to receive the message. - /// - public IActorRef Recipient { get; private set; } } } diff --git a/src/core/Akka/Helios.Concurrency.DedicatedThreadPool.cs b/src/core/Akka/Helios.Concurrency.DedicatedThreadPool.cs index e6e5f21fa0d..497e27af097 100644 --- a/src/core/Akka/Helios.Concurrency.DedicatedThreadPool.cs +++ b/src/core/Akka/Helios.Concurrency.DedicatedThreadPool.cs @@ -1,4 +1,11 @@ -/* +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +/* * Copyright 2015 Roger Alsing, Aaron Stannard * Helios.DedicatedThreadPool - https://github.com/helios-io/DedicatedThreadPool */ diff --git a/src/core/Akka/IO/Dns.cs b/src/core/Akka/IO/Dns.cs index 143120feedb..b510f51331f 100644 --- a/src/core/Akka/IO/Dns.cs +++ b/src/core/Akka/IO/Dns.cs @@ -59,7 +59,7 @@ public class Dns : ExtensionIdProvider /// /// TBD /// - public abstract class Command + public abstract class Command : INoSerializationVerificationNeeded { } /// diff --git a/src/core/Akka/IO/Tcp.cs b/src/core/Akka/IO/Tcp.cs index f5efd617c8d..99695646f44 100644 --- a/src/core/Akka/IO/Tcp.cs +++ b/src/core/Akka/IO/Tcp.cs @@ -53,7 +53,7 @@ public override TcpExt CreateExtension(ExtendedActorSystem system) #region internal connection messages - internal abstract class SocketCompleted { } + internal abstract class SocketCompleted : INoSerializationVerificationNeeded { } internal sealed class SocketSent : SocketCompleted { diff --git a/src/core/Akka/IO/TcpListener.cs b/src/core/Akka/IO/TcpListener.cs index 494fed3fdbe..a26d8f80592 100644 --- a/src/core/Akka/IO/TcpListener.cs +++ b/src/core/Akka/IO/TcpListener.cs @@ -71,9 +71,9 @@ private IEnumerable Accept(int limit) { var self = Self; var saea = new SocketAsyncEventArgs(); - saea.Completed += (s, e) => self.Tell(e); + saea.Completed += (s, e) => self.Tell(new SocketEvent(e)); if (!_socket.AcceptAsync(saea)) - Self.Tell(saea); + Self.Tell(new SocketEvent(saea)); yield return saea; } } @@ -85,34 +85,34 @@ protected override SupervisorStrategy SupervisorStrategy() protected override bool Receive(object message) { - if (message is SocketAsyncEventArgs) + switch (message) { - var saea = message as SocketAsyncEventArgs; - if (saea.SocketError == SocketError.Success) - Context.ActorOf(Props.Create(_tcp, saea.AcceptSocket, _bind.Handler, _bind.Options, _bind.PullMode)); - saea.AcceptSocket = null; + case SocketEvent evt: + var saea = evt.Args; + if (saea.SocketError == SocketError.Success) + Context.ActorOf(Props.Create(_tcp, saea.AcceptSocket, _bind.Handler, _bind.Options, _bind.PullMode).WithDeploy(Deploy.Local)); + saea.AcceptSocket = null; - if (!_socket.AcceptAsync(saea)) - Self.Tell(saea); - return true; - } - var resumeAccepting = message as Tcp.ResumeAccepting; - if (resumeAccepting != null) - { - _acceptLimit = resumeAccepting.BatchSize; - _saeas = Accept(_acceptLimit).ToArray(); - return true; - } - if (message is Tcp.Unbind) - { - _log.Debug("Unbinding endpoint {0}", _bind.LocalAddress); - _socket.Dispose(); - Sender.Tell(Tcp.Unbound.Instance); - _log.Debug("Unbound endpoint {0}, stopping listener", _bind.LocalAddress); - Context.Stop(Self); - return true; + if (!_socket.AcceptAsync(saea)) + Self.Tell(new SocketEvent(saea)); + return true; + + case Tcp.ResumeAccepting resumeAccepting: + _acceptLimit = resumeAccepting.BatchSize; + _saeas = Accept(_acceptLimit).ToArray(); + return true; + + case Tcp.Unbind _: + _log.Debug("Unbinding endpoint {0}", _bind.LocalAddress); + _socket.Dispose(); + Sender.Tell(Tcp.Unbound.Instance); + _log.Debug("Unbound endpoint {0}, stopping listener", _bind.LocalAddress); + Context.Stop(Self); + return true; + + default: + return false; } - return false; } /// @@ -130,5 +130,15 @@ protected override void PostStop() _log.Debug("Error closing ServerSocketChannel: {0}", e); } } + + private readonly struct SocketEvent : INoSerializationVerificationNeeded + { + public readonly SocketAsyncEventArgs Args; + + public SocketEvent(SocketAsyncEventArgs args) + { + Args = args; + } + } } } diff --git a/src/core/Akka/IO/TcpManager.cs b/src/core/Akka/IO/TcpManager.cs index a2c77e6d86d..f844648f466 100644 --- a/src/core/Akka/IO/TcpManager.cs +++ b/src/core/Akka/IO/TcpManager.cs @@ -76,14 +76,14 @@ protected override bool Receive(object message) if (c != null) { var commander = Sender; - Context.ActorOf(Props.Create(_tcp, commander, c)); + Context.ActorOf(Props.Create(_tcp, commander, c).WithDeploy(Deploy.Local)); return true; } var b = message as Bind; if (b != null) { var commander = Sender; - Context.ActorOf(Props.Create(_tcp, commander, b)); + Context.ActorOf(Props.Create(_tcp, commander, b).WithDeploy(Deploy.Local)); return true; } var dl = message as DeadLetter; diff --git a/src/core/Akka/IO/Udp.cs b/src/core/Akka/IO/Udp.cs index b1aea2c2bff..213994ab542 100644 --- a/src/core/Akka/IO/Udp.cs +++ b/src/core/Akka/IO/Udp.cs @@ -34,7 +34,7 @@ public class Udp : ExtensionIdProvider { #region internal connection messages - internal abstract class SocketCompleted { } + internal abstract class SocketCompleted : INoSerializationVerificationNeeded { } internal sealed class SocketSent : SocketCompleted { @@ -104,7 +104,7 @@ public override UdpExt CreateExtension(ExtendedActorSystem system) } /// The common interface for and . - public abstract class Message { } + public abstract class Message : INoSerializationVerificationNeeded { } /// The common type of all commands supported by the UDP implementation. public abstract class Command : Message diff --git a/src/core/Akka/IO/UdpConnected.cs b/src/core/Akka/IO/UdpConnected.cs index 351648c6b77..30b9c213c64 100644 --- a/src/core/Akka/IO/UdpConnected.cs +++ b/src/core/Akka/IO/UdpConnected.cs @@ -34,7 +34,7 @@ public class UdpConnected : ExtensionIdProvider { #region internal connection messages - internal abstract class SocketCompleted + internal abstract class SocketCompleted : INoSerializationVerificationNeeded { public readonly SocketAsyncEventArgs EventArgs; @@ -92,7 +92,7 @@ public override UdpConnectedExt CreateExtension(ExtendedActorSystem system) /// /// The common interface for and . /// - public abstract class Message { } + public abstract class Message : INoSerializationVerificationNeeded { } /// /// The common type of all commands supported by the UDP implementation. @@ -372,7 +372,7 @@ private Disconnected() /// /// TBD /// - public class UdpConnectedExt : IOExtension + public class UdpConnectedExt : IOExtension, INoSerializationVerificationNeeded { public UdpConnectedExt(ExtendedActorSystem system) : this(system, UdpSettings.Create(system.Settings.Config.GetConfig("akka.io.udp-connected"))) diff --git a/src/core/Akka/IO/UdpManager.cs b/src/core/Akka/IO/UdpManager.cs index 46eef528531..7b91ab9f814 100644 --- a/src/core/Akka/IO/UdpManager.cs +++ b/src/core/Akka/IO/UdpManager.cs @@ -65,14 +65,14 @@ protected override bool Receive(object message) if (b != null) { var commander = Sender; - Context.ActorOf(Props.Create(() => new UdpListener(_udp, commander, b))); + Context.ActorOf(Props.Create(() => new UdpListener(_udp, commander, b)).WithDeploy(Deploy.Local)); return true; } var s = message as Udp.SimpleSender; if (s != null) { var commander = Sender; - Context.ActorOf(Props.Create(() => new UdpSender(_udp, commander, s.Options))); + Context.ActorOf(Props.Create(() => new UdpSender(_udp, commander, s.Options)).WithDeploy(Deploy.Local)); return true; } throw new ArgumentException($"The supplied message of type {message.GetType().Name} is invalid. Only Connect and Bind messages are supported. " + diff --git a/src/core/Akka/Pattern/RetrySupport.cs b/src/core/Akka/Pattern/RetrySupport.cs new file mode 100644 index 00000000000..2df128ae548 --- /dev/null +++ b/src/core/Akka/Pattern/RetrySupport.cs @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2020 Lightbend Inc. +// Copyright (C) 2013-2020 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Threading.Tasks; +using Akka.Actor; +using Akka.Util; +using static Akka.Pattern.FutureTimeoutSupport; + +namespace Akka.Pattern +{ + /// + /// This class provides the retry utility functions. + /// + public static class RetrySupport + { + /// + /// + /// Given a function, returns an internally retrying Task. + /// The first attempt will be made immediately, each subsequent attempt will be made immediately + /// if the previous attempt failed. + /// + /// If attempts are exhausted the returned Task is simply the result of invoking attempt. + /// + /// TBD + /// TBD + public static Task Retry(Func> attempt, int attempts) => + Retry(attempt, attempts, attempted: 0); + + /// + /// + /// Given a function, returns an internally retrying Task. + /// The first attempt will be made immediately, each subsequent attempt will be made with a backoff time, + /// if the previous attempt failed. + /// + /// If attempts are exhausted the returned Task is simply the result of invoking attempt. + /// + /// TBD + /// TBD + /// minimum (initial) duration until the child actor will started again, if it is terminated. + /// the exponential back-off is capped to this duration. + /// after calculation of the exponential back-off an additional random delay based on this factor is added, e.g. `0.2` adds up to `20%` delay. In order to skip this additional delay pass in `0`. + /// The scheduler instance to use. + public static Task Retry(Func> attempt, int attempts, TimeSpan minBackoff, TimeSpan maxBackoff, int randomFactor, IScheduler scheduler) + { + if (attempt == null) throw new ArgumentNullException("Parameter attempt should not be null."); + if (minBackoff <= TimeSpan.Zero) throw new ArgumentException("Parameter minBackoff must be > 0"); + if (maxBackoff < minBackoff) throw new ArgumentException("Parameter maxBackoff must be >= minBackoff"); + if (randomFactor < 0.0 || randomFactor > 1.0) throw new ArgumentException("RandomFactor must be between 0.0 and 1.0"); + + return Retry(attempt, attempts, attempted => BackoffSupervisor.CalculateDelay(attempted, minBackoff, maxBackoff, randomFactor), scheduler); + } + + /// + /// + /// Given a function, returns an internally retrying Task. + /// The first attempt will be made immediately, each subsequent attempt will be made after 'delay'. + /// A scheduler (eg Context.System.Scheduler) must be provided to delay each retry. + /// + /// If attempts are exhausted the returned future is simply the result of invoking attempt. + /// + /// TBD + /// TBD + /// TBD + /// The scheduler instance to use. + public static Task Retry(Func> attempt, int attempts, TimeSpan delay, IScheduler scheduler) => + Retry(attempt, attempts, _ => delay, scheduler); + + /// + /// + /// Given a function, returns an internally retrying Task. + /// The first attempt will be made immediately, each subsequent attempt will be made after + /// the 'delay' return by `delayFunction`(the input next attempt count start from 1). + /// Returns for no delay. + /// A scheduler (eg Context.System.Scheduler) must be provided to delay each retry. + /// You could provide a function to generate the next delay duration after first attempt, + /// this function should never return `null`, otherwise an will be through. + /// + /// If attempts are exhausted the returned Task is simply the result of invoking attempt. + /// + /// TBD + /// TBD + /// TBD + /// The scheduler instance to use. + public static Task Retry(Func> attempt, int attempts, Func> delayFunction, IScheduler scheduler) => + Retry(attempt, attempts, delayFunction, attempted: 0, scheduler); + + private static Task Retry(Func> attempt, int maxAttempts, int attempted) => + Retry(attempt, maxAttempts, _ => Option.None, attempted); + + private static Task Retry(Func> attempt, int maxAttempts, Func> delayFunction, int attempted, IScheduler scheduler = null) + { + Task tryAttempt() + { + try + { + return attempt(); + } + catch (Exception ex) + { + return Task.FromException(ex); // in case the `attempt` function throws + } + } + + if (maxAttempts < 0) throw new ArgumentException("Parameter maxAttempts must >= 0."); + if (attempt == null) throw new ArgumentNullException(nameof(attempt), "Parameter attempt should not be null."); + + if (maxAttempts - attempted > 0) + { + return tryAttempt().ContinueWith(t => + { + if (t.IsFaulted) + { + var nextAttempt = attempted + 1; + switch (delayFunction(nextAttempt)) + { + case Option delay when delay.HasValue: + return delay.Value.Ticks < 1 + ? Retry(attempt, maxAttempts, delayFunction, nextAttempt, scheduler) + : After(delay.Value, scheduler, () => Retry(attempt, maxAttempts, delayFunction, nextAttempt, scheduler)); + case Option _: + return Retry(attempt, maxAttempts, delayFunction, nextAttempt, scheduler); + default: + throw new InvalidOperationException("The delayFunction of Retry should not return null."); + } + } + return t; + }).Unwrap(); + } + + return tryAttempt(); + } + } +} diff --git a/src/core/Akka/Routing/ConsistentHashRouter.cs b/src/core/Akka/Routing/ConsistentHashRouter.cs index 53b399e7104..5b201cfdb97 100644 --- a/src/core/Akka/Routing/ConsistentHashRouter.cs +++ b/src/core/Akka/Routing/ConsistentHashRouter.cs @@ -47,7 +47,7 @@ public interface IConsistentHashable /// This class represents a that can be wrapped around a message in order to make /// it hashable for use with or routers. /// - public sealed class ConsistentHashableEnvelope : RouterEnvelope, IConsistentHashable + public sealed class ConsistentHashableEnvelope : RouterEnvelope, IConsistentHashable, IWrappedMessage { /// /// Initializes a new instance of the class. diff --git a/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs b/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs index 146e5136dcc..274c873a4c5 100644 --- a/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs +++ b/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs @@ -143,30 +143,43 @@ public NewtonSoftJsonSerializer(ExtendedActorSystem system, Config config) public NewtonSoftJsonSerializer(ExtendedActorSystem system, NewtonSoftJsonSerializerSettings settings) : base(system) { - var converters = settings.Converters - .Select(type => CreateConverter(type, system)) - .ToList(); - - converters.Add(new SurrogateConverter(this)); - converters.Add(new DiscriminatedUnionConverter()); - Settings = new JsonSerializerSettings { - PreserveReferencesHandling = settings.PreserveObjectReferences - ? PreserveReferencesHandling.Objects + PreserveReferencesHandling = settings.PreserveObjectReferences + ? PreserveReferencesHandling.Objects : PreserveReferencesHandling.None, - Converters = converters, NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, MissingMemberHandling = MissingMemberHandling.Ignore, - ObjectCreationHandling = ObjectCreationHandling.Replace, //important: if reuse, the serializer will overwrite properties in default references, e.g. Props.DefaultDeploy or Props.noArgs ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, TypeNameHandling = settings.EncodeTypeNames - ? TypeNameHandling.All + ? TypeNameHandling.All : TypeNameHandling.None, - ContractResolver = new AkkaContractResolver() }; + if (system != null) + { + var settingsSetup = system.Settings.Setup.Get() + .GetOrElse(NewtonSoftJsonSerializerSetup.Create(s => {})); + + settingsSetup.ApplySettings(Settings); + } + + var converters = settings.Converters + .Select(type => CreateConverter(type, system)) + .ToList(); + + converters.Add(new SurrogateConverter(this)); + converters.Add(new DiscriminatedUnionConverter()); + + foreach (var converter in converters) + { + Settings.Converters.Add(converter); + } + + Settings.ObjectCreationHandling = ObjectCreationHandling.Replace; //important: if reuse, the serializer will overwrite properties in default references, e.g. Props.DefaultDeploy or Props.noArgs + Settings.ContractResolver = new AkkaContractResolver(); + _serializer = JsonSerializer.Create(Settings); } diff --git a/src/core/Akka/Serialization/NewtonSoftJsonSerializerSetup.cs b/src/core/Akka/Serialization/NewtonSoftJsonSerializerSetup.cs new file mode 100644 index 00000000000..429057959b1 --- /dev/null +++ b/src/core/Akka/Serialization/NewtonSoftJsonSerializerSetup.cs @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text; +using Akka.Actor.Setup; +using Newtonsoft.Json; + +namespace Akka.Serialization +{ + /// + /// Setup for the serializer. + /// + /// Constructor is INTERNAL API. Use the factory method . + /// + /// NOTE: + /// - will always be overriden with + /// + /// - will always be overriden with the internal + /// contract resolver + /// + public sealed class NewtonSoftJsonSerializerSetup : Setup + { + public static NewtonSoftJsonSerializerSetup Create(Action settings) + => new NewtonSoftJsonSerializerSetup(settings); + + public Action ApplySettings { get; } + + private NewtonSoftJsonSerializerSetup(Action settings) + { + ApplySettings = settings; + } + } +} diff --git a/src/core/Akka/Util/Internal/ArrayExtensions.cs b/src/core/Akka/Util/Internal/ArrayExtensions.cs index eac11625b9a..9d1eaa222ab 100644 --- a/src/core/Akka/Util/Internal/ArrayExtensions.cs +++ b/src/core/Akka/Util/Internal/ArrayExtensions.cs @@ -103,12 +103,7 @@ internal static IEnumerable Slice(this IEnumerable items, int startInde /// TBD internal static IEnumerable From(this IEnumerable items, T startingItem) { - var itemsAsList = items.ToList(); - var indexOf = itemsAsList.IndexOf(startingItem); - if (indexOf == -1) return new List(); - if (indexOf == 0) return itemsAsList; - var itemCount = (itemsAsList.Count - indexOf); - return itemsAsList.Slice(indexOf, itemCount); + return items.SkipWhile(x => !x.Equals(startingItem)); } /// diff --git a/src/core/Akka/Util/Internal/Collections/Iterator.cs b/src/core/Akka/Util/Internal/Collections/Iterator.cs index e409baf09a4..bafba0c7b5f 100644 --- a/src/core/Akka/Util/Internal/Collections/Iterator.cs +++ b/src/core/Akka/Util/Internal/Collections/Iterator.cs @@ -10,13 +10,14 @@ namespace Akka.Util.Internal.Collections { - internal sealed class Iterator + internal struct Iterator { private readonly IList _enumerator; private int _index; public Iterator(IEnumerable enumerator) { + _index = 0; _enumerator = enumerator.ToList(); } diff --git a/src/protobuf/ClusterMessages.proto b/src/protobuf/ClusterMessages.proto index f749e0aec85..4206c0f3c21 100644 --- a/src/protobuf/ClusterMessages.proto +++ b/src/protobuf/ClusterMessages.proto @@ -30,7 +30,7 @@ import "ContainerFormats.proto"; message Join { UniqueAddress node = 1; repeated string roles = 2; - optional string appVersion = 3; + string appVersion = 3; } // Welcome, reply to Join @@ -59,14 +59,28 @@ message Welcome { ****************************************/ /** + * Prior to version 1.4.19 * Heartbeat * Sends an Address + * Version 1.4.19 can deserialize this message but does not send it */ + message Heartbeat { + Akka.Remote.Serialization.Proto.Msg.AddressData from = 1; + int64 sequenceNr = 2; + sint64 creationTime = 3; +} /** + * Prior to version 1.4.19 * HeartbeatRsp * Sends an UniqueAddress + * Version 1.4.19 can deserialize this message but does not send it */ + message HeartBeatResponse { + UniqueAddress from = 1; + int64 sequenceNr = 2; + int64 creationTime = 3; +} /**************************************** * Cluster Gossip Messages @@ -138,7 +152,7 @@ message Member { int32 upNumber = 2; MemberStatus status = 3; repeated int32 rolesIndexes = 4 [packed = true]; - optional int32 appVersionIndex = 5; + int32 appVersionIndex = 5; } // Vector Clock