diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 29d2aca9c6b..b407db39c0a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,24 @@ +#### 1.4.34 March 7 2022 #### +Akka.NET v1.4.33 is a minor release that contains some bug fixes. Most of the changes have been aimed at improving our web documentation and code cleanup to modernize some of our code. + +__Bug fixes__: +* [Akka: Added support to pass a state object into CircuitBreaker to reduce allocations](https://github.com/akkadotnet/akka.net/pull/5650) +* [Akka.DistributedData: ORSet merge operation performance improvement](https://github.com/akkadotnet/akka.net/pull/5686) +* [Akka.Streams: FlowWithContext generic type parameters have been reordered to make them easier to read](https://github.com/akkadotnet/akka.net/pull/5648) + +__Improvements__: +* [Akka: PipeTo can be configured to retain async threading context](https://github.com/akkadotnet/akka.net/pull/5684) + +If you want to see the [full set of changes made in Akka.NET v1.4.33, click here](https://github.com/akkadotnet/akka.net/milestone/65?closed=1). + +| COMMITS | LOC+ | LOC- | AUTHOR | +|---------|--------|-------|---------------------| +| 12 | 1177 | 718 | Ebere Abanonu | +| 6 | 192 | 47 | Gregorius Soedharmo | +| 3 | 255 | 167 | Ismael Hamed | +| 1 | 3 | 0 | Aaron Stannard | +| 1 | 126 | 10 | Drew | + #### 1.4.33 February 14 2022 #### Akka.NET v1.4.33 is a minor release that contains some bug fixes. Most of the changes have been aimed at improving our web documentation and code cleanup to modernize some of our code. The most important bug fix is the actor Props memory leak when actors are cached inside Akka.Remote. diff --git a/build-system/azure-pipeline.template.yaml b/build-system/azure-pipeline.template.yaml index 1b6e12e22d2..4d0083a0d0a 100644 --- a/build-system/azure-pipeline.template.yaml +++ b/build-system/azure-pipeline.template.yaml @@ -8,9 +8,11 @@ parameters: scriptArgs: 'all' outputDirectory: '' timeoutInMinutes: 120 + run_if: true jobs: - job: ${{ parameters.name }} + condition: eq( ${{ parameters.run_if }}, true ) displayName: ${{ parameters.displayName }} timeoutInMinutes: ${{ parameters.timeoutInMinutes }} pool: diff --git a/build-system/pr-validation.yaml b/build-system/pr-validation.yaml index e9b676bb603..315e664b808 100644 --- a/build-system/pr-validation.yaml +++ b/build-system/pr-validation.yaml @@ -124,6 +124,17 @@ jobs: outputDirectory: "TestResults" artifactName: "net_core_tests_linux-$(Build.BuildId)" + - template: azure-pipeline.template.yaml + parameters: + name: "docfx_test" + displayName: "DocFX warning check" + vmImage: "windows-2019" + scriptFileName: build.cmd + scriptArgs: docfx + outputDirectory: "TestResults" + artifactName: "docfx_test-$(Build.BuildId)" + run_if: eq(variables['Build.Reason'], 'PullRequest') + - template: azure-pipeline.template.yaml parameters: name: "net_5_tests_windows" diff --git a/build.fsx b/build.fsx index 8c00398bda4..6339b45a386 100644 --- a/build.fsx +++ b/build.fsx @@ -632,12 +632,19 @@ Target "DocFx" (fun _ -> Version = "0.1.0-alpha-1611021200" OutputDirectory = currentDirectory @@ "tools" }) "msdn.4.5.2" - let docsPath = "./docs" - DocFx (fun p -> - { p with - Timeout = TimeSpan.FromMinutes 30.0; - WorkingDirectory = docsPath; - DocFxJson = docsPath @@ "docfx.json" }) + let docsPath = FullName "./docs" + let docFxPath = FullName(findToolInSubPath "docfx.exe" "tools/docfx.console/tools") + + let args = StringBuilder() + |> append (docsPath @@ "docfx.json" ) + |> append ("--warningsAsErrors") + |> toText + + let result = ExecProcess(fun info -> + info.FileName <- docFxPath + info.WorkingDirectory <- (Path.GetDirectoryName (FullName docFxPath)) + info.Arguments <- args) (System.TimeSpan.FromMinutes 45.0) (* Reasonably long-running task. *) + if result <> 0 then failwithf "DocFX failed. %s %s" docFxPath args ) FinalTarget "KillCreatedProcesses" (fun _ -> diff --git a/docs/api/index.md b/docs/api/index.md index 12675fef6ba..acaf2ba7a43 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -11,7 +11,6 @@ Full API reference documentation on Akka.NET's modules, classes, and APIs. * [Akka.Streams API Docs](Akka.Streams.yml) * [Akka.Cluster API Docs](Akka.Cluster.yml) * [Akka.Cluster.Sharding API Docs](Akka.Cluster.Sharding.yml) -* [Akka.Cluster.Tools API Docs](Akka.Cluster.Tools.yml) * [Akka.DependencyInjection API Docs](Akka.DependencyInjection.yml) * [Akka.Discovery API Docs](Akka.Discovery.yml) diff --git a/docs/articles/actors/coordinated-shutdown.md b/docs/articles/actors/coordinated-shutdown.md index 1090648493c..2446f76a1aa 100644 --- a/docs/articles/actors/coordinated-shutdown.md +++ b/docs/articles/actors/coordinated-shutdown.md @@ -8,79 +8,7 @@ There's an `ActorSystem` extension called `CoordinatedShutdown` that will stop c The default phases and their orderings are defined in the default HOCON configuration as `akka.coordinated-shutdown.phases`, and they are defined below: -```hocon -phases { - - # 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 - # 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. - actor-system-terminate { - timeout = 10 s - depends-on = [before-actor-system-terminate] - } -} -``` +[!code-hocon[Pigeon.conf](../../../src/core/Akka/Configuration/Pigeon.conf#L1019-L1091)] ## Custom Phases diff --git a/docs/articles/actors/di-core.md b/docs/articles/actors/di-core.md index 7c517ca25e2..c881d329590 100644 --- a/docs/articles/actors/di-core.md +++ b/docs/articles/actors/di-core.md @@ -6,7 +6,7 @@ title: DI Core # Akka.DI.Core > [!WARNING] -> As of [Akka.NET v1.4.15](https://github.com/akkadotnet/akka.net/releases/tag/1.4.15), Akka.DI.Core and all of the libraries that implement it are deprecated. Going forward Akka.NET users are encouraged to use the [Akka.DependencyInjection library](xref:dependency-injection) instead, which uses the Microsoft.Extensions.DependencyInjection interfaces to integration DI directly into your Akka.NET actors. +> As of [Akka.NET v1.4.15](https://github.com/akkadotnet/akka.net/releases/tag/1.4.15), Akka.DI.Core and all of the libraries that implement it are deprecated. Going forward Akka.NET users are encouraged to use the [Akka.DependencyInjection library](xref:dependency-injection) instead, which uses the Microsoft.Extensions.DependencyInjection interfaces to integrate DI directly into your Akka.NET actors. **Actor Producer Extension** library is used to create a Dependency Injection Container for the [Akka.NET](https://github.com/akkadotnet/akka.net) framework. diff --git a/docs/articles/actors/finite-state-machine.md b/docs/articles/actors/finite-state-machine.md index 61751b86933..3cc336f30b0 100644 --- a/docs/articles/actors/finite-state-machine.md +++ b/docs/articles/actors/finite-state-machine.md @@ -99,9 +99,7 @@ The `stateFunction` argument is a `delegate State StateFunction(E Each `FSM` needs a starting point, which is declared using -```csharp -StartWith(state, data[, timeout]) -``` +[!code-csharp[Main](../../../src/core/Akka.Docs.Tests/Actors/FiniteStateMachine/ExampleFSMActor.cs?name=StartWith)] The optionally given timeout argument overrides any specification given for the desired initial state. If you want to cancel a default timeout, use `null`. @@ -109,21 +107,7 @@ The optionally given timeout argument overrides any specification given for the If a state doesn't handle a received event a warning is logged. If you want to do something else in this case you can specify that with `WhenUnhandled(stateFunction)`: -```csharp -WhenUnhandled(state => -{ - if (state.FsmEvent is Queue x) - { - _log.Info("Received unhandled event: " + x); - return Stay(); - } - else - { - _log.Warning("Received unknown event: " + state.FsmEvent); - return Goto(new Error()); - } -}); -``` +[!code-csharp[Main](../../../src/core/Akka.Docs.Tests/Actors/FiniteStateMachine/ExampleFSMActor.cs?name=UnhandledHandler)] Within this handler the state of the `FSM` may be queried using the stateName method. @@ -163,23 +147,7 @@ OnTransition(handler) which associates actions with a transition instead of with a state and event. The handler is a delegate `void TransitionHandler(TState initialState, TState nextState)` function which takes a pair of states as input; no resulting state is needed as it is not possible to modify the transition in progress. -```csharp -OnTransition((initialState, nextState) => -{ - if (initialState == State.Active && nextState == State.Idle) - { - SetTimer("timeout", new Tick(), TimeSpan.FromSeconds(1), repeat: true); - } - else if (initialState == State.Active) - { - CancelTimer("timeout"); - } - else if (nextState == State.Idle) - { - _log.Info("entering Idle from " + initialState); - } -}); -``` +[!code-csharp[Main](../../../src/core/Akka.Docs.Tests/Actors/FiniteStateMachine/ExampleFSMActor.cs?name=TransitionHandler)] The handlers registered with this method are stacked, so you can intersperse `OnTransition` blocks with when blocks as suits your design. It should be noted, however, that all handlers will be invoked for each transition, not only the first matching one. This is designed specifically so you can put all transition handling for a certain aspect into one place without having to worry about earlier declarations shadowing later ones; the actions are still executed in declaration order, though. diff --git a/docs/articles/networking/io.md b/docs/articles/actors/io.md similarity index 100% rename from docs/articles/networking/io.md rename to docs/articles/actors/io.md diff --git a/docs/articles/actors/toc.yml b/docs/articles/actors/toc.yml new file mode 100644 index 00000000000..8fa09e87b78 --- /dev/null +++ b/docs/articles/actors/toc.yml @@ -0,0 +1,29 @@ +- name: ReceiveActor API + href: receive-actor-api.md +- name: UntypedActor API + href: untyped-actor-api.md +- name: Routers + href: routers.md +- name: Dispatchers + href: dispatchers.md +- name: Mailboxes + href: mailboxes.md +- name: Scheduling Future and Recurring Messages + href: schedulers.md +- name: Akka.IO + href: io.md +- name: Inbox + href: inbox.md +- name: Finite State Machines + href: finite-state-machine.md +- name: Fault Tolerance + href: fault-tolerance.md +- name: Dependency Injection + href: dependency-injection.md +- name: Dependency Injection Core + href: di-core.md +- name: Testing Actor Systems + href: testing-actor-systems.md +- name: Coordinated Shutdown + href: coordinated-shutdown.md + \ No newline at end of file diff --git a/docs/articles/clustering/toc.yml b/docs/articles/clustering/toc.yml new file mode 100644 index 00000000000..396a85065fe --- /dev/null +++ b/docs/articles/clustering/toc.yml @@ -0,0 +1,24 @@ +- name: Overview + href: cluster-overview.md +- name: Cluster Routing + href: cluster-routing.md +- name: Cluster Configuration + href: cluster-configuration.md +- name: Accessing the Cluster ActorSystem Extension + href: cluster-extension.md +- name: Cluster Singleton + href: cluster-singleton.md +- name: Distributed Publish Subscribe in Cluster + href: distributed-publish-subscribe.md +- name: Cluster Client + href: cluster-client.md +- name: Cluster Sharding + href: cluster-sharding.md +- name: Sharded Daemon Process + href: cluster-sharded-daemon-process.md +- name: Cluster Metrics + href: cluster-metrics.md +- name: Distributed Data + href: distributed-data.md +- name: Split Brain Resolver + href: split-brain-resolver.md \ No newline at end of file diff --git a/docs/articles/concepts/toc.yml b/docs/articles/concepts/toc.yml new file mode 100644 index 00000000000..6887703b311 --- /dev/null +++ b/docs/articles/concepts/toc.yml @@ -0,0 +1,14 @@ +- name: Terminology, Concepts + href: terminology.md +- name: Actor Systems + href: actor-systems.md +- name: What is an Actor? + href: actors.md +- name: Supervision and Monitoring + href: supervision.md +- name: Actor References, Paths and Addresses + href: addressing.md +- name: Location Transparency + href: location-transparency.md +- name: Message Delivery Reliability + href: message-delivery-reliability.md \ No newline at end of file diff --git a/docs/articles/configuration/akka.html b/docs/articles/configuration/akka.html new file mode 100644 index 00000000000..478d7e9582a --- /dev/null +++ b/docs/articles/configuration/akka.html @@ -0,0 +1,10 @@ + + + + Akka Configuration + + + +

This page has been moved to Akka Configuration.

+ + \ No newline at end of file diff --git a/docs/articles/configuration/hocon.md b/docs/articles/configuration/hocon.md index 4019ba8b6d7..b5909b4e15e 100644 --- a/docs/articles/configuration/hocon.md +++ b/docs/articles/configuration/hocon.md @@ -253,9 +253,9 @@ Substitution processing is performed as the last parsing step, so a substitution If a key has been specified more than once, the substitution will always evaluate to its latest-assigned value (that is, it will evaluate to the merged object, or the last non-object value that was set, in the entire document being parsed including all included files). -If a substitution does not match any value present in the configuration and is not resolved by an external source, then it is undefined. An undefined _required substitution_ is invalid and will generate an error. +If a substitution does not match any value present in the configuration, then it is undefined. An undefined _required substitution_ is invalid and will generate an error. -[!code-csharp[ConfigurationSample](../../../src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs?name=UnsolvableSubstitutionWillThrowSample)] +[!code-csharp[SerializationSetupDocSpec](../../../src/core/Akka.Docs.Tests/Configuration/SerializationSetupDocSpec.cs?name=UnsolvableSubstitutionWillThrowSample)] If an _optional substitution_ is undefined: @@ -271,168 +271,5 @@ A substitution is replaced with any value type (number, object, string, array, t Examples: -[!code-csharp[ConfigurationSample](../../../src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs?name=StringSubstitutionSample)] -[!code-csharp[ConfigurationSample](../../../src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs?name=ArraySubstitutionSample)] -[!code-csharp[ConfigurationSample](../../../src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs?name=ObjectMergeSubstitutionSample)] - -#### Using Substitution to Access Environment Variables - -For substitutions which are not found in the configuration tree, it will be resolved by looking at system environment variables. - -[!code-csharp[ConfigurationSample](../../../src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs?name=EnvironmentVariableSample)] - -An application can explicitly block looking up a substitution in the environment by setting a value in the configuration, with the same name as the environment variable. You could set `HOME : null` in your root object to avoid expanding `${HOME}` from the environment, for example: - -[!code-csharp[ConfigurationSample](../../../src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs?name=BlockedEnvironmentVariableSample)] - -It's recommended that HOCON keys always use lowercase, because environment variables generally are capitalized. This avoids naming collisions between environment variables and configuration properties. (While on Windows `Environment.GetEnvironmentVariable()` is generally not case-sensitive, the lookup will be case sensitive all the way until the env variable fallback lookup is reached). - -Environment variables are interpreted as follows: - -* Env variables set to the empty string are kept as such (set to empty string, rather than undefined) -* If `Environment.GetEnvironmentVariable()` throws SecurityException, then it is treated as not present -* Encoding is handled by C# (`Environment.GetEnvironmentVariable()` already returns a Unicode string) -* Environment variables always become a string value, though if an app asks for another type automatic type conversion would kick in - -##### Note on Windows and Case Sensitivity of Environment Variables - -HOCON's lookup of environment variable values is always case sensitive, but Linux and Windows differ in their handling of case. - -Linux allows one to define multiple environment variables with the same name but with different case; so both "PATH" and "Path" may be defined simultaneously. HOCON's access to these environment variables on Linux is straightforward; ie just make sure you define all your vars with the required case. - -Windows is more confusing. Windows environment variables names may contain a mix of upper and lowercase characters, eg "Path", however Windows does not allow one to define multiple instances of the same name but differing in case. -Whilst accessing env vars in Windows is case insensitive, accessing env vars in HOCON is case sensitive. - -So if you know that you HOCON needs "PATH" then you must ensure that the variable is defined as "PATH" rather than some other name such as "Path" or "path". -However, Windows does not allow us to change the case of an existing env var; we can't simply redefine the var with an upper case name. -The only way to ensure that your environment variables have the desired case is to first undefine all the env vars that you will depend on then redefine them with the required case. - -For example, the the ambient environment might have this definition ... - -```cmd -set Path=A;B;C -``` - -... we just don't know. But if the HOCON needs "PATH", then the start script must take a precautionary approach and enforce the necessary case as follows ... - -```cmd -set OLDPATH=%PATH% -set PATH= -set PATH=%OLDPATH% -``` - -You cannot know what ambient environment variables might exist in the ambient environment when your program is invoked, nor what case those definitions might have. Therefore the only safe thing to do is redefine all the vars you rely on as shown above. - -#### Self-Referential Substitutions - -The idea of self-referential substitution is to allow a new value for a field to be based on the older value. - -```text - path : "a:b:c" - path : ${path}":d" -``` - -is equal to: - -```text - path : "a:b:c:d" -``` - -Examples of self-referential fields: - -[!code-csharp[ConfigurationSample](../../../src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs?name=SelfReferencingSubstitutionWithString)] -[!code-csharp[ConfigurationSample](../../../src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs?name=SelfReferencingSubstitutionWithArray)] - -Note that an object or array with a substitution inside it is **not** considered self-referential for this purpose. The self-referential rules do **not** apply to: - -* `a : { b : ${a} }` -* `a : [${a}]` - -These cases are unbreakable cycles that generate an error. - -Examples: - -[!code-csharp[ConfigurationSample](../../../src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs?name=CircularReferenceSubstitutionError)] - -#### The `+=` Field Separator - -Fields may have `+=` as a separator rather than `:` or `=`. A field with `+=` transforms into a self-referential array -concatenation, like this: - - a += b - -becomes: - - a = ${?a} [b] - -`+=` appends an element to a previous array. If the previous value was not an array, an error will result just as it would in the long form `a = ${?a} [b]`. Note that the previous value is optional (`${?a}` not `${a}`), which allows `a += b` to be the first mention of `a` in the file (it is not necessary to have `a = []` first). - -Example: - -[!code-csharp[ConfigurationSample](../../../src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs?name=PlusEqualOperatorSample)] - -#### Examples of Self-Referential Substitutions - -In isolation (with no merges involved), a self-referential field is an error because the substitution cannot be resolved: - - foo : ${foo} // an error - -When `foo : ${foo}` is merged with an earlier value for `foo`, however, the substitution can be resolved to that earlier value. When merging two objects, the self-reference in the overriding field refers to the overridden field. Say you have: - - foo : { a : 1 } - foo : ${foo} - -Then `${foo}` resolves to `{ a : 1 }`, the value of the overridden field. - -It would be an error if these two fields were reversed, so: - - foo : ${foo} - foo : { a : 1 } - -Here the `${foo}` self-reference comes before `foo` has a value, so it is undefined, exactly as if the substitution referenced a path not found in the document. - -Because `foo : ${foo}` conceptually looks to previous definitions of `foo` for a value, the optional substitution syntax `${?foo}` does not create a cycle: - - foo : ${?foo} // this field just disappears silently - -If a substitution is hidden by a value that could not be merged with it (by a non-object value) then it is never evaluated and no error will be reported. So for example: - - foo : ${does-not-exist} - foo : 42 - -In this case, no matter what `${does-not-exist}` resolves to, we know `foo` is `42`, so `${does-not-exist}` is never evaluated and there is no error. The same is true for cycles like `foo : ${foo}, foo : 42`, where the initial self-reference are simply ignored. - -A self-reference resolves to the value "below" even if it's part of a path expression. So for example: - - foo : { a : { c : 1 } } - foo : ${foo.a} - foo : { a : 2 } - -Here, `${foo.a}` would refer to `{ c : 1 }` rather than `2` and so the final merge would be `{ a : 2, c : 1 }`. - -Recall that for a field to be self-referential, it must have a substitution or value concatenation as its value. If a field has an object or array value, for example, then it is not self-referential even if there is a reference to the field itself inside that object or array. - -Substitution can refer to paths within themselves, for example: - - bar : { foo : 42, - baz : ${bar.foo} - } - -Because there is no inherent cycle here, the substitution will "look forward" (including looking at the field currently being defined). To make this clearer, in the example below, `bar.baz` would be `43`: - - bar : { foo : 42, - baz : ${bar.foo} - } - bar : { foo : 43 } - -Mutually-referring objects would also work, and are not self-referential (so they look forward): - - // bar.a will end up as 4 and foo.c will end up as 3 - bar : { a : ${foo.d}, b : 1 } - bar.b = 3 - foo : { c : ${bar.b}, d : 2 } - foo.d = 4 - -One tricky case is an optional self-reference in a value concatenation, in this example `a` would be `foo` not `foofoo` because the self reference has to "look back" to an undefined `a`: - - a = ${?a}foo +[!code-csharp[SerializationSetupDocSpec](../../../src/core/Akka.Docs.Tests/Configuration/SerializationSetupDocSpec.cs?name=StringSubstitutionSample)] +[!code-csharp[SerializationSetupDocSpec](../../../src/core/Akka.Docs.Tests/Configuration/SerializationSetupDocSpec.cs?name=ArraySubstitutionSample)] diff --git a/docs/articles/configuration/akka.md b/docs/articles/configuration/modules/akka.md similarity index 62% rename from docs/articles/configuration/akka.md rename to docs/articles/configuration/modules/akka.md index f8b6fbfb280..42360758438 100644 --- a/docs/articles/configuration/akka.md +++ b/docs/articles/configuration/modules/akka.md @@ -7,4 +7,4 @@ title: Akka Configuration Below is the default HOCON configuration for the base `Akka` package. -[!code[Akka.dll HOCON Configuration](../../../src/core/Akka/Configuration/Pigeon.conf)] +[!code[Akka.dll HOCON Configuration](../../../../src/core/Akka/Configuration/Pigeon.conf)] diff --git a/docs/articles/configuration/modules/toc.yml b/docs/articles/configuration/modules/toc.yml index 807da952c5d..2579abcb650 100644 --- a/docs/articles/configuration/modules/toc.yml +++ b/docs/articles/configuration/modules/toc.yml @@ -1,3 +1,5 @@ +- name: Akka + href: akka.md - name: Akka.Cluster href: akka.cluster.md - name: Akka.Remote diff --git a/docs/articles/configuration/toc.yml b/docs/articles/configuration/toc.yml index 3a43e97667b..07360f27964 100644 --- a/docs/articles/configuration/toc.yml +++ b/docs/articles/configuration/toc.yml @@ -2,7 +2,5 @@ href: config.md - name: HOCON Syntax and Practices in Akka.NET href: hocon.md -- name: Akka - href: akka.md - name: Modules href: modules/toc.yml \ No newline at end of file diff --git a/docs/articles/deployment/aspnet-core.md b/docs/articles/deployment/aspnet-core.md new file mode 100644 index 00000000000..aa0f1f5876b --- /dev/null +++ b/docs/articles/deployment/aspnet-core.md @@ -0,0 +1,31 @@ +--- +uid: aspnet-core-scenario +title: ASP.NET Core +--- + +# ASP.NET Core + +## The Bridge + +When deploying Akka.NET in ASP.NET Core, one major concern is how to expose `actor` in an ASP.NET Core controllers. We will design an `interface` for this! + +[!code-csharp[Main](../../../src/examples/AspNetCore/Akka.AspNetCore/IActorBridge.cs?name=actor-bridge)] + +## Akka.NET with `IHostedService` + +With the `IActorBridge` created, next is to host Akka.NET with `IHostedService` which will also implement the `IActorBridge`: + +[!code-csharp[Main](../../../src/examples/AspNetCore/Akka.AspNetCore/AkkaService.cs?name=akka-aspnet-core-service)] + +## Interaction Between Controllers and Akka.NET + +[!code-csharp[Main](../../../src/examples/AspNetCore/Akka.AspNetCore/Controllers/AkkaController.cs?name=akka-aspnet-core-controllers)] + +### Wire up Akka.NET and ASP.NET Core + +We need to replace the default `IHostedService` with `AkkaService` and also inject `IActorBridge`. + +[!code-csharp[Main](../../../src/examples/AspNetCore/Akka.AspNetCore/Program.cs?name=akka-asp-net-program)] + +> [!NOTE] +> Visit our site's blog post for [Best Practices for Integrating Akka.NET with ASP.NET Core and SignalR](https://petabridge.com/blog/akkadotnet-aspnetcore) diff --git a/docs/articles/deployment/azure-cloud-service.md b/docs/articles/deployment/azure-cloud-service.md new file mode 100644 index 00000000000..7f97d2b9f74 --- /dev/null +++ b/docs/articles/deployment/azure-cloud-service.md @@ -0,0 +1,81 @@ +--- +uid: azure-cloud-service +title: Azure Cloud Service (Deprecated) +--- + +# Azure Cloud Service (Deprecated) + +> [!IMPORTANT] +> Cloud Services are largely deprecated in Azure now. + +The following sample assumes that you have created a new Azure PaaS Cloud Service that contains a single +empty Worker Role. The Cloud Service project templates are added to Visual Studio by installing the +[Azure .Net SDK](http://azure.microsoft.com/en-gb/downloads/). + +The Worker Role implementation can be tested locally using the Azure Compute Emulator before deploying to the cloud. The MSDN Azure article ["Using Emulator Express to Run and Debug a Cloud Service Locally"](https://msdn.microsoft.com/en-us/library/azure/dn339018.aspx) describes this in more detail. + +The Azure PaaS Worker Role implementation is very similar to the [Windows Service](xref:windows-service). +The quickest way to get started with Akka.Net is to create a simple Worker Role which invokes the top-level +user-actor in the RunAsync() method, as follows: + +## WorkerRole.cs + +```csharp +using Akka.Actor; + +namespace MyActorWorkerRole +{ + public class WorkerRole : RoleEntryPoint + { + private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false); + + private ActorSystem _actorSystem; + + public override bool OnStart() + { + // Setup the Actor System + _actorSystem = ActorSystem.Create("MySystem"); + + return (base.OnStart()); + } + + public override void OnStop() + { + this.cancellationTokenSource.Cancel(); + this.runCompleteEvent.WaitOne(); + + // Shutdown the Actor System + _actorSystem.Shutdown(); + + base.OnStop(); + } + + public override void Run() + { + try + { + this.RunAsync(this.cancellationTokenSource.Token).Wait(); + } + finally + { + this.runCompleteEvent.Set(); + } + } + + private async Task RunAsync(CancellationToken cancellationToken) + { + // Create an instance to the top-level user Actor + var workerRoleActor = _actorSystem.ActorOf("WorkerRole"); + + // Send a message to the Actor + workerRoleActor.Tell(new WorkerRoleMessage("Hello World!")); + + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(1000, cancellationToken); + } + } + } +} +``` diff --git a/docs/articles/deployment/console.md b/docs/articles/deployment/console.md new file mode 100644 index 00000000000..5f5f6092226 --- /dev/null +++ b/docs/articles/deployment/console.md @@ -0,0 +1,21 @@ +--- +uid: console-scenario +title: Console Application +--- +# Console Application + +## Create Message Class `Greet` + +The way to tell an actor to do something is by sending it a message. `Greet` is the message type to be sent! + +[!code-csharp[Main](../../../src/examples/HelloAkka/HelloWorld/Greet.cs?name=hello-world-message)] + +## Creating the `GreetingActor` + +[!code-csharp[Main](../../../src/examples/HelloAkka/HelloWorld/GreetingActor.cs?name=akka-hello-world-greeting)] + +`PreStart` will be called by the Akka framework when the actor is getting started. The `PostStop` will be called after the actor is stopped. + +## Creating a Console Host + +[!code-csharp[Main](../../../src/examples/HelloAkka/HelloWorld/Program.cs?name=akka-hello-world-main)] diff --git a/docs/articles/deployment/headless-service.md b/docs/articles/deployment/headless-service.md new file mode 100644 index 00000000000..e8d1f93503a --- /dev/null +++ b/docs/articles/deployment/headless-service.md @@ -0,0 +1,23 @@ +--- +uid: headless-service +title: Headless Service +--- +# Akka.NET Headless Service + +## Headless Actor + +[!code-csharp[Main](../../../src/examples/HeadlessService/AkkaHeadlesssService/HeadlessActor.cs?name=headless-actor)] + +## Headless Service + +[!code-csharp[Main](../../../src/examples/HeadlessService/AkkaHeadlesssService/AkkaService.cs?name=headless-akka-service)] + +## Headless Service Host + +[!code-csharp[Main](../../../src/examples/HeadlessService/AkkaHeadlesssService/Program.cs?name=headless-service-program)] + +## What About Windows Service? + +Well, quickly, we have a sample application, [Windows Service](xref:windows-service), to show how `Windows Services` are now being built with `BackgroundService`! + +[learn more](https://docs.microsoft.com/en-us/dotnet/core/extensions/windows-service) diff --git a/docs/articles/deployment/index.md b/docs/articles/deployment/index.md new file mode 100644 index 00000000000..935043eca01 --- /dev/null +++ b/docs/articles/deployment/index.md @@ -0,0 +1,28 @@ +--- +uid: deployment-scenarios +title: Deployment Scenarios +--- + +# Console Application + +The console application gives immediate feedback on how a piece of software works. That how most developers test out, quickly, new products to understand its entry and exit point! + +[Console Application](xref:console-scenario) + +# ASP.NET Core + +By using Akka.NET in your ASP.NET Core project, you can make your `Controllers` lightweight while Akka.NET handles the heavyweights. + +[ASP.NET Core](xref:aspnet-core-scenario) + +# Headless Service + +A “headless” service is one that doesn’t expose a public-facing API or UI of any kind - no HTTP APIs, no gRPC, etc. + +Headless services can still communicate with other services via private messaging systems such as Akka.Remote, Kafka, Apache Pulsar, RabbitMQ, and so forth - but they run in the background of most networked applications. + +[Headless Service](xref:headless-service) + +# Azure Cloud Service (Deprecated) + +[Azure Cloud Service](xref:azure-cloud-service) diff --git a/docs/articles/deployment/toc.yml b/docs/articles/deployment/toc.yml new file mode 100644 index 00000000000..7ca473c472b --- /dev/null +++ b/docs/articles/deployment/toc.yml @@ -0,0 +1,10 @@ +- name: Deployment Scenarios + href: index.md +- name: Console Application + href: console.md +- name: ASP.NET Core + href: aspnet-core.md +- name: Headless Service + href: headless-service.md +- name: Azure Cloud Service (Deprecated) + href: azure-cloud-service.md \ No newline at end of file diff --git a/docs/articles/deployment/windows-service.md b/docs/articles/deployment/windows-service.md new file mode 100644 index 00000000000..31e4132ab15 --- /dev/null +++ b/docs/articles/deployment/windows-service.md @@ -0,0 +1,24 @@ +--- +uid: windows-service +title: Windows Service +--- + +# Windows Service + +[learn more from Microsoft](https://docs.microsoft.com/en-us/dotnet/core/extensions/windows-service) + +## Program.cs + +[!code-csharp[Main](../../../src/examples/WindowsService/AkkaWindowsService/Program.cs?name=akka-windows-service-program)] + +## AkkaService.cs + +[!code-csharp[Main](../../../src/examples/WindowsService/AkkaWindowsService/AkkaService.cs?name=akka-windows-service)] + +## JokeService.cs + +[!code-csharp[Main](../../../src/examples/WindowsService/AkkaWindowsService/JokeService.cs?name=akka-windows-joke-service)] + +## MyActor.cs + +[!code-csharp[Main](../../../src/examples/WindowsService/AkkaWindowsService/MyActor.cs?name=akka-windows-service-actor)] diff --git a/docs/articles/intro/deployment-scenarios.html b/docs/articles/intro/deployment-scenarios.html new file mode 100644 index 00000000000..95f339e2cb2 --- /dev/null +++ b/docs/articles/intro/deployment-scenarios.html @@ -0,0 +1,10 @@ + + + + Use-Case and Deployment Scenarios + + + +

This page has been moved to Deployment.

+ + \ No newline at end of file diff --git a/docs/articles/intro/toc.yml b/docs/articles/intro/toc.yml index 3119a76f9f9..b8f7420f999 100644 --- a/docs/articles/intro/toc.yml +++ b/docs/articles/intro/toc.yml @@ -4,8 +4,6 @@ href: what-problems-does-actor-model-solve.md - name: Akka.NET Libraries and Modules href: modules.md -- name: Use-case and Deployment Scenarios - href: use-case-and-deployment-scenarios.md - name: Production Users and Use Cases for Akka.NET href: akka-users.md - name: Getting Started diff --git a/docs/articles/intro/use-case-and-deployment-scenarios.md b/docs/articles/intro/use-case-and-deployment-scenarios.md deleted file mode 100644 index 2596243d856..00000000000 --- a/docs/articles/intro/use-case-and-deployment-scenarios.md +++ /dev/null @@ -1,224 +0,0 @@ ---- -uid: use-case-and-deployment-scenarios -title: Use-case and Deployment Scenarios ---- -# Use-Case and Deployment Scenarios - -## Console Application - -```csharp -PM> install-package Akka -PM> install-package Akka.Remote -``` - -```csharp -using Akka; -using Akka.Actor; -using Akka.Configuration; - -namespace Foo.Bar -{ - class Program - { - static void Main(string[] args) - { - //configure remoting for localhost:8081 - var fluentConfig = FluentConfig.Begin() - .StartRemotingOn("localhost", 8081) - .Build(); - - using (var system = ActorSystem.Create("my-actor-server", fluentConfig)) - { - //start two services - var service1= system.ActorOf("service1"); - var service2 = system.ActorOf("service2"); - Console.ReadKey(); - } - } - } -} -``` - -## ASP.NET - -### Creating the Akka.NET Resources - -Hosting inside an ASP.NET application is easy. The `Global.asax` would be the designated place to start. - -```csharp -public class MvcApplication : System.Web.HttpApplication -{ - protected static ActorSystem ActorSystem; - //here you would store your toplevel actor-refs - protected static IActorRef MyActor; - - protected void Application_Start() - { - //your mvc config. Does not really matter if you initialize - //your actor system before or after - - ActorSystem = ActorSystem.Create("app"); - //here you would register your toplevel actors - MyActor = ActorSystem.ActorOf(); - } -} -``` - -As you can see the main point here is keeping a static reference to your `ActorSystem` . This ensures it won't be accidentally garbage collected and gets disposed and created with the start and stop events of your web application. - -> [!WARNING] -> When you are hosting inside of `IIS`, the application pool your app lives in could be stopped and started at the whim of `IIS`. This means your `ActorSystem` could be stopped at any given time. - -Typically you use a very lightweight `ActorSystem` inside ASP.NET applications, and offload heavy-duty work to a separate Windows Service via Akka.Remote / Akka.Cluster. - -### Interaction Between Controllers and Akka.NET - -In the sample below, we use an Web API Controller: - -```csharp -public class SomeController : ApiController -{ - //expose your endpoint as async - public async Task Post(SomeRequest someRequest) - { - //send a message based on your incoming arguments to one of the actors you created earlier - //and await the result by sending the message to `Ask` - var result = await MvcApplication.MyActor.Ask(new SomeMessage(someRequest.SomeArg1,someRequest.SomeArg2)); - return result; - } -} -``` - -## Windows Service - -For windows service deployment it is recommended to use [TopShelf](http://topshelf.readthedocs.org/en/latest/index.html) -to build your Windows Services. It radically simplifies hosting Windows Services. - -The quickest way to get started with TopShelf is by creating a Console Application. Which would look like this: - -### Program.cs - -```csharp -using Akka.Actor; -using Topshelf; - -class Program -{ - static void Main(string[] args) - { - HostFactory.Run(x => - { - x.Service(s => - { - s.ConstructUsing(n => new MyActorService()); - s.WhenStarted(service => service.Start()); - s.WhenStopped(service => service.Stop()); - //continue and restart directives are also available - }); - - x.RunAsLocalSystem(); - x.UseAssemblyInfoForServiceInfo(); - }); - } -} - -/// -/// This class acts as an interface between your application and TopShelf -/// -public class MyActorService -{ - private ActorSystem mySystem; - - public void Start() - { - //this is where you setup your actor system and other things - mySystem = ActorSystem.Create("MySystem"); - } - - public async void Stop() - { - //this is where you stop your actor system - await mySystem.Terminate(); - } -} -``` - -The above example is the simplest way imaginable, but there are other styles of integration with TopShelf that give you more control. - -Installing with Topshelf is as easy as calling `myConsoleApp.exe install` on the command line. - -For all the options and settings check out their [docs](http://topshelf.readthedocs.org/en/latest/index.html). - -## Azure PaaS Worker Role - -The following sample assumes that you have created a new Azure PaaS Cloud Service that contains a single -empty Worker Role. The Cloud Service project templates are added to Visual Studio by installing the -[Azure .Net SDK](http://azure.microsoft.com/en-gb/downloads/). - -The Worker Role implementation can be tested locally using the Azure Compute Emulator before deploying to the cloud. The MSDN Azure article ["Using Emulator Express to Run and Debug a Cloud Service Locally"](https://msdn.microsoft.com/en-us/library/azure/dn339018.aspx) describes this in more detail. - -The Azure PaaS Worker Role implementation is very similar to the [Windows Service](#windows-service). -The quickest way to get started with Akka.Net is to create a simple Worker Role which invokes the top-level -user-actor in the RunAsync() method, as follows: - -### WorkerRole.cs - -```csharp -using Akka.Actor; - -namespace MyActorWorkerRole -{ - public class WorkerRole : RoleEntryPoint - { - private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false); - - private ActorSystem _actorSystem; - - public override bool OnStart() - { - // Setup the Actor System - _actorSystem = ActorSystem.Create("MySystem"); - - return (base.OnStart()); - } - - public override void OnStop() - { - this.cancellationTokenSource.Cancel(); - this.runCompleteEvent.WaitOne(); - - // Shutdown the Actor System - _actorSystem.Shutdown(); - - base.OnStop(); - } - - public override void Run() - { - try - { - this.RunAsync(this.cancellationTokenSource.Token).Wait(); - } - finally - { - this.runCompleteEvent.Set(); - } - } - - private async Task RunAsync(CancellationToken cancellationToken) - { - // Create an instance to the top-level user Actor - var workerRoleActor = _actorSystem.ActorOf("WorkerRole"); - - // Send a message to the Actor - workerRoleActor.Tell(new WorkerRoleMessage("Hello World!")); - - while (!cancellationToken.IsCancellationRequested) - { - await Task.Delay(1000, cancellationToken); - } - } - } -} -``` diff --git a/docs/articles/networking/io.html b/docs/articles/networking/io.html new file mode 100644 index 00000000000..df1398e5f75 --- /dev/null +++ b/docs/articles/networking/io.html @@ -0,0 +1,10 @@ + + + + Akka.IO + + + +

This page has been moved to Akka.IO.

+ + diff --git a/docs/articles/networking/serialization.html b/docs/articles/networking/serialization.html new file mode 100644 index 00000000000..95e9b2705f6 --- /dev/null +++ b/docs/articles/networking/serialization.html @@ -0,0 +1,10 @@ + + + + Serialization + + + +

This page has been moved to Serialization.

+ + diff --git a/docs/articles/persistence/custom-persistence-provider.md b/docs/articles/persistence/custom-persistence-provider.md index 6af7469f9cc..519c3208611 100644 --- a/docs/articles/persistence/custom-persistence-provider.md +++ b/docs/articles/persistence/custom-persistence-provider.md @@ -444,7 +444,7 @@ There are two conventions that needs to be implemented when you extend `IExtensi * It is strongly recommended to create a static `Get` method that returns an instance of this class. This method is responsible for registering the extension with the Akka.NET extension manager and instantiates a new instance for users to use. -[!code-csharp[Get-Instance](../../../src/examples/Akka.Persistence.Custom/SqlitePersistence.cs?name=Get-Instance "Extension static Get method")] +[!code-csharp[GetInstance](../../../src/examples/Akka.Persistence.Custom/SqlitePersistence.cs?name=GetInstance "Extension static Get method")] ### Extending `ExtensionIdProvider` diff --git a/docs/articles/persistence/toc.yml b/docs/articles/persistence/toc.yml new file mode 100644 index 00000000000..b53b3a549b3 --- /dev/null +++ b/docs/articles/persistence/toc.yml @@ -0,0 +1,22 @@ +- name: Architecture + href: architecture.md +- name: Event sourcing + href: event-sourcing.md +- name: Persistence Views + href: persistent-views.md +- name: Snapshots + href: snapshots.md +- name: At-Least-Once Delivery + href: at-least-once-delivery.md +- name: Event Adapters + href: event-adapters.md +- name: Persistent FSM + href: persistent-fsm.md +- name: Storage plugins + href: storage-plugins.md +- name: Custom serialization + href: custom-serialization.md +- name: Persistence Query + href: persistence-query.md +- name: Persistence Testing + href: persistence-testing.md \ No newline at end of file diff --git a/docs/articles/remoting/performance.md b/docs/articles/remoting/performance.md index 581e019b96b..37da9f7e9aa 100644 --- a/docs/articles/remoting/performance.md +++ b/docs/articles/remoting/performance.md @@ -114,7 +114,7 @@ You can achieve similar results for your application. #### Optimizing Batches for Your Use Case -To take advantage of I/O batching in DotNetty, you need to [tailor the following Akka.Remote configuration values to your use case](../../configuration/modules/akka.remote.md): +To take advantage of I/O batching in DotNetty, you need to [tailor the following Akka.Remote configuration values to your use case](../../articles/configuration/modules/akka.remote.md): ```hocon akka.remote.dot-netty.tcp{ diff --git a/docs/articles/remoting/toc.yml b/docs/articles/remoting/toc.yml new file mode 100644 index 00000000000..d240355e8fe --- /dev/null +++ b/docs/articles/remoting/toc.yml @@ -0,0 +1,14 @@ +- name: Overview + href: index.md +- name: Transports + href: transports.md +- name: Remote Messaging + href: messaging.md +- name: Deploying Actors Remotely + href: deployment.md +- name: Detecting & Handling Network Failures (DeathWatch) + href: deathwatch.md +- name: Network Security + href: security.md +- name: Performance Optimization + href: performance.md \ No newline at end of file diff --git a/docs/articles/serialization/serializer-codes.md b/docs/articles/serialization/codes.md similarity index 100% rename from docs/articles/serialization/serializer-codes.md rename to docs/articles/serialization/codes.md diff --git a/docs/articles/networking/serialization.md b/docs/articles/serialization/serialization.md similarity index 100% rename from docs/articles/networking/serialization.md rename to docs/articles/serialization/serialization.md diff --git a/docs/articles/serialization/toc.yml b/docs/articles/serialization/toc.yml index 3e6995bf902..24d83c34727 100644 --- a/docs/articles/serialization/toc.yml +++ b/docs/articles/serialization/toc.yml @@ -1,2 +1,4 @@ -- name: Serializer Code Table - href: serializer-codes.md +- name: Overview + href: serialization.md +- name: Built-in Serializer Ids + href: codes.md diff --git a/docs/articles/streams/toc.yml b/docs/articles/streams/toc.yml new file mode 100644 index 00000000000..8bcba6800be --- /dev/null +++ b/docs/articles/streams/toc.yml @@ -0,0 +1,38 @@ +- name: Introduction + href: introduction.md +- name: Streams Quickstart Guide + href: quickstart.md +- name: Reactive Tweets + href: reactivetweets.md +- name: Design Principles behind Akka Streams + href: designprinciples.md +- name: Basics and working with Flows + href: basics.md +- name: Working with Graphs + href: workingwithgraphs.md +- name: Modularity, Composition and Hierarchy + href: modularitycomposition.md +- name: Buffers and working with rate + href: buffersandworkingwithrate.md +- name: Dynamic stream handling + href: stream-dynamic.md +- name: Custom stream processing + href: custom-stream-processing.md +- name: Integration + href: integration.md +- name: Error Handling in Streams + href: error-handling.md +- name: Working with streaming IO + href: workingwithstreamingio.md +- name: StreamRefs - Reactive Streams over the network + href: streamrefs.md +- name: Pipelining and Parallelism + href: pipeliningandparallelism.md +- name: Testing streams + href: testingstreams.md +- name: Overview of built-in stages and their semantics + href: builtinstages.md +- name: Streams Cookbook + href: cookbook.md +- name: Configuration + href: ~/articles/configuration/modules/akka.streams.md \ No newline at end of file diff --git a/docs/articles/testing/multi-node-testing-old.md b/docs/articles/testing/multi-node-testing-old.md index becdf77e7af..65aaa6b3407 100644 --- a/docs/articles/testing/multi-node-testing-old.md +++ b/docs/articles/testing/multi-node-testing-old.md @@ -52,7 +52,7 @@ The first step in creating an effective multi-node test is to define the configu The declaration of the `RoleName` properties is what the MNTR uses to determine how many nodes will be participating in this test. In this example, the test will create exactly two test processes. -The `CommonConfig` element of the [`MultiNodeConfig` implementation class](../../api/Akka.Remote.TestKit.MultiNodeConfig.md) is the common config that will be used throughout all of the nodes inside the multi-node test. So, for instance, if you want all of the nodes in your test to run [Akka.Cluster.Sharding](xref:cluster-sharding) you'd want to include those configuration elements inside the `CommonConfig` property. +The `CommonConfig` element of the [`MultiNodeConfig` implementation class](xref:Akka.Remote.TestKit.MultiNodeConfig) is the common config that will be used throughout all of the nodes inside the multi-node test. So, for instance, if you want all of the nodes in your test to run [Akka.Cluster.Sharding](xref:cluster-sharding) you'd want to include those configuration elements inside the `CommonConfig` property. #### Configuring Individual Nodes Differently diff --git a/docs/articles/testing/multi-node-testing.md b/docs/articles/testing/multi-node-testing.md index a6b4fbe3a2a..0646e90c91b 100644 --- a/docs/articles/testing/multi-node-testing.md +++ b/docs/articles/testing/multi-node-testing.md @@ -57,7 +57,7 @@ The first step in creating an effective multi-node test is to define the configu The declaration of the `RoleName` properties is what the MNTR uses to determine how many nodes will be participating in this test. In this example, the test will create exactly two test processes. -The `CommonConfig` element of the [`MultiNodeConfig` implementation class](../../api/Akka.Remote.TestKit.MultiNodeConfig.md) is the common config that will be used throughout all of the nodes inside the multi-node test. So, for instance, if you want all of the nodes in your test to run [Akka.Cluster.Sharding](xref:cluster-sharding) you'd want to include those configuration elements inside the `CommonConfig` property. +The `CommonConfig` element of the [`MultiNodeConfig` implementation class](xref:Akka.Remote.TestKit.MultiNodeConfig) is the common config that will be used throughout all of the nodes inside the multi-node test. So, for instance, if you want all of the nodes in your test to run [Akka.Cluster.Sharding](xref:cluster-sharding) you'd want to include those configuration elements inside the `CommonConfig` property. #### Configuring Individual Nodes Differently diff --git a/docs/articles/testing/toc.yml b/docs/articles/testing/toc.yml new file mode 100644 index 00000000000..249934e043c --- /dev/null +++ b/docs/articles/testing/toc.yml @@ -0,0 +1,2 @@ +- name: Multi-Node Testing Distributed Akka.NET Applications + href: multi-node-testing.md \ No newline at end of file diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml index 20fa79af59b..cfa782be1ff 100644 --- a/docs/articles/toc.yml +++ b/docs/articles/toc.yml @@ -1,181 +1,25 @@ - name: Introduction href: intro/toc.yml +- name: Deployment + href: deployment/toc.yml - name: Concepts - items: - - name: Terminology, Concepts - href: concepts/terminology.md - - name: Actor Systems - href: concepts/actor-systems.md - - name: What is an Actor? - href: concepts/actors.md - - name: Supervision and Monitoring - href: concepts/supervision.md - - name: Actor References, Paths and Addresses - href: concepts/addressing.md - - name: Location Transparency - href: concepts/location-transparency.md - - name: Message Delivery Reliability - href: concepts/message-delivery-reliability.md + href: concepts/toc.yml - name: Actors - items: - - name: ReceiveActor API - href: actors/receive-actor-api.md - - name: UntypedActor API - href: actors/untyped-actor-api.md - - name: Routers - href: actors/routers.md - - name: Dispatchers - href: actors/dispatchers.md - - name: Mailboxes - href: actors/mailboxes.md - - name: Scheduling Future and Recurring Messages - href: actors/schedulers.md - - name: Inbox - href: actors/inbox.md - - name: Finite State Machines - href: actors/finite-state-machine.md - - name: Fault Tolerance - href: actors/fault-tolerance.md - - name: Dependency Injection - href: actors/dependency-injection.md - - name: Dependency Injection Core - href: actors/di-core.md - - name: Testing Actor Systems - href: actors/testing-actor-systems.md - - name: Coordinated Shutdown - href: actors/coordinated-shutdown.md + href: actors/toc.yml - name: Persistence - items: - - name: Architecture - href: persistence/architecture.md - - name: Event sourcing - href: persistence/event-sourcing.md - - name: Persistence Views - href: persistence/persistent-views.md - - name: Snapshots - href: persistence/snapshots.md - - name: At-Least-Once Delivery - href: persistence/at-least-once-delivery.md - - name: Event Adapters - href: persistence/event-adapters.md - - name: Persistent FSM - href: persistence/persistent-fsm.md - - name: Storage plugins - href: persistence/storage-plugins.md - - name: Custom serialization - href: persistence/custom-serialization.md - - name: Writing A Custom Persistent Provider - href: persistence/custom-persistence-provider.md - - name: Persistence Query - href: persistence/persistence-query.md - - name: Persistence Testing - href: persistence/persistence-testing.md + href: persistence/toc.yml - name: Streams - items: - - name: Introduction - href: streams/introduction.md - - name: Streams Quickstart Guide - href: streams/quickstart.md - - name: Reactive Tweets - href: streams/reactivetweets.md - - name: Design Principles behind Akka Streams - href: streams/designprinciples.md - - name: Basics and working with Flows - href: streams/basics.md - - name: Working with Graphs - href: streams/workingwithgraphs.md - - name: Modularity, Composition and Hierarchy - href: streams/modularitycomposition.md - - name: Buffers and working with rate - href: streams/buffersandworkingwithrate.md - - name: Dynamic stream handling - href: streams/stream-dynamic.md - - name: Custom stream processing - href: streams/custom-stream-processing.md - - name: Integration - href: streams/integration.md - - name: Error Handling in Streams - href: streams/error-handling.md - - name: Working with streaming IO - href: streams/workingwithstreamingio.md - - name: StreamRefs - Reactive Streams over the network - href: streams/streamrefs.md - - name: Pipelining and Parallelism - href: streams/pipeliningandparallelism.md - - name: Testing streams - href: streams/testingstreams.md - - name: Overview of built-in stages and their semantics - href: streams/builtinstages.md - - name: Streams Cookbook - href: streams/cookbook.md - - name: Configuration - href: configuration/akka.streams.md -- name: Networking - items: - - name: I/O - href: networking/io.md - - name: Serialization - href: networking/serialization.md + href: streams/toc.yml - name: Remoting - items: - - name: Overview - href: remoting/index.md - - name: Transports - href: remoting/transports.md - - name: Remote Messaging - href: remoting/messaging.md - - name: Deploying Actors Remotely - href: remoting/deployment.md - - name: Detecting & Handling Network Failures (DeathWatch) - href: remoting/deathwatch.md - - name: Network Security - href: remoting/security.md - - name: Performance Optimization - href: remoting/performance.md + href: remoting/toc.yml - name: Clustering - items: - - name: Overview - href: clustering/cluster-overview.md - - name: Cluster Routing - href: clustering/cluster-routing.md - - name: Cluster Configuration - href: clustering/cluster-configuration.md - - name: Accessing the Cluster ActorSystem Extension - href: clustering/cluster-extension.md - - name: Cluster Singleton - href: clustering/cluster-singleton.md - - name: Distributed Publish Subscribe in Cluster - href: clustering/distributed-publish-subscribe.md - - name: Cluster Client - href: clustering/cluster-client.md - - name: Cluster Sharding - href: clustering/cluster-sharding.md - - name: Sharded Daemon Process - href: clustering/cluster-sharded-daemon-process.md - - name: Cluster Metrics - href: clustering/cluster-metrics.md - - name: Distributed Data - href: clustering/distributed-data.md - - name: Split Brain Resolver - href: clustering/split-brain-resolver.md + href: clustering/toc.yml - name: Testing - items: - - name: Multi-Node Testing Distributed Akka.NET Applications - href: testing/multi-node-testing.md + href: testing/toc.yml - name: Service Discovery href: discovery/toc.yml - name: Utilities - items: - - name: Event Bus - href: utilities/event-bus.md - - name: Logging - href: utilities/logging.md - - name: Serilog - href: utilities/serilog.md - - name: Scheduler - href: utilities/scheduler.md - - name: Circuit Breaker - href: utilities/circuit-breaker.md + href: utilities/toc.yml - name: Configurations href: configuration/toc.yml - name: Serialization diff --git a/docs/articles/utilities/toc.yml b/docs/articles/utilities/toc.yml new file mode 100644 index 00000000000..18201417c51 --- /dev/null +++ b/docs/articles/utilities/toc.yml @@ -0,0 +1,10 @@ +- name: Event Bus + href: event-bus.md +- name: Logging + href: logging.md +- name: Serilog + href: serilog.md +- name: Scheduler + href: scheduler.md +- name: Circuit Breaker + href: circuit-breaker.md \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 02a4b558eb3..9c669f1ed00 100644 --- a/docs/index.md +++ b/docs/index.md @@ -30,7 +30,7 @@ h2:before{

Follow our tutorial and build your first Akka.NET application today.

diff --git a/src/Akka.sln b/src/Akka.sln index 40209ecfae8..a06864509d8 100644 --- a/src/Akka.sln +++ b/src/Akka.sln @@ -250,23 +250,37 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sharding", "Sharding", "{CF EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShoppingCart", "examples\Cluster\ClusterSharding\ShoppingCart\ShoppingCart.csproj", "{9F8CCEDE-E871-473A-9C9A-DDDDE69E5130}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HelloAkka", "HelloAkka", "{1B513910-F5AF-4E8B-AECD-A9BBB4B1E3FA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloWorld", "examples\HelloAkka\HelloWorld\HelloWorld.csproj", "{01DB0B0E-E458-426A-A705-6D159EDFBFC1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.AspNetCore", "examples\AspNetCore\Akka.AspNetCore\Akka.AspNetCore.csproj", "{46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HeadlessService", "HeadlessService", "{59E80734-202F-4907-BAFE-55E0554FBE3C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AkkaHeadlesssService", "examples\HeadlessService\AkkaHeadlesssService\AkkaHeadlesssService.csproj", "{073AE790-6D6E-434C-8AFA-26866B532A53}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Persistence", "Persistence", "{A640E39E-F45C-4AE9-AABF-7F1432D357DA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Persistence.Custom", "examples\Akka.Persistence.Custom\Akka.Persistence.Custom.csproj", "{B9091AE9-B257-4D3A-A9BC-EE2B43AF57A8}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Persistence.Custom.Tests", "examples\Akka.Persistence.Custom.Tests\Akka.Persistence.Custom.Tests.csproj", "{F6C974B8-48F8-41C7-95AC-3CFAA720E0E4}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WIndowsService", "WIndowsService", "{71AE3A57-B735-45CA-A936-836DAC390B76}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AkkaWindowsService", "examples\WindowsService\AkkaWindowsService\AkkaWindowsService.csproj", "{81851799-31D0-4C61-B862-6E75EB7FBF3D}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Cluster.Cpu.Benchmark", "benchmark\Akka.Cluster.Cpu.Benchmark\Akka.Cluster.Cpu.Benchmark.csproj", "{6FA94D22-9369-4A60-BBC1-764CA68F4ED1}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PublishSubscribe", "PublishSubscribe", "{51C887A7-7A69-43E2-9BE2-17016E2D8476}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleSubscriber", "examples\Cluster\PublishSubscribe\SamplePublishSubscribe\SampleSubscriber.csproj", "{F8BA242A-AEA4-46AC-BA08-C11C5624256D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SamplePublisher", "examples\Cluster\PublishSubscribe\SamplePublisher\SamplePublisher.csproj", "{3AF9CF2D-6C20-4CFE-BC0A-CA48F60E2724}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SamplePublisher", "examples\Cluster\PublishSubscribe\SamplePublisher\SamplePublisher.csproj", "{3AF9CF2D-6C20-4CFE-BC0A-CA48F60E2724}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleDestination", "examples\Cluster\PublishSubscribe\SampleDestination\SampleDestination.csproj", "{09CFD060-C7DC-49CC-A6C6-D3FE341A7320}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleDestination", "examples\Cluster\PublishSubscribe\SampleDestination\SampleDestination.csproj", "{09CFD060-C7DC-49CC-A6C6-D3FE341A7320}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleSender", "examples\Cluster\PublishSubscribe\SampleSender\SampleSender.csproj", "{A5392607-15B8-4869-BB20-FAAD4D09E08B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleSender", "examples\Cluster\PublishSubscribe\SampleSender\SampleSender.csproj", "{A5392607-15B8-4869-BB20-FAAD4D09E08B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1183,6 +1197,42 @@ Global {9F8CCEDE-E871-473A-9C9A-DDDDE69E5130}.Release|x64.Build.0 = Release|Any CPU {9F8CCEDE-E871-473A-9C9A-DDDDE69E5130}.Release|x86.ActiveCfg = Release|Any CPU {9F8CCEDE-E871-473A-9C9A-DDDDE69E5130}.Release|x86.Build.0 = Release|Any CPU + {01DB0B0E-E458-426A-A705-6D159EDFBFC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01DB0B0E-E458-426A-A705-6D159EDFBFC1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01DB0B0E-E458-426A-A705-6D159EDFBFC1}.Debug|x64.ActiveCfg = Debug|Any CPU + {01DB0B0E-E458-426A-A705-6D159EDFBFC1}.Debug|x64.Build.0 = Debug|Any CPU + {01DB0B0E-E458-426A-A705-6D159EDFBFC1}.Debug|x86.ActiveCfg = Debug|Any CPU + {01DB0B0E-E458-426A-A705-6D159EDFBFC1}.Debug|x86.Build.0 = Debug|Any CPU + {01DB0B0E-E458-426A-A705-6D159EDFBFC1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01DB0B0E-E458-426A-A705-6D159EDFBFC1}.Release|Any CPU.Build.0 = Release|Any CPU + {01DB0B0E-E458-426A-A705-6D159EDFBFC1}.Release|x64.ActiveCfg = Release|Any CPU + {01DB0B0E-E458-426A-A705-6D159EDFBFC1}.Release|x64.Build.0 = Release|Any CPU + {01DB0B0E-E458-426A-A705-6D159EDFBFC1}.Release|x86.ActiveCfg = Release|Any CPU + {01DB0B0E-E458-426A-A705-6D159EDFBFC1}.Release|x86.Build.0 = Release|Any CPU + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}.Debug|x64.ActiveCfg = Debug|Any CPU + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}.Debug|x64.Build.0 = Debug|Any CPU + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}.Debug|x86.ActiveCfg = Debug|Any CPU + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}.Debug|x86.Build.0 = Debug|Any CPU + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}.Release|Any CPU.Build.0 = Release|Any CPU + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}.Release|x64.ActiveCfg = Release|Any CPU + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}.Release|x64.Build.0 = Release|Any CPU + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}.Release|x86.ActiveCfg = Release|Any CPU + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C}.Release|x86.Build.0 = Release|Any CPU + {073AE790-6D6E-434C-8AFA-26866B532A53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {073AE790-6D6E-434C-8AFA-26866B532A53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {073AE790-6D6E-434C-8AFA-26866B532A53}.Debug|x64.ActiveCfg = Debug|Any CPU + {073AE790-6D6E-434C-8AFA-26866B532A53}.Debug|x64.Build.0 = Debug|Any CPU + {073AE790-6D6E-434C-8AFA-26866B532A53}.Debug|x86.ActiveCfg = Debug|Any CPU + {073AE790-6D6E-434C-8AFA-26866B532A53}.Debug|x86.Build.0 = Debug|Any CPU + {073AE790-6D6E-434C-8AFA-26866B532A53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {073AE790-6D6E-434C-8AFA-26866B532A53}.Release|Any CPU.Build.0 = Release|Any CPU + {073AE790-6D6E-434C-8AFA-26866B532A53}.Release|x64.ActiveCfg = Release|Any CPU + {073AE790-6D6E-434C-8AFA-26866B532A53}.Release|x64.Build.0 = Release|Any CPU + {073AE790-6D6E-434C-8AFA-26866B532A53}.Release|x86.ActiveCfg = Release|Any CPU + {073AE790-6D6E-434C-8AFA-26866B532A53}.Release|x86.Build.0 = Release|Any CPU {B9091AE9-B257-4D3A-A9BC-EE2B43AF57A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B9091AE9-B257-4D3A-A9BC-EE2B43AF57A8}.Debug|Any CPU.Build.0 = Debug|Any CPU {B9091AE9-B257-4D3A-A9BC-EE2B43AF57A8}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1207,6 +1257,18 @@ Global {F6C974B8-48F8-41C7-95AC-3CFAA720E0E4}.Release|x64.Build.0 = Release|Any CPU {F6C974B8-48F8-41C7-95AC-3CFAA720E0E4}.Release|x86.ActiveCfg = Release|Any CPU {F6C974B8-48F8-41C7-95AC-3CFAA720E0E4}.Release|x86.Build.0 = Release|Any CPU + {81851799-31D0-4C61-B862-6E75EB7FBF3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81851799-31D0-4C61-B862-6E75EB7FBF3D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81851799-31D0-4C61-B862-6E75EB7FBF3D}.Debug|x64.ActiveCfg = Debug|Any CPU + {81851799-31D0-4C61-B862-6E75EB7FBF3D}.Debug|x64.Build.0 = Debug|Any CPU + {81851799-31D0-4C61-B862-6E75EB7FBF3D}.Debug|x86.ActiveCfg = Debug|Any CPU + {81851799-31D0-4C61-B862-6E75EB7FBF3D}.Debug|x86.Build.0 = Debug|Any CPU + {81851799-31D0-4C61-B862-6E75EB7FBF3D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81851799-31D0-4C61-B862-6E75EB7FBF3D}.Release|Any CPU.Build.0 = Release|Any CPU + {81851799-31D0-4C61-B862-6E75EB7FBF3D}.Release|x64.ActiveCfg = Release|Any CPU + {81851799-31D0-4C61-B862-6E75EB7FBF3D}.Release|x64.Build.0 = Release|Any CPU + {81851799-31D0-4C61-B862-6E75EB7FBF3D}.Release|x86.ActiveCfg = Release|Any CPU + {81851799-31D0-4C61-B862-6E75EB7FBF3D}.Release|x86.Build.0 = Release|Any CPU {6FA94D22-9369-4A60-BBC1-764CA68F4ED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6FA94D22-9369-4A60-BBC1-764CA68F4ED1}.Debug|Any CPU.Build.0 = Debug|Any CPU {6FA94D22-9369-4A60-BBC1-764CA68F4ED1}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1377,9 +1439,16 @@ Global {3CEBB0AE-6A88-4C32-A1D3-A8FB1E7E236B} = {73108242-625A-4D7B-AA09-63375DBAE464} {CF2704C6-3A27-450F-9C60-081341D88C1D} = {C50E1A9E-820C-4E75-AE39-6F96A99AC4A7} {9F8CCEDE-E871-473A-9C9A-DDDDE69E5130} = {CF2704C6-3A27-450F-9C60-081341D88C1D} + {1B513910-F5AF-4E8B-AECD-A9BBB4B1E3FA} = {D3AF8295-AEB5-4324-AA82-FCC0014AC310} + {01DB0B0E-E458-426A-A705-6D159EDFBFC1} = {1B513910-F5AF-4E8B-AECD-A9BBB4B1E3FA} + {46C7EC90-9B2E-40B3-BE02-1CDDD3C2768C} = {162F5991-EA57-4221-9B70-F9B6FEC18036} + {59E80734-202F-4907-BAFE-55E0554FBE3C} = {D3AF8295-AEB5-4324-AA82-FCC0014AC310} + {073AE790-6D6E-434C-8AFA-26866B532A53} = {59E80734-202F-4907-BAFE-55E0554FBE3C} {A640E39E-F45C-4AE9-AABF-7F1432D357DA} = {D3AF8295-AEB5-4324-AA82-FCC0014AC310} {B9091AE9-B257-4D3A-A9BC-EE2B43AF57A8} = {A640E39E-F45C-4AE9-AABF-7F1432D357DA} {F6C974B8-48F8-41C7-95AC-3CFAA720E0E4} = {A640E39E-F45C-4AE9-AABF-7F1432D357DA} + {71AE3A57-B735-45CA-A936-836DAC390B76} = {D3AF8295-AEB5-4324-AA82-FCC0014AC310} + {81851799-31D0-4C61-B862-6E75EB7FBF3D} = {71AE3A57-B735-45CA-A936-836DAC390B76} {6FA94D22-9369-4A60-BBC1-764CA68F4ED1} = {73108242-625A-4D7B-AA09-63375DBAE464} {51C887A7-7A69-43E2-9BE2-17016E2D8476} = {C50E1A9E-820C-4E75-AE39-6F96A99AC4A7} {F8BA242A-AEA4-46AC-BA08-C11C5624256D} = {51C887A7-7A69-43E2-9BE2-17016E2D8476} diff --git a/src/common.props b/src/common.props index 2c00bf9f5bc..d1a21be2d00 100644 --- a/src/common.props +++ b/src/common.props @@ -2,7 +2,7 @@ Copyright © 2013-2021 Akka.NET Team Akka.NET Team - 1.4.33 + 1.4.34 akkalogo.png https://github.com/akkadotnet/akka.net https://github.com/akkadotnet/akka.net/blob/master/LICENSE @@ -22,6 +22,7 @@ net471 netstandard2.0 5.10.3 + true 2.16.3 2.0.3 4.7.0 diff --git a/src/contrib/cluster/Akka.DistributedData.Tests/ReplicatorSpecs.cs b/src/contrib/cluster/Akka.DistributedData.Tests/ReplicatorSpecs.cs index bd52b70f7eb..f6b8e5424d0 100644 --- a/src/contrib/cluster/Akka.DistributedData.Tests/ReplicatorSpecs.cs +++ b/src/contrib/cluster/Akka.DistributedData.Tests/ReplicatorSpecs.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Numerics; using System.Text; @@ -33,7 +34,11 @@ static ReplicatorSpecs() SpecConfig = ConfigurationFactory.ParseString(@" akka.loglevel = DEBUG akka.actor.provider = cluster - akka.remote.dot-netty.tcp.port = 0") + akka.remote.dot-netty.tcp.port = 0 + akka.remote.dot-netty.tcp.send-buffer-size = 2000000 + akka.remote.dot-netty.tcp.receive-buffer-size = 2000000 + akka.remote.dot-netty.tcp.maximum-frame-size = 1000000 + akka.cluster.sharding.updating-state-timeout = 15s") .WithFallback(DistributedData.DefaultConfig()); } @@ -503,7 +508,7 @@ public async Task Bugfix_4367_ORMultiValueDictionary_WithValueDeltas_DeltaGroup_ // Scenario 1 - add 1 entry with multiple values to all nodes var keyA = "A"; - var entryA = ImmutableHashSet.Empty.Add("1").Add("2"); + var entryA = ImmutableHashSet.Empty.Add("1").Add("2").Add("3").Add("4"); await AwaitAssertAsync(async () => { var m1 = await _replicator1.Ask(Dsl.Update( _keyJ, @@ -539,7 +544,7 @@ await AwaitAssertAsync(async () => }); // Scenario 3 - modify set with existing items in it - var entryA1 = entryA.Add("4"); + var entryA1 = entryA.Add("6"); ORMultiValueDictionary node2EntriesBCA = null; await AwaitAssertAsync(async () => { @@ -551,10 +556,10 @@ await AwaitAssertAsync(async () => s => s.SetItems(Cluster.Cluster.Get(_sys1), keyA, entryA1))); node2EntriesBCA = changedProbe2.ExpectMsg(g => Equals(g.Key, _keyJ)).Get(_keyJ); - node2EntriesBCA.Entries["A"].Should().BeEquivalentTo("1", "2", "4"); + node2EntriesBCA.Entries["A"].Should().BeEquivalentTo("1", "2", "3", "4", "6"); var node3EntriesBCA = changedProbe3.ExpectMsg(g => Equals(g.Key, _keyJ)).Get(_keyJ).Entries; - node3EntriesBCA["A"].Should().BeEquivalentTo("1", "2", "4"); + node3EntriesBCA["A"].Should().BeEquivalentTo("1", "2", "3", "4", "6"); }); // Trigger update from Node2 back to Node 1 @@ -570,11 +575,76 @@ await AwaitAssertAsync(async () => s => s.SetItems(Cluster.Cluster.Get(_sys2), keyA, entryA2))); var node1EntriesBCA = changedProbe1.ExpectMsg(g => Equals(g.Key, _keyJ)).Get(_keyJ).Entries; - node1EntriesBCA["A"].Should().BeEquivalentTo("1", "2", "4", "5"); + node1EntriesBCA["A"].Should().BeEquivalentTo("1", "2", "3", "4", "5", "6"); }); } + // Reproduction spec for issue #5663 + [Fact] + public async Task ORMultiValueDictionary_WithValueDeltas_LargeDataSet() + { + await InitCluster(); + + var changedProbe2 = CreateTestProbe(_sys2); + _replicator2.Tell(Dsl.Subscribe(_keyJ, changedProbe2.Ref)); + + var changedProbe3 = CreateTestProbe(_sys3); + _replicator3.Tell(Dsl.Subscribe(_keyJ, changedProbe3.Ref)); + + var messages = Enumerable.Range(0, 20000).Select(i => i.ToString()).ToList(); + + // Scenario 1 - add 1 entry with multiple values to all nodes + var keyA = "A"; + var entryA = messages.ToImmutableHashSet(); + var stopwatch = Stopwatch.StartNew(); + try + { + await _replicator1.Ask(Dsl.Update( + _keyJ, + ORMultiValueDictionary.EmptyWithValueDeltas, + new WriteMajority(_timeOut), + s => s.SetItems(Cluster.Cluster.Get(_sys1), keyA, entryA))); + } + finally + { + stopwatch.Stop(); + } + Log.Info($"Update time: {stopwatch.ElapsedMilliseconds} ms ({stopwatch.ElapsedMilliseconds / 1000.0} s)"); + + var node2EntriesA = changedProbe2.ExpectMsg(g => Equals(g.Key, _keyJ)).Get(_keyJ).Entries; + node2EntriesA[keyA].Should().BeEquivalentTo(entryA); + + var node3EntriesA = changedProbe3.ExpectMsg(g => Equals(g.Key, _keyJ)).Get(_keyJ).Entries; + node3EntriesA[keyA].Should().BeEquivalentTo(entryA); + + // Scenario 2 - modify set with existing items in it + var entryA1 = entryA.Add("999999").Add("1000000"); + + stopwatch = Stopwatch.StartNew(); + try + { + await _replicator1.Ask(Dsl.Update( + _keyJ, + ORMultiValueDictionary.EmptyWithValueDeltas, + new WriteMajority(_timeOut), + s => s.SetItems(Cluster.Cluster.Get(_sys1), keyA, entryA1))); + } + finally + { + stopwatch.Stop(); + } + Log.Info($"Single update time: {stopwatch.ElapsedMilliseconds} ms ({stopwatch.ElapsedMilliseconds / 1000.0} s)"); + + var node2Changed = changedProbe2.ExpectMsg(g => Equals(g.Key, _keyJ), TimeSpan.FromSeconds(3)); + var node2EntriesBCA = node2Changed.Get(_keyJ); + node2EntriesBCA.Entries["A"].Should().BeEquivalentTo(entryA1); + + var node3Changed = changedProbe3.ExpectMsg(g => Equals(g.Key, _keyJ), TimeSpan.FromSeconds(3)); + var node3EntriesBCA = node3Changed.Get(_keyJ).Entries; + node3EntriesBCA["A"].Should().BeEquivalentTo(entryA1); + } + protected override void BeforeTermination() { Shutdown(_sys1); diff --git a/src/contrib/cluster/Akka.DistributedData/ORSet.cs b/src/contrib/cluster/Akka.DistributedData/ORSet.cs index 4701470ced8..5c56b3be21a 100644 --- a/src/contrib/cluster/Akka.DistributedData/ORSet.cs +++ b/src/contrib/cluster/Akka.DistributedData/ORSet.cs @@ -613,12 +613,12 @@ public DeltaGroup(ImmutableArray operations) public IReplicatedData Merge(IReplicatedData other) { - if (other is AddDeltaOperation) + if (other is AddDeltaOperation thatAdd) { // merge AddDeltaOp into last AddDeltaOp in the group, if possible var last = Operations[Operations.Length - 1]; - return last is AddDeltaOperation - ? new DeltaGroup(Operations.SetItem(Operations.Length - 1, other.Merge(last))) + return last is AddDeltaOperation thisAdd + ? new DeltaGroup(Operations.SetItem(Operations.Length - 1, thisAdd.Merge(thatAdd))) : new DeltaGroup(Operations.Add(other)); } else if (other is DeltaGroup @group) diff --git a/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaAssertEqualityComparer.cs b/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaAssertEqualityComparer.cs index d754853f747..abab2df52e2 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaAssertEqualityComparer.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit/Internals/AkkaAssertEqualityComparer.cs @@ -13,7 +13,7 @@ namespace Akka.TestKit.Xunit.Internals { /// - /// Default implementation of IEqualityComparer used by the Akka's xUnit.net equality assertions. + /// Default implementation of IEqualityComparer{T} used by the Akka's xUnit.net equality assertions. /// Copy of xUnits code /// https://github.com/xunit/xunit/blob/3e6ab94ca231a6d8c86e90d6e724631a0faa33b7/src/xunit.assert/Asserts/Sdk/AssertEqualityComparer.cs /// Note! Part of internal API. Breaking changes may occur without notice. Use at own risk. diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt index c06404e477b..08805ed01dd 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt @@ -1387,7 +1387,9 @@ namespace Akka.Actor public class static PipeToSupport { public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } + public static System.Threading.Tasks.Task PipeTo(this System.Threading.Tasks.Task taskToPipe, Akka.Actor.ICanTell recipient, bool useConfigureAwait, Akka.Actor.IActorRef sender = null, System.Func success = null, System.Func failure = null) { } } public sealed class PoisonPill : Akka.Actor.IAutoReceivedMessage, Akka.Actor.IPossiblyHarmful, Akka.Event.IDeadLetterSuppression { @@ -4043,7 +4045,9 @@ namespace Akka.Pattern public Akka.Pattern.CircuitBreaker OnOpen(System.Action callback) { } public void Succeed() { } public System.Threading.Tasks.Task WithCircuitBreaker(System.Func> body) { } + public System.Threading.Tasks.Task WithCircuitBreaker(TState state, System.Func> body) { } public System.Threading.Tasks.Task WithCircuitBreaker(System.Func body) { } + public System.Threading.Tasks.Task WithCircuitBreaker(TState state, System.Func body) { } public Akka.Pattern.CircuitBreaker WithExponentialBackoff(System.TimeSpan maxResetTimeout) { } public Akka.Pattern.CircuitBreaker WithRandomFactor(double randomFactor) { } public void WithSyncCircuitBreaker(System.Action body) { } diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveStreams.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveStreams.approved.txt index c43ccd9a3da..6af1dcc0ee5 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveStreams.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveStreams.approved.txt @@ -1324,7 +1324,7 @@ namespace Akka.Streams.Dsl public static Akka.Streams.Dsl.Flow AggregateAsync(this Akka.Streams.Dsl.Flow flow, TOut2 zero, System.Func> fold) { } public static Akka.Streams.Dsl.Flow AlsoTo(this Akka.Streams.Dsl.Flow flow, Akka.Streams.IGraph, TMat> that) { } public static Akka.Streams.Dsl.Flow AlsoToMaterialized(this Akka.Streams.Dsl.Flow flow, Akka.Streams.IGraph, TMat2> that, System.Func materializerFunction) { } - public static Akka.Streams.Dsl.FlowWithContext AsFlowWithContext(this Akka.Streams.Dsl.Flow flow, System.Func collapseContext, System.Func extractContext) { } + public static Akka.Streams.Dsl.FlowWithContext AsFlowWithContext(this Akka.Streams.Dsl.Flow flow, System.Func collapseContext, System.Func extractContext) { } public static Akka.Streams.Dsl.Flow BackpressureTimeout(this Akka.Streams.Dsl.Flow flow, System.TimeSpan timeout) { } public static Akka.Streams.Dsl.Flow Batch(this Akka.Streams.Dsl.Flow flow, long max, System.Func seed, System.Func aggregate) { } public static Akka.Streams.Dsl.Flow BatchWeighted(this Akka.Streams.Dsl.Flow flow, long max, System.Func costFunction, System.Func seed, System.Func aggregate) { } @@ -1403,7 +1403,7 @@ namespace Akka.Streams.Dsl [System.ObsoleteAttribute("Use Via(GraphStage) instead. [1.1.2]")] public static Akka.Streams.Dsl.Flow Transform(this Akka.Streams.Dsl.Flow flow, System.Func> stageFactory) { } public static Akka.Streams.Dsl.Flow Watch(this Akka.Streams.Dsl.Flow flow, Akka.Actor.IActorRef actorRef) { } - public static Akka.Streams.Dsl.Flow WatchTermination(this Akka.Streams.Dsl.Flow flow, System.Func materializerFunction) + public static Akka.Streams.Dsl.Flow WatchTermination(this Akka.Streams.Dsl.Flow flow, System.Func, TMat2> materializerFunction) where TIn : TOut { } public static Akka.Streams.Dsl.Flow Where(this Akka.Streams.Dsl.Flow flow, System.Predicate predicate) { } public static Akka.Streams.Dsl.Flow WhereNot(this Akka.Streams.Dsl.Flow flow, System.Predicate predicate) { } @@ -1416,28 +1416,29 @@ namespace Akka.Streams.Dsl } public class static FlowWithContext { - public static Akka.Streams.Dsl.FlowWithContext Create() { } - public static Akka.Streams.Dsl.FlowWithContext From(Akka.Streams.Dsl.Flow, System.ValueTuple, TMat> flow) { } + public static Akka.Streams.Dsl.FlowWithContext Create() { } + public static Akka.Streams.Dsl.FlowWithContext From(Akka.Streams.Dsl.Flow, System.ValueTuple, TMat> flow) { } } public class static FlowWithContextOperations { - public static Akka.Streams.Dsl.FlowWithContext Collect(this Akka.Streams.Dsl.FlowWithContext fn) + public static Akka.Streams.Dsl.FlowWithContext Collect(this Akka.Streams.Dsl.FlowWithContext fn) where TOut2 : class { } - public static Akka.Streams.Dsl.FlowWithContext, System.Collections.Generic.IReadOnlyList, TMat> Grouped(this Akka.Streams.Dsl.FlowWithContext Select(this Akka.Streams.Dsl.FlowWithContext fn) { } - public static Akka.Streams.Dsl.FlowWithContext SelectAsync(this Akka.Streams.Dsl.FlowWithContext> fn) { } - public static Akka.Streams.Dsl.FlowWithContext SelectConcat(this Akka.Streams.Dsl.FlowWithContext> fn) { } - public static Akka.Streams.Dsl.FlowWithContext SelectContext(this Akka.Streams.Dsl.FlowWithContext mapContext) { } - public static Akka.Streams.Dsl.FlowWithContext, System.Collections.Generic.IReadOnlyList, TMat> Sliding(this Akka.Streams.Dsl.FlowWithContext StatefulSelectConcat(this Akka.Streams.Dsl.FlowWithContext>> fn) { } - public static Akka.Streams.Dsl.FlowWithContext Where(this Akka.Streams.Dsl.FlowWithContext predicate) { } - public static Akka.Streams.Dsl.FlowWithContext WhereNot(this Akka.Streams.Dsl.FlowWithContext predicate) { } - } - public sealed class FlowWithContext : Akka.Streams.GraphDelegate, System.ValueTuple>, TMat> + public static Akka.Streams.Dsl.FlowWithContext, System.Collections.Generic.IReadOnlyList, TMat> Grouped(this Akka.Streams.Dsl.FlowWithContext Select(this Akka.Streams.Dsl.FlowWithContext fn) { } + public static Akka.Streams.Dsl.FlowWithContext SelectAsync(this Akka.Streams.Dsl.FlowWithContext> fn) { } + public static Akka.Streams.Dsl.FlowWithContext SelectConcat(this Akka.Streams.Dsl.FlowWithContext> fn) { } + public static Akka.Streams.Dsl.FlowWithContext SelectContext(this Akka.Streams.Dsl.FlowWithContext mapContext) { } + public static Akka.Streams.Dsl.FlowWithContext, System.Collections.Generic.IReadOnlyList, TMat> Sliding(this Akka.Streams.Dsl.FlowWithContext StatefulSelectConcat(this Akka.Streams.Dsl.FlowWithContext>> fn) { } + public static Akka.Streams.Dsl.FlowWithContext Where(this Akka.Streams.Dsl.FlowWithContext predicate) { } + public static Akka.Streams.Dsl.FlowWithContext WhereNot(this Akka.Streams.Dsl.FlowWithContext predicate) { } + } + [Akka.Annotations.ApiMayChangeAttribute()] + public sealed class FlowWithContext : Akka.Streams.GraphDelegate, System.ValueTuple>, TMat> { public Akka.Streams.Dsl.Flow, System.ValueTuple, TMat> AsFlow() { } - public Akka.Streams.Dsl.FlowWithContext Via(Akka.Streams.IGraph, System.ValueTuple>, TMat2> viaFlow) { } - public Akka.Streams.Dsl.FlowWithContext ViaMaterialized(Akka.Streams.IGraph, System.ValueTuple>, TMat2> viaFlow, System.Func combine) { } + public Akka.Streams.Dsl.FlowWithContext Via(Akka.Streams.IGraph, System.ValueTuple>, TMat2> viaFlow) { } + public Akka.Streams.Dsl.FlowWithContext ViaMaterialized(Akka.Streams.IGraph, System.ValueTuple>, TMat2> viaFlow, System.Func combine) { } } public sealed class Flow : Akka.Streams.Dsl.IFlow, Akka.Streams.IGraph>, Akka.Streams.IGraph, TMat> { @@ -1996,7 +1997,7 @@ namespace Akka.Streams.Dsl public static Akka.Streams.Dsl.Source AggregateAsync(this Akka.Streams.Dsl.Source flow, TOut2 zero, System.Func> fold) { } public static Akka.Streams.Dsl.Source AlsoTo(this Akka.Streams.Dsl.Source flow, Akka.Streams.IGraph, TMat> that) { } public static Akka.Streams.Dsl.Source AlsoToMaterialized(this Akka.Streams.Dsl.Source flow, Akka.Streams.IGraph, TMat2> that, System.Func materializerFunction) { } - public static Akka.Streams.Dsl.SourceWithContext AsSourceWithContext(this Akka.Streams.Dsl.Source flow, System.Func fn) { } + public static Akka.Streams.Dsl.SourceWithContext AsSourceWithContext(this Akka.Streams.Dsl.Source flow, System.Func fn) { } public static Akka.Streams.Dsl.Source BackpressureTimeout(this Akka.Streams.Dsl.Source flow, System.TimeSpan timeout) { } public static Akka.Streams.Dsl.Source Batch(this Akka.Streams.Dsl.Source flow, long max, System.Func seed, System.Func aggregate) { } public static Akka.Streams.Dsl.Source BatchWeighted(this Akka.Streams.Dsl.Source flow, long max, System.Func costFunction, System.Func seed, System.Func aggregate) { } @@ -2076,7 +2077,7 @@ namespace Akka.Streams.Dsl [System.ObsoleteAttribute("Use Via(GraphStage) instead. [1.1.2]")] public static Akka.Streams.Dsl.Source Transform(this Akka.Streams.Dsl.Source flow, System.Func> stageFactory) { } public static Akka.Streams.Dsl.Source Watch(this Akka.Streams.Dsl.Source flow, Akka.Actor.IActorRef actorRef) { } - public static Akka.Streams.Dsl.Source WatchTermination(this Akka.Streams.Dsl.Source flow, System.Func materializerFunction) { } + public static Akka.Streams.Dsl.Source WatchTermination(this Akka.Streams.Dsl.Source flow, System.Func, TMat2> materializerFunction) { } public static Akka.Streams.Dsl.Source Where(this Akka.Streams.Dsl.Source flow, System.Predicate predicate) { } public static Akka.Streams.Dsl.Source WhereNot(this Akka.Streams.Dsl.Source flow, System.Predicate predicate) { } public static Akka.Streams.Dsl.Source WireTap(this Akka.Streams.Dsl.Source flow, System.Action action) { } @@ -2088,24 +2089,26 @@ namespace Akka.Streams.Dsl } public class static SourceWithContextOperations { - public static Akka.Streams.Dsl.SourceWithContext Collect(this Akka.Streams.Dsl.SourceWithContext flow, System.Func fn) + public static Akka.Streams.Dsl.SourceWithContext Collect(this Akka.Streams.Dsl.SourceWithContext flow, System.Func fn) where TOut2 : class { } - public static Akka.Streams.Dsl.SourceWithContext, System.Collections.Generic.IReadOnlyList, TMat> Grouped(this Akka.Streams.Dsl.SourceWithContext flow, int n) { } - public static Akka.Streams.Dsl.SourceWithContext Select(this Akka.Streams.Dsl.SourceWithContext flow, System.Func fn) { } - public static Akka.Streams.Dsl.SourceWithContext SelectAsync(this Akka.Streams.Dsl.SourceWithContext flow, int parallelism, System.Func> fn) { } - public static Akka.Streams.Dsl.SourceWithContext SelectConcat(this Akka.Streams.Dsl.SourceWithContext flow, System.Func> fn) { } - public static Akka.Streams.Dsl.SourceWithContext SelectContext(this Akka.Streams.Dsl.SourceWithContext flow, System.Func mapContext) { } - public static Akka.Streams.Dsl.SourceWithContext, System.Collections.Generic.IReadOnlyList, TMat> Sliding(this Akka.Streams.Dsl.SourceWithContext flow, int n, int step = 1) { } - public static Akka.Streams.Dsl.SourceWithContext StatefulSelectConcat(this Akka.Streams.Dsl.SourceWithContext flow, System.Func>> fn) { } - public static Akka.Streams.Dsl.SourceWithContext Where(this Akka.Streams.Dsl.SourceWithContext flow, System.Func predicate) { } - public static Akka.Streams.Dsl.SourceWithContext WhereNot(this Akka.Streams.Dsl.SourceWithContext flow, System.Func predicate) { } - } - public sealed class SourceWithContext : Akka.Streams.GraphDelegate>, TMat> + public static Akka.Streams.Dsl.SourceWithContext, System.Collections.Generic.IReadOnlyList, TMat> Grouped(this Akka.Streams.Dsl.SourceWithContext flow, int n) { } + public static Akka.Streams.Dsl.SourceWithContext Select(this Akka.Streams.Dsl.SourceWithContext flow, System.Func fn) { } + public static Akka.Streams.Dsl.SourceWithContext SelectAsync(this Akka.Streams.Dsl.SourceWithContext flow, int parallelism, System.Func> fn) { } + public static Akka.Streams.Dsl.SourceWithContext SelectConcat(this Akka.Streams.Dsl.SourceWithContext flow, System.Func> fn) { } + public static Akka.Streams.Dsl.SourceWithContext SelectContext(this Akka.Streams.Dsl.SourceWithContext flow, System.Func mapContext) { } + public static Akka.Streams.Dsl.SourceWithContext, System.Collections.Generic.IReadOnlyList, TMat> Sliding(this Akka.Streams.Dsl.SourceWithContext flow, int n, int step = 1) { } + public static Akka.Streams.Dsl.SourceWithContext StatefulSelectConcat(this Akka.Streams.Dsl.SourceWithContext flow, System.Func>> fn) { } + public static Akka.Streams.Dsl.SourceWithContext Where(this Akka.Streams.Dsl.SourceWithContext flow, System.Func predicate) { } + public static Akka.Streams.Dsl.SourceWithContext WhereNot(this Akka.Streams.Dsl.SourceWithContext flow, System.Func predicate) { } + } + [Akka.Annotations.ApiMayChangeAttribute()] + public sealed class SourceWithContext : Akka.Streams.GraphDelegate>, TMat> { public SourceWithContext(Akka.Streams.Dsl.Source, TMat> source) { } public Akka.Streams.Dsl.Source, TMat> AsSource() { } - public Akka.Streams.Dsl.SourceWithContext Via(Akka.Streams.IGraph, System.ValueTuple>, TMat2> viaFlow) { } - public Akka.Streams.Dsl.SourceWithContext ViaMaterialized(Akka.Streams.IGraph, System.ValueTuple>, TMat2> viaFlow, System.Func combine) { } + public TMat2 RunWith(Akka.Streams.IGraph>, TMat2> sink, Akka.Streams.IMaterializer materializer) { } + public Akka.Streams.Dsl.SourceWithContext Via(Akka.Streams.IGraph, System.ValueTuple>, TMat2> viaFlow) { } + public Akka.Streams.Dsl.SourceWithContext ViaMaterialized(Akka.Streams.IGraph, System.ValueTuple>, TMat2> viaFlow, System.Func combine) { } } public sealed class Source : Akka.Streams.Dsl.IFlow, Akka.Streams.IGraph>, Akka.Streams.IGraph, TMat> { @@ -2246,7 +2249,7 @@ namespace Akka.Streams.Dsl public static Akka.Streams.Dsl.SubFlow Throttle(this Akka.Streams.Dsl.SubFlow flow, int cost, System.TimeSpan per, int maximumBurst, System.Func calculateCost, Akka.Streams.ThrottleMode mode) { } [System.ObsoleteAttribute("Use Via(GraphStage) instead. [1.1.2]")] public static Akka.Streams.Dsl.SubFlow Transform(this Akka.Streams.Dsl.SubFlow flow, System.Func> stageFactory) { } - public static Akka.Streams.Dsl.SubFlow WatchTermination(this Akka.Streams.Dsl.SubFlow flow, System.Func materializerFunction) { } + public static Akka.Streams.Dsl.SubFlow WatchTermination(this Akka.Streams.Dsl.SubFlow flow, System.Func, TMat2> materializerFunction) { } public static Akka.Streams.Dsl.SubFlow Where(this Akka.Streams.Dsl.SubFlow flow, System.Predicate predicate) { } public static Akka.Streams.Dsl.SubFlow WhereNot(this Akka.Streams.Dsl.SubFlow flow, System.Predicate predicate) { } public static Akka.Streams.Dsl.SubFlow WireTap(this Akka.Streams.Dsl.SubFlow flow, System.Action action) { } diff --git a/src/core/Akka.Cluster/ClusterDaemon.cs b/src/core/Akka.Cluster/ClusterDaemon.cs index d7e9211001c..338b4a97469 100644 --- a/src/core/Akka.Cluster/ClusterDaemon.cs +++ b/src/core/Akka.Cluster/ClusterDaemon.cs @@ -1797,11 +1797,10 @@ public void Quarantined(UniqueAddress node) public void ReceiveGossipStatus(GossipStatus status) { var from = status.From; - if (!LatestGossip.Overview.Reachability.IsReachable(SelfUniqueAddress, from)) + if(!LatestGossip.HasMember(from)) + _cluster.LogInfo("Ignoring received gossip status from unknown [{0}]", from); + else if (!LatestGossip.IsReachable(SelfUniqueAddress, from)) _cluster.LogInfo("Ignoring received gossip status from unreachable [{0}]", 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); @@ -1870,14 +1869,14 @@ public ReceiveGossipType ReceiveGossip(GossipEnvelope envelope) from.Address, envelope.To); return ReceiveGossipType.Ignored; } - if (!localGossip.Overview.Reachability.IsReachable(SelfUniqueAddress, from)) + if (!localGossip.HasMember(from)) { - _cluster.LogInfo("Ignoring received gossip from unreachable [{0}]", from); + _cluster.LogInfo("Ignoring received gossip from unknown [{0}]", from); return ReceiveGossipType.Ignored; } - if (localGossip.Members.All(m => !m.UniqueAddress.Equals(from))) + if (!localGossip.IsReachable(SelfUniqueAddress, from)) { - _cluster.LogInfo("Cluster Node [{0}] - Ignoring received gossip from unknown [{1}]", _cluster.SelfAddress, from); + _cluster.LogInfo("Ignoring received gossip from unreachable [{0}]", from); return ReceiveGossipType.Ignored; } if (remoteGossip.Members.All(m => !m.UniqueAddress.Equals(SelfUniqueAddress))) diff --git a/src/core/Akka.Cluster/Gossip.cs b/src/core/Akka.Cluster/Gossip.cs index 09104aef329..0ea5bb947f5 100644 --- a/src/core/Akka.Cluster/Gossip.cs +++ b/src/core/Akka.Cluster/Gossip.cs @@ -294,6 +294,21 @@ public bool IsSingletonCluster get { return _members.Count == 1; } } + /// + /// Returns `true` if should be able to reach + /// based on the unreachability data. + /// + /// TBD + /// TBD + public bool IsReachable(UniqueAddress fromAddress, UniqueAddress toAddress) + { + if (!HasMember(toAddress)) + return false; + + // as it looks for specific unreachable entires for the node pair we don't have to filter on team + return Overview.Reachability.IsReachable(fromAddress, toAddress); + } + /// /// TBD /// @@ -415,7 +430,7 @@ public GossipOverview Copy(ImmutableHashSet seen = null, Reachabi /// TBD /// public Reachability Reachability { get { return _reachability; } } - + /// public override string ToString() => $"GossipOverview(seen=[{string.Join(", ", Seen)}], reachability={Reachability})"; } diff --git a/src/core/Akka.Docs.Tests/Actors/FiniteStateMachine/ExampleFSMActor.cs b/src/core/Akka.Docs.Tests/Actors/FiniteStateMachine/ExampleFSMActor.cs index 7a41fdcc390..3cef746e5a7 100644 --- a/src/core/Akka.Docs.Tests/Actors/FiniteStateMachine/ExampleFSMActor.cs +++ b/src/core/Akka.Docs.Tests/Actors/FiniteStateMachine/ExampleFSMActor.cs @@ -19,7 +19,9 @@ public class ExampleFSMActor : FSM public ExampleFSMActor() { + // StartWith(State.Idle, Uninitialized.Instance); + // #region FSMHandlers When(State.Idle, state => diff --git a/src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs b/src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs index d37b95eca24..addc3cf20d7 100644 --- a/src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs +++ b/src/core/Akka.Docs.Tests/Configuration/ConfigurationSample.cs @@ -204,19 +204,5 @@ // // // } -// [Fact] -// public void UnsolvableSubstitutionWillThrowSample() -// { -// // -// // This substitution will throw an exception because it is a required substitution, -// // and we can not resolve it, even when checking for environment variables. -// var hoconString = "from_environment = ${MY_ENV_VAR}"; - -// Assert.Throws(() => -// { -// Config config = hoconString; -// }).Message.Should().StartWith("Unresolved substitution"); -// // -// } // } //} diff --git a/src/core/Akka.Docs.Tests/Configuration/SerializationSetupDocSpec.cs b/src/core/Akka.Docs.Tests/Configuration/SerializationSetupDocSpec.cs index 3a576c738f0..35935250a1c 100644 --- a/src/core/Akka.Docs.Tests/Configuration/SerializationSetupDocSpec.cs +++ b/src/core/Akka.Docs.Tests/Configuration/SerializationSetupDocSpec.cs @@ -125,5 +125,45 @@ public void SerializationSetupShouldWorkAsExpected() } // } + [Fact] + public void UnsolvableSubstitutionWillThrowSample() + { + // + // This substitution will throw an exception because it is a required substitution, + // and we can not resolve it. + + var hoconString = "from_environment = ${does-not-exist}"; + + Assert.Throws(() => + { + Config config = hoconString; + }).Message.Should().StartWith("Unresolved substitution"); + // + } + [Fact] + public void StringSubstitutionSample() + { + // + // ${string_bar} will be substituted by 'bar' and concatenated with `foo` into `foobar` + var hoconString = @" + string_bar = bar + string_foobar = foo${string_bar} + "; + var config = ConfigurationFactory.ParseString(hoconString); // This config uses ConfigurationFactory as a helper + config.GetString("string_foobar").Should().Be("foobar"); + // + } + [Fact] + public void ArraySubstitutionSample() + { + // + // ${a} will be substituted by the array [1, 2] and concatenated with [3, 4] to create [1, 2, 3, 4] + var hoconString = @" + a = [1,2] + b = ${a} [3, 4]"; + Config config = hoconString; // This Config uses implicit conversion from string directly into a Config object + (new[] { 1, 2, 3, 4 }).Should().BeEquivalentTo(config.GetIntList("b")); + // + } } } diff --git a/src/core/Akka.FSharp.Tests/Akka.FSharp.Tests.fsproj b/src/core/Akka.FSharp.Tests/Akka.FSharp.Tests.fsproj index d402f14c23c..47f166fb6d6 100644 --- a/src/core/Akka.FSharp.Tests/Akka.FSharp.Tests.fsproj +++ b/src/core/Akka.FSharp.Tests/Akka.FSharp.Tests.fsproj @@ -4,7 +4,8 @@ Akka.FSharp.Tests - $(NetFrameworkTestVersion);$(NetTestVersion);$(NetCoreTestVersion) + $(NetCoreTestVersion) + false false diff --git a/src/core/Akka.FSharp/Akka.FSharp.fsproj b/src/core/Akka.FSharp/Akka.FSharp.fsproj index 5a079c947dd..0c10acc9887 100644 --- a/src/core/Akka.FSharp/Akka.FSharp.fsproj +++ b/src/core/Akka.FSharp/Akka.FSharp.fsproj @@ -7,6 +7,7 @@ $(NetStandardLibVersion) $(AkkaPackageTags);F#;fsharp true + false diff --git a/src/core/Akka.Persistence.FSharp/Akka.Persistence.FSharp.fsproj b/src/core/Akka.Persistence.FSharp/Akka.Persistence.FSharp.fsproj index 1795d2283c8..ed20e078b15 100644 --- a/src/core/Akka.Persistence.FSharp/Akka.Persistence.FSharp.fsproj +++ b/src/core/Akka.Persistence.FSharp/Akka.Persistence.FSharp.fsproj @@ -7,6 +7,7 @@ $(NetStandardLibVersion) akka;actors;actor model;Akka;concurrency;F#;Fsharp;persistence;eventsource true + false diff --git a/src/core/Akka.Remote.Tests/Serialization/SerializationTransportInformationSpec.cs b/src/core/Akka.Remote.Tests/Serialization/SerializationTransportInformationSpec.cs index c4c154588c5..ad786f096cf 100644 --- a/src/core/Akka.Remote.Tests/Serialization/SerializationTransportInformationSpec.cs +++ b/src/core/Akka.Remote.Tests/Serialization/SerializationTransportInformationSpec.cs @@ -181,6 +181,7 @@ public void Serialization_of_ActorRef_in_remote_message_must_resolve_Address() protected override void AfterAll() { + base.AfterAll(); Shutdown(System2, verifySystemShutdown: true); } } diff --git a/src/core/Akka.Streams.Tests/Dsl/FlowWithContextSpec.cs b/src/core/Akka.Streams.Tests/Dsl/FlowWithContextSpec.cs new file mode 100644 index 00000000000..8a9d0858fb4 --- /dev/null +++ b/src/core/Akka.Streams.Tests/Dsl/FlowWithContextSpec.cs @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Linq; +using Akka.Streams.Dsl; +using Akka.Streams.TestKit; +using Akka.TestKit; +using Xunit; +using Xunit.Abstractions; + +namespace Akka.Streams.Tests.Dsl +{ + public class FlowWithContextSpec : AkkaSpec + { + private ActorMaterializer Materializer { get; } + + public FlowWithContextSpec(ITestOutputHelper helper) : base(helper) + { + var settings = ActorMaterializerSettings.Create(Sys); + Materializer = ActorMaterializer.Create(Sys, settings); + } + + [Fact] + public void A_FlowWithContext_must_get_created_from_FlowAsFlowWithContext() + { + var flow = Flow.Create().Select(m => m.Copy(data: m.Data + "z")); + var flowWithContext = flow.AsFlowWithContext((m, o) => new Message(m.Data, o), m => m.Offset); + + Source.From(new[] { new Message("a", 1L) }) + .AsSourceWithContext(m => m.Offset) + .Via(flowWithContext) + .AsSource() + .RunWith(this.SinkProbe<(Message, long)>(), Materializer) + .Request(1) + .ExpectNext((new Message("az", 1L), 1L)) + .ExpectComplete(); + } + } + + sealed class Message : IEquatable + { + public string Data { get; } + public long Offset { get; } + + public Message(string data, long offset) + { + Data = data; + Offset = offset; + } + + public Message Copy(string data = null, long? offset = null) => new Message(data ?? Data, offset ?? Offset); + + public bool Equals(Message other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Data, other.Data) && Offset == other.Offset; + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + return obj is Message other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return ((Data != null ? Data.GetHashCode() : 0) * 397) ^ Offset.GetHashCode(); + } + } + } +} diff --git a/src/core/Akka.Streams.Tests/Dsl/SourceWithContextSpec.cs b/src/core/Akka.Streams.Tests/Dsl/SourceWithContextSpec.cs index fae6584584a..60c7a849443 100644 --- a/src/core/Akka.Streams.Tests/Dsl/SourceWithContextSpec.cs +++ b/src/core/Akka.Streams.Tests/Dsl/SourceWithContextSpec.cs @@ -126,17 +126,14 @@ public void SourceWithContext_must_pass_through_context_using_Select_and_Where() [Fact] public void SourceWithContext_must_pass_through_context_using_FlowWithContext() { - var flowWithContext = FlowWithContext.Create(); - - var msg = new Message("a", 1); + var flowWithContext = FlowWithContext.Create(); var sink = this.CreateSubscriberProbe<(string, long)>(); - Source.From(new[] { msg }) + Source.From(new[] { new Message("a", 1L) }) .AsSourceWithContext(x => x.Offset) .Select(x => x.Data) .Via(flowWithContext.Select(s => s + "b")) - .AsSource() .RunWith(Sink.FromSubscriber(sink), Materializer); var sub = sink.ExpectSubscription(); diff --git a/src/core/Akka.Streams.Tests/Dsl/WithContextUsageSpec.cs b/src/core/Akka.Streams.Tests/Dsl/WithContextUsageSpec.cs index ba9d243562b..42dc1c8b1cf 100644 --- a/src/core/Akka.Streams.Tests/Dsl/WithContextUsageSpec.cs +++ b/src/core/Akka.Streams.Tests/Dsl/WithContextUsageSpec.cs @@ -214,7 +214,7 @@ private static CommittableMessage[] GenInput(int start, int end) => new CommittableMessage(new Record(GenKey(i), GenValue(i)), new CommittableOffsetImpl(i))) .ToArray(); - private static SourceWithContext CreateSourceWithContext( + private static SourceWithContext CreateSourceWithContext( params CommittableMessage[] messages) => CommittableConsumer.CommittableSource(messages) .AsSourceWithContext(m => new Offset(m.Offset.Offset)) diff --git a/src/core/Akka.Streams/Dsl/FlowOperations.cs b/src/core/Akka.Streams/Dsl/FlowOperations.cs index 6288f8ddfa5..b8359b7beea 100644 --- a/src/core/Akka.Streams/Dsl/FlowOperations.cs +++ b/src/core/Akka.Streams/Dsl/FlowOperations.cs @@ -1916,7 +1916,7 @@ public static Flow DivertTo(this FlowTBD /// TBD /// TBD - public static Flow WatchTermination(this Flow flow, Func materializerFunction) where TIn : TOut + public static Flow WatchTermination(this Flow flow, Func, TMat2> materializerFunction) where TIn : TOut { return (Flow)InternalFlowOperations.WatchTermination(flow, materializerFunction); } @@ -2399,7 +2399,7 @@ public static Flow Watch(this Flow flow, IActor /// Resulting context type /// Materialized value type /// Type of passed flow elements - public static FlowWithContext AsFlowWithContext( + public static FlowWithContext AsFlowWithContext( this Flow flow, Func collapseContext, Func extractContext) diff --git a/src/core/Akka.Streams/Dsl/FlowWithContext.cs b/src/core/Akka.Streams/Dsl/FlowWithContext.cs index 5edc6d8665f..359295483b4 100644 --- a/src/core/Akka.Streams/Dsl/FlowWithContext.cs +++ b/src/core/Akka.Streams/Dsl/FlowWithContext.cs @@ -7,6 +7,7 @@ using System; using System.Runtime.CompilerServices; +using Akka.Annotations; namespace Akka.Streams.Dsl { @@ -17,17 +18,16 @@ namespace Akka.Streams.Dsl /// operations. /// /// An "empty" flow can be created by calling . - /// - /// API MAY CHANGE - /// - public sealed class FlowWithContext + /// + [ApiMayChange] + public sealed class FlowWithContext : GraphDelegate, TMat> { - internal FlowWithContext(Flow<(TIn, TCtxIn), (TOut, TCtxOut), TMat> flow) + internal FlowWithContext(Flow<(TIn, TCtxIn), (TOut, TCtxOut), TMat> flow) : base(flow) { } - + /// /// Transform this flow by the regular flow. The given flow must support manual context propagation by /// taking and producing tuples of (data, context). @@ -35,10 +35,10 @@ internal FlowWithContext(Flow<(TIn, TCtxIn), (TOut, TCtxOut), TMat> flow) /// This can be used as an escape hatch for operations that are not (yet) provided with automatic /// context propagation here. /// - public FlowWithContext Via( + public FlowWithContext Via( IGraph, TMat2> viaFlow) => FlowWithContext.From(Flow.FromGraph(Inner).Via(viaFlow)); - + /// /// Transform this flow by the regular flow. The given flow must support manual context propagation by /// taking and producing tuples of (data, context). @@ -49,7 +49,7 @@ public FlowWithContext Via /// The function is used to compose the materialized values of this flow and that /// flow into the materialized value of the resulting Flow. /// - public FlowWithContext ViaMaterialized( + public FlowWithContext ViaMaterialized( IGraph, TMat2> viaFlow, Func combine) => FlowWithContext.From(Flow.FromGraph(Inner).ViaMaterialized(viaFlow, combine)); @@ -60,17 +60,17 @@ public FlowWithContext ViaMaterialized - /// Creates an "empty" that passes elements through with their context unchanged. + /// Creates an "empty" that passes elements through with their context unchanged. /// - /// /// + /// /// - public static FlowWithContext Create() + public static FlowWithContext Create() { var under = Flow.Create<(TIn, TCtx), NotUsed>(); - return new FlowWithContext(under); + return new FlowWithContext(under); } - + /// /// Creates a FlowWithContext from a regular flow that operates on a pair of `(data, context)` elements. /// @@ -81,8 +81,8 @@ public static FlowWithContext Create() /// /// /// - public static FlowWithContext From( - Flow<(TIn, TCtxIn), (TOut, TCtxOut), TMat> flow) => - new FlowWithContext(flow); + public static FlowWithContext From( + Flow<(TIn, TCtxIn), (TOut, TCtxOut), TMat> flow) => + new FlowWithContext(flow); } } diff --git a/src/core/Akka.Streams/Dsl/FlowWithContextOperations.cs b/src/core/Akka.Streams/Dsl/FlowWithContextOperations.cs index 3c13979dd52..f014543a46b 100644 --- a/src/core/Akka.Streams/Dsl/FlowWithContextOperations.cs +++ b/src/core/Akka.Streams/Dsl/FlowWithContextOperations.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Akka.Streams.Implementation.Fusing; @@ -17,20 +16,20 @@ namespace Akka.Streams.Dsl public static class FlowWithContextOperations { /// - /// Context-preserving variant of + /// Context-preserving variant of /// - public static FlowWithContext Select( - this FlowWithContext flow, Func fn) + public static FlowWithContext Select( + this FlowWithContext flow, Func fn) { var stage = new Select<(TOut, TCtx), (TOut2, TCtx)>(x => (fn(x.Item1), x.Item2)); return flow.Via(Flow.FromGraph(stage)); } /// - /// Context-preserving variant of + /// Context-preserving variant of /// - public static FlowWithContext SelectAsync( - this FlowWithContext flow, int parallelism, Func> fn) + public static FlowWithContext SelectAsync( + this FlowWithContext flow, int parallelism, Func> fn) { var stage = new SelectAsync<(TOut, TCtx), (TOut2, TCtx)>(parallelism, async x => (await fn(x.Item1), x.Item2)); @@ -38,10 +37,10 @@ public static FlowWithContext SelectAsync - /// Context-preserving variant of + /// Context-preserving variant of /// - public static FlowWithContext Collect( - this FlowWithContext flow, Func fn) where TOut2 : class + public static FlowWithContext Collect( + this FlowWithContext flow, Func fn) where TOut2 : class { var stage = new Collect<(TOut, TCtx), (TOut2, TCtx)>(func: x => { @@ -54,8 +53,8 @@ public static FlowWithContext Collect /// Context-preserving variant of /// - public static FlowWithContext Where( - this FlowWithContext flow, Func predicate) + public static FlowWithContext Where( + this FlowWithContext flow, Func predicate) { var stage = new Where<(TOut, TCtx)>(x => predicate(x.Item1)); return flow.Via(Flow.FromGraph(stage)); @@ -64,8 +63,8 @@ public static FlowWithContext Where /// Context-preserving variant of /// - public static FlowWithContext WhereNot( - this FlowWithContext flow, Func predicate) + public static FlowWithContext WhereNot( + this FlowWithContext flow, Func predicate) { var stage = new Where<(TOut, TCtx)>(x => !predicate(x.Item1)); return flow.Via(Flow.FromGraph(stage)); @@ -75,9 +74,8 @@ public static FlowWithContext WhereNot /// Each output group will be associated with a `Seq` of corresponding context elements. /// - public static FlowWithContext, IReadOnlyList, TMat> Grouped( - this FlowWithContext flow, int n) + public static FlowWithContext, IReadOnlyList, TMat> Grouped( + this FlowWithContext flow, int n) { var stage = new Grouped<(TOut, TCtx)>(n); return flow.Via(Flow.FromGraph(stage).Select(itemsWithContexts => @@ -99,9 +97,8 @@ public static FlowWithContext WhereNot /// Each output group will be associated with a `Seq` of corresponding context elements. /// - public static FlowWithContext, IReadOnlyList, TMat> Sliding( - this FlowWithContext flow, int n, int step = 1) + public static FlowWithContext, IReadOnlyList, TMat> Sliding( + this FlowWithContext flow, int n, int step = 1) { var stage = new Sliding<(TOut, TCtx)>(n, step); return flow.Via(Flow.FromGraph(stage).Select(itemsWithContexts => @@ -120,19 +117,19 @@ public static FlowWithContext WhereNot - /// Context-preserving variant of . + /// Context-preserving variant of . /// The context of the input element will be associated with each of the output elements calculated from /// this input element. /// - public static FlowWithContext SelectConcat( - this FlowWithContext flow, Func> fn) => + public static FlowWithContext SelectConcat( + this FlowWithContext flow, Func> fn) => StatefulSelectConcat(flow, () => fn); /// - /// Context-preserving variant of . + /// Context-preserving variant of . /// - public static FlowWithContext StatefulSelectConcat( - this FlowWithContext flow, Func>> fn) + public static FlowWithContext StatefulSelectConcat( + this FlowWithContext flow, Func>> fn) { var stage = new StatefulSelectMany<(TOut, TCtx), (TOut2, TCtx)>(() => { @@ -149,33 +146,32 @@ public static FlowWithContext StatefulSelectConcat /// /// Apply the given function to each context element (leaving the data elements unchanged). /// - public static FlowWithContext SelectContext( - this FlowWithContext flow, Func mapContext) + public static FlowWithContext SelectContext( + this FlowWithContext flow, Func mapContext) { var stage = new Select<(TOut, TCtx), (TOut, TCtx2)>(x => (x.Item1, mapContext(x.Item2))); return flow.Via(Flow.FromGraph(stage)); } } - - + public static class SourceWithContextOperations { /// - /// Context-preserving variant of + /// Context-preserving variant of /// - public static SourceWithContext Select( - this SourceWithContext flow, Func fn) + public static SourceWithContext Select( + this SourceWithContext flow, Func fn) { var stage = new Select<(TOut, TCtx), (TOut2, TCtx)>(x => (fn(x.Item1), x.Item2)); return flow.Via(Flow.FromGraph(stage)); } /// - /// Context-preserving variant of + /// Context-preserving variant of /// - public static SourceWithContext SelectAsync( - this SourceWithContext flow, int parallelism, Func> fn) + public static SourceWithContext SelectAsync( + this SourceWithContext flow, int parallelism, Func> fn) { var stage = new SelectAsync<(TOut, TCtx), (TOut2, TCtx)>(parallelism, async x => (await fn(x.Item1), x.Item2)); @@ -185,8 +181,8 @@ public static SourceWithContext SelectAsync /// Context-preserving variant of /// - public static SourceWithContext Collect( - this SourceWithContext flow, Func fn) where TOut2 : class + public static SourceWithContext Collect( + this SourceWithContext flow, Func fn) where TOut2 : class { var stage = new Collect<(TOut, TCtx), (TOut2, TCtx)>(func: x => { @@ -199,8 +195,8 @@ public static SourceWithContext Collect /// Context-preserving variant of /// - public static SourceWithContext Where( - this SourceWithContext flow, Func predicate) + public static SourceWithContext Where( + this SourceWithContext flow, Func predicate) { var stage = new Where<(TOut, TCtx)>(x => predicate(x.Item1)); return flow.Via(Flow.FromGraph(stage)); @@ -209,8 +205,8 @@ public static SourceWithContext Where( /// /// Context-preserving variant of /// - public static SourceWithContext WhereNot( - this SourceWithContext flow, Func predicate) + public static SourceWithContext WhereNot( + this SourceWithContext flow, Func predicate) { var stage = new Where<(TOut, TCtx)>(x => !predicate(x.Item1)); return flow.Via(Flow.FromGraph(stage)); @@ -220,8 +216,8 @@ public static SourceWithContext WhereNot( /// Context-preserving variant of /// Each output group will be associated with a `Seq` of corresponding context elements. /// - public static SourceWithContext, IReadOnlyList, TMat> Grouped( - this SourceWithContext flow, int n) + public static SourceWithContext, IReadOnlyList, TMat> Grouped( + this SourceWithContext flow, int n) { var stage = new Grouped<(TOut, TCtx)>(n); return flow.Via(Flow.FromGraph(stage).Select(itemsWithContexts => @@ -243,8 +239,8 @@ public static SourceWithContext, IReadOnlyList, TMat> /// Context-preserving variant of /// Each output group will be associated with a `Seq` of corresponding context elements. /// - public static SourceWithContext, IReadOnlyList, TMat> Sliding( - this SourceWithContext flow, int n, int step = 1) + public static SourceWithContext, IReadOnlyList, TMat> Sliding( + this SourceWithContext flow, int n, int step = 1) { var stage = new Sliding<(TOut, TCtx)>(n, step); return flow.Via(Flow.FromGraph(stage).Select(itemsWithContexts => @@ -267,15 +263,15 @@ public static SourceWithContext, IReadOnlyList, TMat> /// The context of the input element will be associated with each of the output elements calculated from /// this input element. /// - public static SourceWithContext SelectConcat( - this SourceWithContext flow, Func> fn) => + public static SourceWithContext SelectConcat( + this SourceWithContext flow, Func> fn) => StatefulSelectConcat(flow, () => fn); /// /// Context-preserving variant of . /// - public static SourceWithContext StatefulSelectConcat( - this SourceWithContext flow, Func>> fn) + public static SourceWithContext StatefulSelectConcat( + this SourceWithContext flow, Func>> fn) { var stage = new StatefulSelectMany<(TOut, TCtx), (TOut2, TCtx)>(() => { @@ -292,8 +288,8 @@ public static SourceWithContext StatefulSelectConcat /// Apply the given function to each context element (leaving the data elements unchanged). /// - public static SourceWithContext SelectContext( - this SourceWithContext flow, Func mapContext) + public static SourceWithContext SelectContext( + this SourceWithContext flow, Func mapContext) { var stage = new Select<(TOut, TCtx), (TOut, TCtx2)>(x => (x.Item1, mapContext(x.Item2))); diff --git a/src/core/Akka.Streams/Dsl/Internal/InternalFlowOperations.cs b/src/core/Akka.Streams/Dsl/Internal/InternalFlowOperations.cs index 69239996177..70900d5d5c2 100644 --- a/src/core/Akka.Streams/Dsl/Internal/InternalFlowOperations.cs +++ b/src/core/Akka.Streams/Dsl/Internal/InternalFlowOperations.cs @@ -2579,11 +2579,8 @@ private static IGraph, TMat> DivertToGraph(IGr /// TBD /// TBD /// TBD - public static IFlow WatchTermination(this IFlow flow, - Func materializerFunction) - { - return flow.ViaMaterialized(Fusing.GraphStages.TerminationWatcher(), materializerFunction); - } + public static IFlow WatchTermination(this IFlow flow, Func, TMat2> materializerFunction) => + flow.ViaMaterialized(Fusing.GraphStages.TerminationWatcher(), materializerFunction); /// /// Materializes to that allows monitoring of the the current flow. All events are propagated diff --git a/src/core/Akka.Streams/Dsl/SourceOperations.cs b/src/core/Akka.Streams/Dsl/SourceOperations.cs index 63ed8f95e1d..7c22a513630 100644 --- a/src/core/Akka.Streams/Dsl/SourceOperations.cs +++ b/src/core/Akka.Streams/Dsl/SourceOperations.cs @@ -1808,10 +1808,8 @@ public static Source DivertTo(this Source fl /// TBD /// TBD /// TBD - public static Source WatchTermination(this Source flow, Func materializerFunction) - { - return (Source)InternalFlowOperations.WatchTermination(flow, materializerFunction); - } + public static Source WatchTermination(this Source flow, Func, TMat2> materializerFunction) => + (Source)InternalFlowOperations.WatchTermination(flow, materializerFunction); /// /// Materializes to that allows monitoring of the the current flow. All events are propagated @@ -2277,10 +2275,9 @@ public static Source OrElseMaterialized(this So /// Type of produced events. /// Type of materialized value. /// - public static SourceWithContext AsSourceWithContext( - this Source flow, Func fn) => - new SourceWithContext(flow.Select(x => (x, fn(x)))); - + public static SourceWithContext AsSourceWithContext(this Source flow, Func fn) => + new SourceWithContext(flow.Select(x => (x, fn(x)))); + /// /// The operator fails with an if the target actor is terminated. /// diff --git a/src/core/Akka.Streams/Dsl/SourceWithContext.cs b/src/core/Akka.Streams/Dsl/SourceWithContext.cs index 96ca92ebd7c..d3e011cc2a4 100644 --- a/src/core/Akka.Streams/Dsl/SourceWithContext.cs +++ b/src/core/Akka.Streams/Dsl/SourceWithContext.cs @@ -6,7 +6,7 @@ //----------------------------------------------------------------------- using System; -using Akka.Streams.Implementation; +using Akka.Annotations; namespace Akka.Streams.Dsl { @@ -15,17 +15,15 @@ namespace Akka.Streams.Dsl /// Only a subset of common operations from [[FlowOps]] is supported. As an escape hatch you can /// use [[FlowWithContextOps.via]] to manually provide the context propagation for otherwise unsupported /// operations. - /// - /// API MAY CHANGE /// - public sealed class SourceWithContext : GraphDelegate, TMat> + [ApiMayChange] + public sealed class SourceWithContext : GraphDelegate, TMat> { public SourceWithContext(Source<(TOut, TCtx), TMat> source) - : base(source) + : base(source) { } - - + /// /// Transform this flow by the regular flow. The given flow must support manual context propagation by /// taking and producing tuples of (data, context). @@ -33,10 +31,9 @@ public SourceWithContext(Source<(TOut, TCtx), TMat> source) /// This can be used as an escape hatch for operations that are not (yet) provided with automatic /// context propagation here. /// - public SourceWithContext Via( - IGraph, TMat2> viaFlow) => - new SourceWithContext(Source.FromGraph(Inner).Via(viaFlow)); - + public SourceWithContext Via(IGraph, TMat2> viaFlow) => + new SourceWithContext(Source.FromGraph(Inner).Via(viaFlow)); + /// /// Transform this flow by the regular flow. The given flow must support manual context propagation by /// taking and producing tuples of (data, context). @@ -47,16 +44,25 @@ public SourceWithContext Via( /// The function is used to compose the materialized values of this flow and that /// flow into the materialized value of the resulting Flow. /// - public SourceWithContext ViaMaterialized( + public SourceWithContext ViaMaterialized( IGraph, TMat2> viaFlow, Func combine) => - new SourceWithContext(Source.FromGraph(Inner).ViaMaterialized(viaFlow, combine)); + new SourceWithContext(Source.FromGraph(Inner).ViaMaterialized(viaFlow, combine)); + + /// + /// Connect this to a Sink and run it. The returned value is the materialized value of the Sink. + /// Note that the ActorSystem can be used as the implicit materializer parameter to use the SystemMaterializer for running the stream. + /// + /// + /// + /// + /// + public TMat2 RunWith(IGraph, TMat2> sink, IMaterializer materializer) + => Source.FromGraph(Inner).RunWith(sink, materializer); - /// ///Stops automatic context propagation from here and converts this to a regular ///stream of a pair of (data, context). /// - public Source<(TOut, TCtx), TMat> AsSource() => - Inner is Source<(TOut, TCtx), TMat> source ? source : Source.FromGraph(Inner); + public Source<(TOut, TCtx), TMat> AsSource() => Inner is Source<(TOut, TCtx), TMat> source ? source : Source.FromGraph(Inner); } } diff --git a/src/core/Akka.Streams/Dsl/SubFlowOperations.cs b/src/core/Akka.Streams/Dsl/SubFlowOperations.cs index cd51847fe67..fba0d62b5a0 100644 --- a/src/core/Akka.Streams/Dsl/SubFlowOperations.cs +++ b/src/core/Akka.Streams/Dsl/SubFlowOperations.cs @@ -1287,7 +1287,7 @@ public static SubFlow MergeMany)InternalFlowOperations.MergeMany(flow, breadth, flatten); } - + /// /// Combine the elements of current flow into a stream of tuples consisting /// of all elements paired with their index. Indices start at 0. @@ -1558,7 +1558,7 @@ public static SubFlow AlsoTo(this SubF { return (SubFlow) InternalFlowOperations.AlsoTo(flow, that); } - + /// /// /// Attaches the given to this , as a wire tap, meaning that elements that pass @@ -1571,7 +1571,7 @@ public static SubFlow AlsoTo(this SubF /// Completes when upstream completes /// Cancels when downstream cancels /// - public static SubFlow WireTap(this SubFlow flow, IGraph, TMat> that) => + public static SubFlow WireTap(this SubFlow flow, IGraph, TMat> that) => (SubFlow) InternalFlowOperations.WireTap(flow, that); /// @@ -1614,7 +1614,7 @@ public static SubFlow DivertToMaterializedTBD /// TBD /// TBD - public static SubFlow DivertTo(this SubFlow flow, IGraph, TMat> that, Func when) => + public static SubFlow DivertTo(this SubFlow flow, IGraph, TMat> that, Func when) => (SubFlow)InternalFlowOperations.DivertTo(flow, that, when); /// @@ -1633,10 +1633,8 @@ public static SubFlow DivertTo(this Su /// TBD /// TBD /// TBD - public static SubFlow WatchTermination(this SubFlow flow, Func materializerFunction) - { - return (SubFlow) InternalFlowOperations.WatchTermination(flow, materializerFunction); - } + public static SubFlow WatchTermination(this SubFlow flow, Func, TMat2> materializerFunction) => + (SubFlow)InternalFlowOperations.WatchTermination(flow, materializerFunction); /// /// Detaches upstream demand from downstream demand without detaching the diff --git a/src/core/Akka.Streams/Implementation/Fusing/GraphStages.cs b/src/core/Akka.Streams/Implementation/Fusing/GraphStages.cs index 4f32eca311a..59a3d7f51ee 100644 --- a/src/core/Akka.Streams/Implementation/Fusing/GraphStages.cs +++ b/src/core/Akka.Streams/Implementation/Fusing/GraphStages.cs @@ -36,7 +36,7 @@ public static class GraphStages /// /// TBD /// TBD - internal static GraphStageWithMaterializedValue, Task> TerminationWatcher() + internal static GraphStageWithMaterializedValue, Task> TerminationWatcher() => Implementation.Fusing.TerminationWatcher.Instance; /// @@ -289,7 +289,7 @@ public Detacher() : base("Detacher") /// TBD /// /// TBD - internal sealed class TerminationWatcher : GraphStageWithMaterializedValue, Task> + internal sealed class TerminationWatcher : GraphStageWithMaterializedValue, Task> { /// /// TBD @@ -301,10 +301,10 @@ internal sealed class TerminationWatcher : GraphStageWithMaterializedValue _stage; - private readonly TaskCompletionSource _finishPromise; + private readonly TaskCompletionSource _finishPromise; private bool _completedSignalled; - public Logic(TerminationWatcher stage, TaskCompletionSource finishPromise) : base(stage.Shape) + public Logic(TerminationWatcher stage, TaskCompletionSource finishPromise) : base(stage.Shape) { _stage = stage; _finishPromise = finishPromise; @@ -316,7 +316,7 @@ public Logic(TerminationWatcher stage, TaskCompletionSource finishPr public override void OnUpstreamFinish() { - _finishPromise.TrySetResult(NotUsed.Instance); + _finishPromise.TrySetResult(Done.Instance); _completedSignalled = true; CompleteStage(); } @@ -332,7 +332,7 @@ public override void OnUpstreamFailure(Exception e) public override void OnDownstreamFinish() { - _finishPromise.TrySetResult(NotUsed.Instance); + _finishPromise.TrySetResult(Done.Instance); _completedSignalled = true; CompleteStage(); } @@ -369,10 +369,10 @@ private TerminationWatcher() /// /// TBD /// TBD - public override ILogicAndMaterializedValue CreateLogicAndMaterializedValue(Attributes inheritedAttributes) + public override ILogicAndMaterializedValue> CreateLogicAndMaterializedValue(Attributes inheritedAttributes) { - var finishPromise = new TaskCompletionSource(); - return new LogicAndMaterializedValue(new Logic(this, finishPromise), finishPromise.Task); + var finishPromise = new TaskCompletionSource(); + return new LogicAndMaterializedValue>(new Logic(this, finishPromise), finishPromise.Task); } /// diff --git a/src/core/Akka/Actor/PipeToSupport.cs b/src/core/Akka/Actor/PipeToSupport.cs index a78a95c5d84..474ce5945e7 100644 --- a/src/core/Akka/Actor/PipeToSupport.cs +++ b/src/core/Akka/Actor/PipeToSupport.cs @@ -21,20 +21,47 @@ public static class PipeToSupport /// Pipes the output of a Task directly to the 's mailbox once /// the task completes /// - /// TBD - /// TBD - /// TBD - /// TBD - /// TBD - /// TBD + /// The type of result of the Task + /// The Task that result needs to be piped to an actor + /// The actor that will receive the Task result + /// The IActorRef that will be used as the sender of the result. Defaults to + /// A callback function that will be called on Task success. Defaults to null for no callback + /// A callback function that will be called on Task failure. Defaults to null for no callback /// A detached task - public static async Task PipeTo(this Task taskToPipe, ICanTell recipient, IActorRef sender = null, Func success = null, Func failure = null) + public static Task PipeTo( + this Task taskToPipe, + ICanTell recipient, + IActorRef sender = null, + Func success = null, + Func failure = null) + => PipeTo(taskToPipe, recipient, false, sender, success, failure); + + /// + /// Pipes the output of a Task directly to the 's mailbox once + /// the task completes + /// + /// The type of result of the Task + /// The Task that result needs to be piped to an actor + /// The actor that will receive the Task result + /// The IActorRef that will be used as the sender of the result. Defaults to + /// A callback function that will be called on Task success. Defaults to null for no callback + /// A callback function that will be called on Task failure. Defaults to null for no callback + /// Sets the taskToPipe.ConfigureAwait(bool) bool parameter + /// A detached task + public static async Task PipeTo( + this Task taskToPipe, + ICanTell recipient, + bool useConfigureAwait, + IActorRef sender = null, + Func success = null, + Func failure = null) { sender = sender ?? ActorRefs.NoSender; try { - var result = await taskToPipe.ConfigureAwait(false); + var result = await taskToPipe.ConfigureAwait(useConfigureAwait); + recipient.Tell(success != null ? success(result) : result, sender); @@ -51,19 +78,44 @@ public static async Task PipeTo(this Task taskToPipe, ICanTell recipient, /// Pipes the output of a Task directly to the 's mailbox once /// the task completes. As this task has no result, only exceptions will be piped to the /// - /// TBD - /// TBD - /// TBD - /// TBD - /// TBD - /// TBD - public static async Task PipeTo(this Task taskToPipe, ICanTell recipient, IActorRef sender = null, Func success = null, Func failure = null) + /// The Task that result needs to be piped to an actor + /// The actor that will receive the Task result + /// The IActorRef that will be used as the sender of the result. Defaults to + /// A callback function that will be called on Task success. Defaults to null for no callback + /// A callback function that will be called on Task failure. Defaults to null for no callback + /// A detached task + public static Task PipeTo( + this Task taskToPipe, + ICanTell recipient, + IActorRef sender = null, + Func success = null, + Func failure = null) + => PipeTo(taskToPipe, recipient, false, sender, success, failure); + + /// + /// Pipes the output of a Task directly to the 's mailbox once + /// the task completes. As this task has no result, only exceptions will be piped to the + /// + /// The Task that result needs to be piped to an actor + /// The actor that will receive the Task result + /// The IActorRef that will be used as the sender of the result. Defaults to + /// A callback function that will be called on Task success. Defaults to null for no callback + /// A callback function that will be called on Task failure. Defaults to null for no callback + /// Sets the taskToPipe.ConfigureAwait(bool) bool parameter + /// A detached task + public static async Task PipeTo( + this Task taskToPipe, + ICanTell recipient, + bool useConfigureAwait, + IActorRef sender = null, + Func success = null, + Func failure = null) { sender = sender ?? ActorRefs.NoSender; try - { - await taskToPipe.ConfigureAwait(false); + { + await taskToPipe.ConfigureAwait(useConfigureAwait); if (success != null) { diff --git a/src/core/Akka/Configuration/Pigeon.conf b/src/core/Akka/Configuration/Pigeon.conf index 10796a1e71e..c51231e125d 100644 --- a/src/core/Akka/Configuration/Pigeon.conf +++ b/src/core/Akka/Configuration/Pigeon.conf @@ -1016,6 +1016,7 @@ akka { # - recover=off: If the phase fails the shutdown is aborted # and depending phases will not be executed. # depends-on=[]: Run the phase after the given phases + phases { # The first pre-defined phase that applications can add tasks to. @@ -1087,6 +1088,6 @@ akka { depends-on = [before-actor-system-terminate] } } - #//#coordinated-shutdown-phases + } } diff --git a/src/core/Akka/Pattern/CircuitBreaker.cs b/src/core/Akka/Pattern/CircuitBreaker.cs index 5c3fc735f42..45d738ecb82 100644 --- a/src/core/Akka/Pattern/CircuitBreaker.cs +++ b/src/core/Akka/Pattern/CircuitBreaker.cs @@ -232,6 +232,12 @@ public Task WithCircuitBreaker(Func> body) return CurrentState.Invoke(body); } + public Task WithCircuitBreaker(TState state, + Func> body) + { + return CurrentState.InvokeState(state, body); + } + /// /// Wraps invocation of asynchronous calls that need to be protected /// @@ -241,6 +247,10 @@ public Task WithCircuitBreaker(Func body) { return CurrentState.Invoke(body); } + public Task WithCircuitBreaker(TState state, Func body) + { + return CurrentState.InvokeState(state, body); + } /// /// The failure will be recorded farther down. @@ -248,7 +258,7 @@ public Task WithCircuitBreaker(Func body) /// TBD public void WithSyncCircuitBreaker(Action body) { - var cbTask = WithCircuitBreaker(() => Task.Factory.StartNew(body)); + var cbTask = WithCircuitBreaker(body,(b) => Task.Factory.StartNew(b)); if (!cbTask.Wait(CallTimeout)) { //throw new TimeoutException( string.Format( "Execution did not complete within the time allotted {0} ms", CallTimeout.TotalMilliseconds ) ); @@ -275,7 +285,7 @@ public void WithSyncCircuitBreaker(Action body) /// or default() public T WithSyncCircuitBreaker(Func body) { - var cbTask = WithCircuitBreaker(() => Task.Factory.StartNew(body)); + var cbTask = WithCircuitBreaker(body,(b) => Task.Factory.StartNew(b)); return cbTask.Wait(CallTimeout) ? cbTask.Result : default(T); } diff --git a/src/core/Akka/Pattern/CircuitBreakerState.cs b/src/core/Akka/Pattern/CircuitBreakerState.cs index f225867e13f..c335ec9ac9e 100644 --- a/src/core/Akka/Pattern/CircuitBreakerState.cs +++ b/src/core/Akka/Pattern/CircuitBreakerState.cs @@ -51,6 +51,17 @@ private TimeSpan RemainingDuration() public override Task Invoke(Func> body) => Task.FromException(new OpenCircuitException(_breaker.LastCaughtException, RemainingDuration())); + public override Task + InvokeState(TState state, Func body) => + Task.FromException( + new OpenCircuitException(_breaker.LastCaughtException, + RemainingDuration())); + + public override Task InvokeState(TState state, + Func> body) => Task.FromException( + new OpenCircuitException(_breaker.LastCaughtException, + RemainingDuration())); + /// /// Fail-fast on any invocation /// @@ -121,6 +132,14 @@ public HalfOpen(CircuitBreaker breaker) _lock = new AtomicBoolean(); } + private void CheckState() + { + if (!_lock.CompareAndSet(true, false)) + { + throw new OpenCircuitException("Circuit breaker is half open, only one call is allowed; this call is failing fast.", _breaker.LastCaughtException, TimeSpan.Zero); + } + } + /// /// Allows a single call through, during which all other callers fail-fast. If the call fails, the breaker reopens. /// If the call succeeds, the breaker closes. @@ -130,12 +149,15 @@ public HalfOpen(CircuitBreaker breaker) /// containing result of protected call public override async Task Invoke(Func> body) { - if (!_lock.CompareAndSet(true, false)) - { - throw new OpenCircuitException("Circuit breaker is half open, only one call is allowed; this call is failing fast.", _breaker.LastCaughtException, TimeSpan.Zero); - } + CheckState(); return await CallThrough(body); } + + public override async Task InvokeState(TState state, Func> body) + { + CheckState(); + return await CallThrough(state,body); + } /// /// Allows a single call through, during which all other callers fail-fast. If the call fails, the breaker reopens. @@ -145,13 +167,17 @@ public override async Task Invoke(Func> body) /// containing result of protected call public override async Task Invoke(Func body) { - if (!_lock.CompareAndSet(true, false)) - { - throw new OpenCircuitException("Circuit breaker is half open, only one call is allowed; this call is failing fast.", _breaker.LastCaughtException, TimeSpan.Zero); - } + CheckState(); await CallThrough(body); } + public override async Task InvokeState(TState state, + Func body) + { + CheckState(); + await CallThrough(state,body); + } + /// /// Reopen breaker on failed call. /// @@ -216,6 +242,11 @@ public override Task Invoke(Func> body) return CallThrough(body); } + public override Task InvokeState(TState state, Func> body) + { + return CallThrough(state, body); + } + /// /// Implementation of invoke, which simply attempts the call /// @@ -226,6 +257,11 @@ public override Task Invoke(Func body) return CallThrough(body); } + public override Task InvokeState(TState state, Func body) + { + return CallThrough(state, body); + } + /// /// On failed call, the failure count is incremented. The count is checked against the configured maxFailures, and /// the breaker is tripped if we have reached maxFailures. diff --git a/src/core/Akka/Util/Internal/AtomicState.cs b/src/core/Akka/Util/Internal/AtomicState.cs index db50b9d2d16..81878a91ec2 100644 --- a/src/core/Akka/Util/Internal/AtomicState.cs +++ b/src/core/Akka/Util/Internal/AtomicState.cs @@ -113,6 +113,38 @@ public async Task CallThrough(Func> task) } return result; } + + public async Task CallThrough(TState state, Func> task) + { + var deadline = DateTime.UtcNow.Add(_callTimeout); + ExceptionDispatchInfo capturedException = null; + T result = default(T); + try + { + result = await task(state).ConfigureAwait(false); + } + catch (Exception ex) + { + capturedException = ExceptionDispatchInfo.Capture(ex); + } + + // Need to make sure that timeouts are reported as timeouts + if (capturedException != null) + { + CallFails(capturedException.SourceException); + capturedException.Throw(); + } + else if (DateTime.UtcNow.CompareTo(deadline) >= 0) + { + CallFails(new TimeoutException( + $"Execution did not complete within the time allotted {_callTimeout.TotalMilliseconds} ms")); + } + else + { + CallSucceeds(); + } + return result; + } /// /// Shared implementation of call across all states. Thrown exception or execution of the call beyond the allowed @@ -154,8 +186,37 @@ public async Task CallThrough(Func task) { CallSucceeds(); } + } + + public async Task CallThrough(TState state, Func task) + { + var deadline = DateTime.UtcNow.Add(_callTimeout); + ExceptionDispatchInfo capturedException = null; + try + { + await task(state).ConfigureAwait(false); + } + catch (Exception ex) + { + capturedException = ExceptionDispatchInfo.Capture(ex); + } + // Need to make sure that timeouts are reported as timeouts + if (capturedException != null) + { + CallFails(capturedException?.SourceException); + capturedException.Throw(); + } + else if (DateTime.UtcNow.CompareTo(deadline) >= 0) + { + CallFails(new TimeoutException( + $"Execution did not complete within the time allotted {_callTimeout.TotalMilliseconds} ms")); + } + else + { + CallSucceeds(); + } } /// @@ -166,6 +227,9 @@ public async Task CallThrough(Func task) /// containing result of protected call public abstract Task Invoke(Func> body); + public abstract Task InvokeState(TState state, + Func> body); + /// /// Abstract entry point for all states /// @@ -173,6 +237,10 @@ public async Task CallThrough(Func task) /// containing result of protected call public abstract Task Invoke(Func body); + public abstract Task InvokeState(TState state, + Func body); + + /// /// Invoked when call fails /// diff --git a/src/examples/Akka.Persistence.Custom/SqlitePersistence.cs b/src/examples/Akka.Persistence.Custom/SqlitePersistence.cs index 8e0f6265b65..5c81c4846f1 100644 --- a/src/examples/Akka.Persistence.Custom/SqlitePersistence.cs +++ b/src/examples/Akka.Persistence.Custom/SqlitePersistence.cs @@ -23,12 +23,12 @@ public static Config DefaultConfiguration() return ConfigurationFactory.FromResource("Akka.Persistence.Custom.sqlite.conf"); } - // + // public static SqlitePersistence Get(ActorSystem system) { return system.WithExtension(); } - // + // /// /// Journal-related settings loaded from HOCON configuration. diff --git a/src/examples/AspNetCore/Akka.AspNetCore/Akka.AspNetCore.csproj b/src/examples/AspNetCore/Akka.AspNetCore/Akka.AspNetCore.csproj new file mode 100644 index 00000000000..28cf01fa7ca --- /dev/null +++ b/src/examples/AspNetCore/Akka.AspNetCore/Akka.AspNetCore.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/src/examples/AspNetCore/Akka.AspNetCore/AkkaService.cs b/src/examples/AspNetCore/Akka.AspNetCore/AkkaService.cs new file mode 100644 index 00000000000..e75c685bb31 --- /dev/null +++ b/src/examples/AspNetCore/Akka.AspNetCore/AkkaService.cs @@ -0,0 +1,70 @@ +#region akka-aspnet-core-service + +using Akka.Actor; +using Akka.DependencyInjection; + +namespace Akka.AspNetCore +{ + public class AkkaService : IHostedService, IActorBridge + { + private ActorSystem _actorSystem; + private readonly IConfiguration _configuration; + private readonly IServiceProvider _serviceProvider; + private IActorRef _actorRef; + + private readonly IHostApplicationLifetime _applicationLifetime; + + public AkkaService(IServiceProvider serviceProvider, IHostApplicationLifetime appLifetime, IConfiguration configuration) + { + _serviceProvider = serviceProvider; + _applicationLifetime = appLifetime; + _configuration = configuration; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + + var bootstrap = BootstrapSetup.Create(); + + + // enable DI support inside this ActorSystem, if needed + var diSetup = DependencyResolverSetup.Create(_serviceProvider); + + // merge this setup (and any others) together into ActorSystemSetup + var actorSystemSetup = bootstrap.And(diSetup); + + // start ActorSystem + _actorSystem = ActorSystem.Create("akka-universe", actorSystemSetup); + + _actorRef = _actorSystem.ActorOf(Worker.Prop(), "heavy-weight-word"); + + // add a continuation task that will guarantee shutdown of application if ActorSystem terminates + //await _actorSystem.WhenTerminated.ContinueWith(tr => { + // _applicationLifetime.StopApplication(); + //}); + _actorSystem.WhenTerminated.ContinueWith(tr => { + _applicationLifetime.StopApplication(); + }); + await Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + // strictly speaking this may not be necessary - terminating the ActorSystem would also work + // but this call guarantees that the shutdown of the cluster is graceful regardless + await CoordinatedShutdown.Get(_actorSystem).Run(CoordinatedShutdown.ClrExitReason.Instance); + } + + public void Tell(object message) + { + _actorRef.Tell(message); + } + + public async Task Ask(object message) + { + return await _actorRef.Ask(message); + } + } +} + +#endregion \ No newline at end of file diff --git a/src/examples/AspNetCore/Akka.AspNetCore/Controllers/AkkaController.cs b/src/examples/AspNetCore/Akka.AspNetCore/Controllers/AkkaController.cs new file mode 100644 index 00000000000..faabe83a19c --- /dev/null +++ b/src/examples/AspNetCore/Akka.AspNetCore/Controllers/AkkaController.cs @@ -0,0 +1,38 @@ +#region akka-aspnet-core-controllers + +using Microsoft.AspNetCore.Mvc; + +// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 + +namespace Akka.AspNetCore.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class AkkaController : ControllerBase + { + private readonly ILogger _logger; + private readonly IActorBridge _bridge; + + public AkkaController(ILogger logger, IActorBridge bridge) + { + _logger = logger; + _bridge = bridge; + } + + [HttpGet] + public async Task> Get() + { + return await _bridge.Ask>("get"); + } + + // POST api/ + [HttpPost] + public void Post([FromBody] string value) + { + _bridge.Tell(value); + } + + } +} + +#endregion \ No newline at end of file diff --git a/src/examples/AspNetCore/Akka.AspNetCore/IActorBridge.cs b/src/examples/AspNetCore/Akka.AspNetCore/IActorBridge.cs new file mode 100644 index 00000000000..2d7615937f3 --- /dev/null +++ b/src/examples/AspNetCore/Akka.AspNetCore/IActorBridge.cs @@ -0,0 +1,10 @@ +#region actor-bridge +namespace Akka.AspNetCore +{ + public interface IActorBridge + { + void Tell(object message); + Task Ask(object message); + } +} +#endregion diff --git a/src/examples/AspNetCore/Akka.AspNetCore/Program.cs b/src/examples/AspNetCore/Akka.AspNetCore/Program.cs new file mode 100644 index 00000000000..7c8670978f6 --- /dev/null +++ b/src/examples/AspNetCore/Akka.AspNetCore/Program.cs @@ -0,0 +1,32 @@ +#region akka-asp-net-program +using Akka.AspNetCore; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +// creates instance of IPublicHashingService that can be accessed by ASP.NET +builder.Services.AddSingleton(); + +// starts the IHostedService, which creates the ActorSystem and actors +builder.Services.AddHostedService(sp => (AkkaService)sp.GetRequiredService()); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); +#endregion \ No newline at end of file diff --git a/src/examples/AspNetCore/Akka.AspNetCore/Worker.cs b/src/examples/AspNetCore/Akka.AspNetCore/Worker.cs new file mode 100644 index 00000000000..c4421ed3656 --- /dev/null +++ b/src/examples/AspNetCore/Akka.AspNetCore/Worker.cs @@ -0,0 +1,31 @@ +#region akka-aspnet-core-worker +using Akka.Actor; + +namespace Akka.AspNetCore +{ + public class Worker : ReceiveActor + { + public Worker() + { + ReceiveAny(message => + { + // do your work here. Call a database, call a REST API, send message to another Actor + // Whatever you wish to, the digital world is yours! + switch (message) + { + case "get": + Sender.Tell(new string[] { "value1", "value2" }); + break; + default: + // do something + break; + } + }); + } + public static Props Prop() + { + return Props.Create(); + } + } +} +#endregion diff --git a/src/examples/AspNetCore/Akka.AspNetCore/appsettings.Development.json b/src/examples/AspNetCore/Akka.AspNetCore/appsettings.Development.json new file mode 100644 index 00000000000..0c208ae9181 --- /dev/null +++ b/src/examples/AspNetCore/Akka.AspNetCore/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/examples/AspNetCore/Akka.AspNetCore/appsettings.json b/src/examples/AspNetCore/Akka.AspNetCore/appsettings.json new file mode 100644 index 00000000000..23039ed8955 --- /dev/null +++ b/src/examples/AspNetCore/Akka.AspNetCore/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/examples/HeadlessService/AkkaHeadlesssService/AkkaHeadlesssService.csproj b/src/examples/HeadlessService/AkkaHeadlesssService/AkkaHeadlesssService.csproj new file mode 100644 index 00000000000..7b5a8ad0d7f --- /dev/null +++ b/src/examples/HeadlessService/AkkaHeadlesssService/AkkaHeadlesssService.csproj @@ -0,0 +1,19 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/src/examples/HeadlessService/AkkaHeadlesssService/AkkaService.cs b/src/examples/HeadlessService/AkkaHeadlesssService/AkkaService.cs new file mode 100644 index 00000000000..9c5defb9c1a --- /dev/null +++ b/src/examples/HeadlessService/AkkaHeadlesssService/AkkaService.cs @@ -0,0 +1,53 @@ +#region headless-akka-service + +using Akka.Actor; +using Akka.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace AkkaHeadlesssService +{ + public sealed class AkkaService : IHostedService + { + private IActorRef _actorRef; + private ActorSystem _actorSystem; + private readonly IServiceProvider _serviceProvider; + + private readonly IHostApplicationLifetime _applicationLifetime; + + public AkkaService(IServiceProvider sp, IHostApplicationLifetime applicationLifetime) + { + _serviceProvider = sp; + _applicationLifetime = applicationLifetime; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + var bootstrap = BootstrapSetup.Create(); + + + // enable DI support inside this ActorSystem, if needed + var diSetup = DependencyResolverSetup.Create(_serviceProvider); + + // merge this setup (and any others) together into ActorSystemSetup + var actorSystemSetup = bootstrap.And(diSetup); + + // start ActorSystem + _actorSystem = ActorSystem.Create("headless-service", actorSystemSetup); + _actorRef = _actorSystem.ActorOf(HeadlessActor.Prop()); + // add a continuation task that will guarantee shutdown of application if ActorSystem terminates + _actorSystem.WhenTerminated.ContinueWith(tr => { + _applicationLifetime.StopApplication(); + }); + return Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + // strictly speaking this may not be necessary - terminating the ActorSystem would also work + // but this call guarantees that the shutdown of the cluster is graceful regardless + await CoordinatedShutdown.Get(_actorSystem).Run(CoordinatedShutdown.ClrExitReason.Instance); + } + } +} + +#endregion \ No newline at end of file diff --git a/src/examples/HeadlessService/AkkaHeadlesssService/HeadlessActor.cs b/src/examples/HeadlessService/AkkaHeadlesssService/HeadlessActor.cs new file mode 100644 index 00000000000..5fc00c0241c --- /dev/null +++ b/src/examples/HeadlessService/AkkaHeadlesssService/HeadlessActor.cs @@ -0,0 +1,21 @@ +#region headless-actor + +using Akka.Actor; + +namespace AkkaHeadlesssService +{ + internal class HeadlessActor : ReceiveActor + { + // Things that are possible with this actor: + // Connect to an Apache Kafka, Apache Pulsar, RabbitMQ instance or send/receive messages to/from a remote actor! + public HeadlessActor() + { + + } + public static Props Prop() + { + return Props.Create(); + } + } +} +#endregion diff --git a/src/examples/HeadlessService/AkkaHeadlesssService/Program.cs b/src/examples/HeadlessService/AkkaHeadlesssService/Program.cs new file mode 100644 index 00000000000..51ce7decad0 --- /dev/null +++ b/src/examples/HeadlessService/AkkaHeadlesssService/Program.cs @@ -0,0 +1,25 @@ +#region headless-service-program + +// See https://aka.ms/new-console-template for more information +using AkkaHeadlesssService; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +var host = new HostBuilder() + .ConfigureServices((hostContext, services) => + { + services.AddLogging(); + services.AddHostedService(); + + }) + .ConfigureLogging((hostContext, configLogging) => + { + configLogging.AddConsole(); + + }) + .UseConsoleLifetime() + .Build(); + +await host.RunAsync(); +#endregion diff --git a/src/examples/HelloAkka/HelloWorld/Greet.cs b/src/examples/HelloAkka/HelloWorld/Greet.cs new file mode 100644 index 00000000000..eb8100a6f2d --- /dev/null +++ b/src/examples/HelloAkka/HelloWorld/Greet.cs @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2022 .NET Foundation +// +//----------------------------------------------------------------------- +#region hello-world-message +namespace HelloWorld +{ + /// + /// Immutable message type that actor will respond to + /// + public class Greet + { + public string Who { get; private set; } + + public Greet(string who) + { + Who = who; + } + } +} +#endregion diff --git a/src/examples/HelloAkka/HelloWorld/GreetingActor.cs b/src/examples/HelloAkka/HelloWorld/GreetingActor.cs new file mode 100644 index 00000000000..a96d8bfcff8 --- /dev/null +++ b/src/examples/HelloAkka/HelloWorld/GreetingActor.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2022 .NET Foundation +// +//----------------------------------------------------------------------- +#region akka-hello-world-greeting + +using System; +using Akka.Actor; + +namespace HelloWorld +{ + /// + /// The actor class + /// + public class GreetingActor : ReceiveActor + { + public GreetingActor() + { + // Tell the actor to respond to the Greet message + Receive(greet => Console.WriteLine($"Hello {greet.Who}", ConsoleColor.Green)); + } + protected override void PreStart() => Console.WriteLine("Good Morning, we are awake!", ConsoleColor.Green); + + protected override void PostStop() => Console.WriteLine("Good Night, going to bed!", ConsoleColor.Red); + } +} +#endregion + diff --git a/src/examples/HelloAkka/HelloWorld/HelloWorld.csproj b/src/examples/HelloAkka/HelloWorld/HelloWorld.csproj new file mode 100644 index 00000000000..b8496f11897 --- /dev/null +++ b/src/examples/HelloAkka/HelloWorld/HelloWorld.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/src/examples/HelloAkka/HelloWorld/Program.cs b/src/examples/HelloAkka/HelloWorld/Program.cs new file mode 100644 index 00000000000..2bafa477b80 --- /dev/null +++ b/src/examples/HelloAkka/HelloWorld/Program.cs @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2022 .NET Foundation +// +//----------------------------------------------------------------------- +#region akka-hello-world-main + +using System; +using Akka.Actor; +using HelloWorld; + +namespace HelloAkka +{ + class Program + { + static void Main(string[] args) + { + // create a new actor system (a container for actors) + var system = ActorSystem.Create("the-universe"); + + // create actor and get a reference to it. + // this will be an "ActorRef", which is not a + // reference to the actual actor instance + // but rather a client or proxy to it + var greeter = system.ActorOf("greeter"); + + // send a message to the actor + greeter.Tell(new Greet("World")); + + //this is for demostration purposes + Thread.Sleep(5000); + system.Stop(greeter); + + // prevent the application from exiting before message is handled + Console.ReadLine(); + } + } +} + +#endregion diff --git a/src/examples/HelloWorld/HelloAkka/App.config b/src/examples/HelloWorld/HelloAkka/App.config index 8e15646352e..4bfa0056185 100644 --- a/src/examples/HelloWorld/HelloAkka/App.config +++ b/src/examples/HelloWorld/HelloAkka/App.config @@ -1,6 +1,6 @@ - + - + - \ No newline at end of file + diff --git a/src/examples/HelloWorld/HelloAkka/GreetingActor.cs b/src/examples/HelloWorld/HelloAkka/GreetingActor.cs index 5a764bd48ee..9996dc65cfa 100644 --- a/src/examples/HelloWorld/HelloAkka/GreetingActor.cs +++ b/src/examples/HelloWorld/HelloAkka/GreetingActor.cs @@ -4,6 +4,7 @@ // Copyright (C) 2013-2021 .NET Foundation // //----------------------------------------------------------------------- +#region akka-hello-world-greeting using System; using Akka.Actor; @@ -22,4 +23,5 @@ public GreetingActor() } } } +#endregion diff --git a/src/examples/HelloWorld/HelloAkka/HelloAkka.csproj b/src/examples/HelloWorld/HelloAkka/HelloAkka.csproj index fc80a3402ff..4dcf65cadb9 100644 --- a/src/examples/HelloWorld/HelloAkka/HelloAkka.csproj +++ b/src/examples/HelloWorld/HelloAkka/HelloAkka.csproj @@ -1,5 +1,5 @@  - + Debug @@ -9,8 +9,9 @@ Properties HelloAkka HelloAkka - v4.5 + v4.8 512 + AnyCPU diff --git a/src/examples/HelloWorld/HelloAkka/Program.cs b/src/examples/HelloWorld/HelloAkka/Program.cs index 6b7c6ba6384..d1cdb6446e6 100644 --- a/src/examples/HelloWorld/HelloAkka/Program.cs +++ b/src/examples/HelloWorld/HelloAkka/Program.cs @@ -4,6 +4,7 @@ // Copyright (C) 2013-2021 .NET Foundation // //----------------------------------------------------------------------- +#region akka-hello-world-main using System; using Akka.Actor; @@ -32,3 +33,4 @@ static void Main(string[] args) } } +#endregion diff --git a/src/examples/WindowsService/AkkaWindowsService/AkkaService.cs b/src/examples/WindowsService/AkkaWindowsService/AkkaService.cs new file mode 100644 index 00000000000..d75bb469b84 --- /dev/null +++ b/src/examples/WindowsService/AkkaWindowsService/AkkaService.cs @@ -0,0 +1,46 @@ +#region akka-windows-service +using Akka.Actor; +using Akka.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace AkkaWindowsService +{ + public sealed class AkkaService : BackgroundService + { + private readonly ILogger _logger; + private readonly JokeService _jokeService; + private readonly ActorSystem _actorSystem; + private readonly IActorRef _actorRef; + + public AkkaService(JokeService jokeService, ILogger logger, IServiceProvider serviceProvider) + { + _jokeService = jokeService; + _logger = logger; + var bootstrap = BootstrapSetup.Create(); + + + // enable DI support inside this ActorSystem, if needed + var diSetup = DependencyResolverSetup.Create(serviceProvider); + + // merge this setup (and any others) together into ActorSystemSetup + var actorSystemSetup = bootstrap.And(diSetup); + + // start ActorSystem + _actorSystem = ActorSystem.Create("akka-system", actorSystemSetup); + _actorRef = _actorSystem.ActorOf(MyActor.Prop()); + } + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + var joke = await _jokeService.GetJokeAsync(); + _logger.LogWarning(joke); + + await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + _actorRef.Tell(joke); + } + } + } +} +#endregion diff --git a/src/examples/WindowsService/AkkaWindowsService/AkkaWindowsService.csproj b/src/examples/WindowsService/AkkaWindowsService/AkkaWindowsService.csproj new file mode 100644 index 00000000000..ae82fc7b7f0 --- /dev/null +++ b/src/examples/WindowsService/AkkaWindowsService/AkkaWindowsService.csproj @@ -0,0 +1,20 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/src/examples/WindowsService/AkkaWindowsService/JokeService.cs b/src/examples/WindowsService/AkkaWindowsService/JokeService.cs new file mode 100644 index 00000000000..a8eafcd8316 --- /dev/null +++ b/src/examples/WindowsService/AkkaWindowsService/JokeService.cs @@ -0,0 +1,43 @@ +#region akka-windows-joke-service +using System.Net.Http.Json; +using System.Text.Json; + +namespace AkkaWindowsService +{ + public class JokeService + { + private readonly HttpClient _httpClient; + private readonly JsonSerializerOptions _options = new() + { + PropertyNameCaseInsensitive = true + }; + + private const string JokeApiUrl = + "https://karljoke.herokuapp.com/jokes/programming/random"; + + public JokeService(HttpClient httpClient) => _httpClient = httpClient; + + public async Task GetJokeAsync() + { + try + { + // The API returns an array with a single entry. + var jokes = await _httpClient.GetFromJsonAsync( + JokeApiUrl, _options); + + var joke = jokes?[0]; + + return joke is not null + ? $"{joke.Setup}{Environment.NewLine}{joke.Punchline}" + : "No joke here..."; + } + catch (Exception ex) + { + return $"That's not funny! {ex}"; + } + } + } + + public record Joke(int Id, string Type, string Setup, string Punchline); +} +#endregion diff --git a/src/examples/WindowsService/AkkaWindowsService/MyActor.cs b/src/examples/WindowsService/AkkaWindowsService/MyActor.cs new file mode 100644 index 00000000000..8151024c1e6 --- /dev/null +++ b/src/examples/WindowsService/AkkaWindowsService/MyActor.cs @@ -0,0 +1,19 @@ +#region akka-windows-service-actor +using Akka.Actor; +using System.Text.Json; + +namespace AkkaWindowsService +{ + internal class MyActor : ReceiveActor + { + public MyActor() + { + Receive(j => Console.WriteLine(JsonSerializer.Serialize(j, options: new JsonSerializerOptions { WriteIndented = true }))); + } + public static Props Prop() + { + return Props.Create(); + } + } +} +#endregion diff --git a/src/examples/WindowsService/AkkaWindowsService/Program.cs b/src/examples/WindowsService/AkkaWindowsService/Program.cs new file mode 100644 index 00000000000..4833a96f007 --- /dev/null +++ b/src/examples/WindowsService/AkkaWindowsService/Program.cs @@ -0,0 +1,19 @@ +#region akka-windows-service-program +using AkkaWindowsService; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +using IHost host = Host.CreateDefaultBuilder(args) + .UseWindowsService(options => + { + options.ServiceName = ".NET Joke Service"; + }) + .ConfigureServices(services => + { + services.AddHostedService(); + services.AddHttpClient(); + }) + .Build(); + +await host.RunAsync(); +#endregion \ No newline at end of file