├── docs ├── index.md ├── articles │ ├── toc.yml │ └── index.md ├── images │ ├── icon.png │ └── akkalogo.png ├── toc.yml ├── web.config └── docfx.json ├── src ├── Examples │ ├── AkkaSql │ │ ├── setup.sql │ │ ├── Dockerfile │ │ ├── docker-compose.yaml │ │ └── setup.sh │ ├── Akka.Hosting.SimpleDemo │ │ ├── Echo.cs │ │ ├── appsettings.Development.json │ │ ├── appsettings.json │ │ ├── Akka.Hosting.SimpleDemo.csproj │ │ └── Program.cs │ ├── Akka.Hosting.LoggingDemo │ │ ├── Echo.cs │ │ ├── appsettings.Development.json │ │ ├── appsettings.json │ │ ├── Akka.Hosting.LoggingDemo.csproj │ │ ├── WorkerService.cs │ │ └── Program.cs │ └── Akka.Hosting.Asp.LoggingDemo │ │ ├── Echo.cs │ │ ├── appsettings.Development.json │ │ ├── appsettings.json │ │ ├── Akka.Hosting.Asp.LoggingDemo.csproj │ │ ├── TestHealthCheck.cs │ │ └── Program.cs ├── Akka.Hosting │ ├── IDiscoveryOptions.cs │ ├── Properties │ │ └── FriendsOf.cs │ ├── Coordination │ │ └── LeaseOptionBase.cs │ ├── Logging │ │ └── LoggerFactorySetup.cs │ ├── HealthChecks │ │ ├── ActorSystemCheck.cs │ │ ├── DelegateHealthCheck.cs │ │ ├── HealthCheckAdapter.cs │ │ └── AkkaHealthCheckExtensions.cs │ ├── Akka.Hosting.csproj │ ├── Util.cs │ ├── LoggingExtensions.cs │ └── IHoconOption.cs ├── Akka.Cluster.Hosting │ ├── Properties │ │ └── FriendsOf.cs │ ├── ClusterDaemonOptions.cs │ ├── Akka.Cluster.Hosting.csproj │ ├── AkkaClusterHealthCheck.cs │ └── ClusterClientDiscoveryOptions.cs ├── Akka.Hosting.TestKit │ ├── Properties │ │ └── FriendsOf.cs │ ├── Akka.Hosting.TestKit.csproj.DotSettings │ ├── Internals │ │ ├── XUnitLoggerProvider.cs │ │ ├── TestKitLoggerFactoryLogger.cs │ │ └── XUnitLogger.cs │ └── Akka.Hosting.TestKit.csproj ├── Akka.Persistence.Hosting │ ├── Properties │ │ └── FriendsOf.cs │ ├── Akka.Persistence.Hosting.csproj │ ├── Utils.cs │ ├── Extensions.cs │ ├── README.md │ └── HealthChecks.cs ├── Akka.Remote.Hosting.Tests │ ├── Resources │ │ └── akka-validcert.pfx │ ├── xunit.runner.json │ └── Akka.Remote.Hosting.Tests.csproj ├── Akka.Hosting.Tests │ ├── xunit.runner.json │ ├── test.hocon │ ├── XUnitLoggerProvider.cs │ ├── HoconSpecs.cs │ ├── TestHelpers.cs │ ├── StartFailureSpec.cs │ ├── Akka.Hosting.Tests.csproj │ ├── SerializerRegistrationSpecs.cs │ ├── HostingExtensionsSpec.cs │ ├── XUnitLogger.cs │ ├── Logging │ │ ├── TestLogger.cs │ │ ├── LoggerConfigEnd2EndSpecs.cs │ │ ├── SerilogLoggerEnd2EndSpecs.cs │ │ └── LogMessageFormatterSpec.cs │ ├── UtilSpec.cs │ └── Bugfix208Specs.cs ├── Akka.Cluster.Hosting.Tests │ ├── xunit.runner.json │ ├── ConfigAssertionHelper.cs │ ├── XUnitLoggerProvider.cs │ ├── ClusterClientSpecs.cs │ ├── Akka.Cluster.Hosting.Tests.csproj │ ├── ClusterShardingDistributedDataSpecs.cs │ ├── ClusterSingletonWithDiSpecs.cs │ ├── XUnitLogger.cs │ └── TestHelper.cs ├── Akka.Hosting.API.Tests │ ├── xunit.runner.json │ ├── CoreApiSpec.cs │ ├── Akka.Hosting.API.Tests.csproj │ └── verify │ │ └── CoreApiSpec.ApproveRemoting.verified.txt ├── Akka.Hosting.TestKit.Tests │ ├── xunit.runner.json │ ├── TestActorRefTests │ │ ├── WrappedTerminated.cs │ │ ├── Logger.cs │ │ ├── WorkerActor.cs │ │ ├── TActorBase.cs │ │ ├── WatchAndForwardActor.cs │ │ ├── NestingActor.cs │ │ ├── SenderActor.cs │ │ ├── FsmActor.cs │ │ ├── ReplyActor.cs │ │ ├── BossActor.cs │ │ ├── PersistActor.cs │ │ └── SnapshotActor.cs │ ├── TestKitBaseTests │ │ ├── RemainingTests.cs │ │ ├── WithinTests.cs │ │ ├── AwaitAssertTests.cs │ │ ├── ExpectTests.cs │ │ ├── IgnoreMessagesTests.cs │ │ └── DilatedTests.cs │ ├── Akka.Hosting.TestKit.Tests.csproj │ ├── TestEventListenerTests │ │ ├── ConfigTests.cs │ │ ├── ForwardAllEventsTestEventListener.cs │ │ ├── CustomEventFilterTests.cs │ │ ├── DeadLettersEventFilterTests.cs │ │ └── EventFilterTestBase.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── TestKit_Config_Tests.cs │ ├── DiPropsFailTest.cs │ ├── HostingSpecSpec.cs │ ├── NoImplicitSenderSpec.cs │ ├── LoggerSpec.cs │ ├── TestFSMRefTests │ │ └── TestFSMRefSpec.cs │ └── TestPersistenceTestKistTests │ │ └── TestSnapshotStoreSpec.cs ├── Akka.Persistence.Hosting.Tests │ ├── xunit.runner.json │ ├── Akka.Persistence.Hosting.Tests.csproj │ ├── HoconKeyValidatorSpec.cs │ └── InMemoryPersistenceSpecs.cs ├── Akka.Hosting.Tests.Performance │ ├── Program.cs │ ├── Akka.Hosting.Tests.Performance.csproj │ └── CombinedPerfSpecs.cs └── Akka.Remote.Hosting │ ├── Akka.Remote.Hosting.csproj │ ├── README.md │ └── AkkaRemoteHostingExtensions.cs ├── .nuke └── parameters.json ├── global.json ├── appsettings.json ├── .github ├── dependabot.yaml └── workflows │ ├── pr_validation.yml │ └── publish_nuget.yml ├── Akka.Hosting.sln.DotSettings ├── nuget.config ├── .gitattributes ├── scripts ├── bumpVersion.ps1 └── getReleaseNotes.ps1 └── Directory.Build.props /docs/index.md: -------------------------------------------------------------------------------- 1 | # Introduction to My Project -------------------------------------------------------------------------------- /src/Examples/AkkaSql/setup.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE Akka; 2 | GO -------------------------------------------------------------------------------- /docs/articles/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Introduction 2 | href: index.md -------------------------------------------------------------------------------- /.nuke/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./build.schema.json" 3 | } -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.SimpleDemo/Echo.cs: -------------------------------------------------------------------------------- 1 | public struct Echo{} -------------------------------------------------------------------------------- /docs/articles/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Article text goes here. -------------------------------------------------------------------------------- /docs/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkadotnet/Akka.Hosting/HEAD/docs/images/icon.png -------------------------------------------------------------------------------- /docs/images/akkalogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkadotnet/Akka.Hosting/HEAD/docs/images/akkalogo.png -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "rollForward": "latestMinor", 4 | "version": "8.0.302" 5 | } 6 | } -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.LoggingDemo/Echo.cs: -------------------------------------------------------------------------------- 1 | namespace Akka.Hosting.LoggingDemo; 2 | 3 | public struct Echo{} -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.Asp.LoggingDemo/Echo.cs: -------------------------------------------------------------------------------- 1 | namespace Akka.Hosting.Asp.LoggingDemo; 2 | 3 | public struct Echo{} -------------------------------------------------------------------------------- /src/Akka.Hosting/IDiscoveryOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Akka.Hosting; 2 | 3 | public interface IDiscoveryOptions: IHoconOption 4 | { 5 | } -------------------------------------------------------------------------------- /docs/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Home 2 | href: index.md 3 | - name: Documentation 4 | href: articles/ 5 | - name: API Reference 6 | href: api/ -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting/Properties/FriendsOf.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Akka.Cluster.Hosting.Tests")] -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit/Properties/FriendsOf.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Akka.Hosting.TestKit.Tests")] -------------------------------------------------------------------------------- /src/Akka.Persistence.Hosting/Properties/FriendsOf.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Akka.Persistence.Hosting.Tests")] -------------------------------------------------------------------------------- /src/Akka.Remote.Hosting.Tests/Resources/akka-validcert.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkadotnet/Akka.Hosting/HEAD/src/Akka.Remote.Hosting.Tests/Resources/akka-validcert.pfx -------------------------------------------------------------------------------- /src/Akka.Hosting/Properties/FriendsOf.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Akka.Hosting.Tests")] 4 | [assembly: InternalsVisibleTo("Akka.Hosting.Maui")] -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.SimpleDemo/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.SimpleDemo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.github.io/schema/current/xunit.runner.schema.json", 3 | "longRunningTestSeconds": 60, 4 | "parallelizeAssembly": false, 5 | "parallelizeTestCollections": false 6 | } -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.github.io/schema/current/xunit.runner.schema.json", 3 | "longRunningTestSeconds": 60, 4 | "parallelizeAssembly": false, 5 | "parallelizeTestCollections": false 6 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.API.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.github.io/schema/current/xunit.runner.schema.json", 3 | "longRunningTestSeconds": 60, 4 | "parallelizeAssembly": false, 5 | "parallelizeTestCollections": false 6 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.github.io/schema/current/xunit.runner.schema.json", 3 | "longRunningTestSeconds": 60, 4 | "parallelizeAssembly": false, 5 | "parallelizeTestCollections": false 6 | } -------------------------------------------------------------------------------- /src/Akka.Remote.Hosting.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.github.io/schema/current/xunit.runner.schema.json", 3 | "longRunningTestSeconds": 60, 4 | "parallelizeAssembly": false, 5 | "parallelizeTestCollections": false 6 | } -------------------------------------------------------------------------------- /src/Examples/AkkaSql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/mssql/server:2019-latest 2 | 3 | COPY ./setup.sql . 4 | COPY ./setup.sh . 5 | 6 | # Grant permission on the setup script 7 | #RUN chmod +x ./setup.sh 8 | 9 | CMD /bin/bash -C './setup.sh';'bash' -------------------------------------------------------------------------------- /src/Akka.Persistence.Hosting.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.github.io/schema/current/xunit.runner.schema.json", 3 | "longRunningTestSeconds": 60, 4 | "parallelizeAssembly": false, 5 | "parallelizeTestCollections": false 6 | } -------------------------------------------------------------------------------- /docs/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.LoggingDemo/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.Asp.LoggingDemo/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "SignClient": { 3 | "AzureAd": { 4 | "AADInstance": "https://login.microsoftonline.com/", 5 | "ClientId": "", 6 | "TenantId": "" 7 | }, 8 | "Service": { 9 | "Url": "", 10 | "ResourceId": "" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | time: "11:00" 9 | 10 | - package-ecosystem: nuget 11 | directory: "/" 12 | schedule: 13 | interval: daily 14 | time: "11:00" 15 | -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.LoggingDemo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information", 7 | "Akka": "Debug" 8 | } 9 | }, 10 | "AllowedHosts": "*" 11 | } 12 | -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests.Performance/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NBench; 3 | 4 | namespace Akka.Hosting.Tests.Performance 5 | { 6 | class Program 7 | { 8 | static int Main(string[] args) 9 | { 10 | return NBenchRunner.Run(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.Asp.LoggingDemo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information", 7 | "Akka": "Debug" 8 | } 9 | }, 10 | "AllowedHosts": "*" 11 | } 12 | -------------------------------------------------------------------------------- /src/Examples/AkkaSql/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | sqlserver: 5 | image: sharding2.sql 6 | build: 7 | context: . 8 | ports: 9 | - '1533:1433' 10 | environment: 11 | ACCEPT_EULA: Y 12 | SA_PASSWORD: l0lTh1sIsOpenSource 13 | MSSQL_PID: Developer -------------------------------------------------------------------------------- /src/Examples/AkkaSql/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # start SQL Server 4 | /opt/mssql/bin/sqlservr & 5 | 6 | #wait for the SQL Server to come up 7 | sleep 15s 8 | 9 | # setup the tables 10 | echo "Connecting to SQL and creating Akka database." 11 | /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P ${SA_PASSWORD} -d master -i setup.sql 12 | 13 | sleep infinity -------------------------------------------------------------------------------- /Akka.Hosting.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/test.hocon: -------------------------------------------------------------------------------- 1 | # See petabridge.cmd configuration options here: https://cmd.petabridge.com/articles/install/host-configuration.html 2 | petabridge.cmd{ 3 | # default IP address used to listen for incoming petabridge.cmd client connections 4 | # should be a safe default as it listens on "all network interfaces". 5 | host = "0.0.0.0" 6 | 7 | # default port number used to listen for incoming petabridge.cmd client connections 8 | port = 9110 9 | } -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit/Akka.Hosting.TestKit.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.SimpleDemo/Akka.Hosting.SimpleDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(TestsNetCoreFramework) 4 | enable 5 | enable 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests.Performance/Akka.Hosting.Tests.Performance.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | $(TestsNetCoreFramework) 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Akka.Persistence.Hosting/Akka.Persistence.Hosting.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(LibraryFramework);$(NetLibraryFramework) 4 | README.md 5 | Akka.Persistence Microsoft.Extensions.Hosting support. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Akka.Remote.Hosting/Akka.Remote.Hosting.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(LibraryFramework);$(NetLibraryFramework) 4 | README.md 5 | Akka.Remote Microsoft.Extensions.Hosting support. 6 | Latest 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting.Tests/ConfigAssertionHelper.cs: -------------------------------------------------------------------------------- 1 | using Akka.Configuration; 2 | using Xunit; 3 | 4 | namespace Akka.Cluster.Hosting.Tests; 5 | 6 | public static class ConfigAssertionHelper 7 | { 8 | public static void AssertSameString(this Config first, Config second, string key) 9 | => Assert.Equal(second.GetString(key), first.GetString(key)); 10 | 11 | public static void AssertSameInt(this Config first, Config second, string key) 12 | => Assert.Equal(second.GetInt(key), first.GetInt(key)); 13 | 14 | public static void AssertSameTimeSpan(this Config first, Config second, string key) 15 | => Assert.Equal(second.GetTimeSpan(key), first.GetTimeSpan(key)); 16 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/XUnitLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Xunit.Abstractions; 3 | 4 | namespace Akka.Hosting.Tests; 5 | 6 | public class XUnitLoggerProvider : ILoggerProvider 7 | { 8 | private readonly ITestOutputHelper _helper; 9 | private readonly LogLevel _logLevel; 10 | 11 | public XUnitLoggerProvider(ITestOutputHelper helper, LogLevel logLevel) 12 | { 13 | _helper = helper; 14 | _logLevel = logLevel; 15 | } 16 | 17 | public void Dispose() 18 | { 19 | // no-op 20 | } 21 | 22 | public ILogger CreateLogger(string categoryName) 23 | { 24 | return new XUnitLogger(categoryName, _helper, _logLevel); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting/ClusterDaemonOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Akka.Cluster.Sharding; 3 | using Akka.Configuration; 4 | using Akka.Hosting; 5 | 6 | namespace Akka.Cluster.Hosting; 7 | 8 | public sealed class ClusterDaemonOptions 9 | { 10 | public TimeSpan? KeepAliveInterval { get; set; } 11 | public ClusterShardingSettings? ShardingSettings { get; set; } 12 | public string? Role { get; set; } 13 | public object? HandoffStopMessage { get; set; } 14 | 15 | internal Config ToHocon() 16 | { 17 | return KeepAliveInterval is not null 18 | ? $"akka.cluster.sharded-daemon-process.keep-alive-interval = {KeepAliveInterval.ToHocon()}" 19 | : Config.Empty; 20 | } 21 | } -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting.Tests/XUnitLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Xunit.Abstractions; 3 | 4 | namespace Akka.Cluster.Hosting.Tests; 5 | 6 | public class XUnitLoggerProvider : ILoggerProvider 7 | { 8 | private readonly ITestOutputHelper _helper; 9 | private readonly LogLevel _logLevel; 10 | 11 | public XUnitLoggerProvider(ITestOutputHelper helper, LogLevel logLevel) 12 | { 13 | _helper = helper; 14 | _logLevel = logLevel; 15 | } 16 | 17 | public void Dispose() 18 | { 19 | // no-op 20 | } 21 | 22 | public ILogger CreateLogger(string categoryName) 23 | { 24 | return new XUnitLogger(categoryName, _helper, _logLevel); 25 | } 26 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | 5 | # Custom for Visual Studio 6 | *.cs diff=csharp 7 | *.sln merge=union 8 | *.csproj merge=union 9 | *.vbproj merge=union 10 | *.fsproj merge=union 11 | *.dbproj merge=union 12 | 13 | # Standard to msysgit 14 | *.doc diff=astextplain 15 | *.DOC diff=astextplain 16 | *.docx diff=astextplain 17 | *.DOCX diff=astextplain 18 | *.dot diff=astextplain 19 | *.DOT diff=astextplain 20 | *.pdf diff=astextplain 21 | *.PDF diff=astextplain 22 | *.rtf diff=astextplain 23 | *.RTF diff=astextplain 24 | 25 | # Needed for Mono build shell script 26 | *.sh -text eol=lf 27 | 28 | # Needed for API Approvals 29 | *.txt text eol=crlf 30 | 31 | build.sh eol=lf 32 | -------------------------------------------------------------------------------- /src/Akka.Hosting/Coordination/LeaseOptionBase.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2013-2022 .NET Foundation 4 | // 5 | // ----------------------------------------------------------------------- 6 | 7 | using System; 8 | using Akka.Actor.Setup; 9 | 10 | namespace Akka.Hosting.Coordination 11 | { 12 | public abstract class LeaseOptionBase : IHoconOption 13 | { 14 | public abstract string ConfigPath { get; } 15 | public abstract Type Class { get; } 16 | public abstract void Apply(AkkaConfigurationBuilder builder, Setup? setup = null); 17 | } 18 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit/Internals/XUnitLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Xunit.Abstractions; 3 | 4 | namespace Akka.Hosting.TestKit.Internals 5 | { 6 | public class XUnitLoggerProvider : ILoggerProvider 7 | { 8 | private readonly ITestOutputHelper _helper; 9 | private readonly LogLevel _logLevel; 10 | 11 | public XUnitLoggerProvider(ITestOutputHelper helper, LogLevel logLevel) 12 | { 13 | _helper = helper; 14 | _logLevel = logLevel; 15 | } 16 | 17 | public void Dispose() 18 | { 19 | // no-op 20 | } 21 | 22 | public ILogger CreateLogger(string categoryName) 23 | { 24 | return new XUnitLogger(categoryName, _helper, _logLevel); 25 | } 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting/Akka.Cluster.Hosting.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(LibraryFramework);$(NetLibraryFramework) 4 | README.md 5 | Akka.Cluster and Akka.Cluster.Sharding Microsoft.Extensions.Hosting support. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.Asp.LoggingDemo/Akka.Hosting.Asp.LoggingDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(TestsNetCoreFramework) 4 | enable 5 | enable 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | PreserveNewest 16 | 17 | 18 | PreserveNewest 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Akka.Hosting/Logging/LoggerFactorySetup.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2022 Lightbend Inc. 4 | // Copyright (C) 2013-2022 .NET Foundation 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | using Akka.Actor.Setup; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Akka.Hosting.Logging 12 | { 13 | public class LoggerFactorySetup : Setup 14 | { 15 | public LoggerFactorySetup(ILoggerFactory loggerFactory) 16 | { 17 | LoggerFactory = loggerFactory; 18 | } 19 | 20 | public ILoggerFactory LoggerFactory { get; } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestActorRefTests/WrappedTerminated.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using Akka.Actor; 9 | 10 | namespace Akka.Hosting.TestKit.Tests.TestActorRefTests; 11 | 12 | public class WrappedTerminated 13 | { 14 | private readonly Terminated _terminated; 15 | 16 | public WrappedTerminated(Terminated terminated) 17 | { 18 | _terminated = terminated; 19 | } 20 | 21 | public Terminated Terminated { get { return _terminated; } } 22 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/HoconSpecs.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Akka.Actor; 3 | using FluentAssertions; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Xunit; 6 | using static Akka.Hosting.Tests.TestHelpers; 7 | 8 | namespace Akka.Hosting.Tests; 9 | 10 | public class HoconSpecs 11 | { 12 | [Fact] 13 | public async Task Should_load_HOCON_from_file() 14 | { 15 | // arrange 16 | using var host = await StartHost(collection => collection.AddAkka("Test", builder => 17 | { 18 | builder.AddHoconFile("test.hocon", HoconAddMode.Append); 19 | })); 20 | 21 | // act 22 | var sys = host.Services.GetRequiredService(); 23 | var hocon = sys.Settings.Config; 24 | 25 | // assert 26 | hocon.HasPath("petabridge.cmd").Should().BeTrue(); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Akka.Hosting/HealthChecks/ActorSystemCheck.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Akka.Actor; 5 | using Microsoft.Extensions.Diagnostics.HealthChecks; 6 | 7 | namespace Akka.Hosting.HealthChecks; 8 | 9 | /// 10 | /// Checks to see if the is alive or not. 11 | /// 12 | public sealed class ActorSystemLivenessCheck : IAkkaHealthCheck 13 | { 14 | public Task CheckHealthAsync(AkkaHealthCheckContext context, CancellationToken cancellationToken = default) 15 | { 16 | if (context.ActorSystem.WhenTerminated.IsCompleted) 17 | { 18 | return Task.FromResult(new HealthCheckResult(status: context.Registration.FailureStatus, description: "ActorSystem has terminated.")); 19 | } 20 | 21 | return Task.FromResult(new HealthCheckResult(HealthStatus.Healthy, description: "ActorSystem is running.")); 22 | } 23 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestActorRefTests/Logger.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using Akka.Actor; 9 | using Akka.Event; 10 | 11 | namespace Akka.Hosting.TestKit.Tests.TestActorRefTests; 12 | 13 | public class Logger : ActorBase 14 | { 15 | private int _count; 16 | private string? _msg; 17 | protected override bool Receive(object message) 18 | { 19 | if(message is Warning { Message: string } warning) 20 | { 21 | _count++; 22 | _msg = (string)warning.Message; 23 | return true; 24 | } 25 | return false; 26 | } 27 | } -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.LoggingDemo/Akka.Hosting.LoggingDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(TestsNetCoreFramework) 5 | enable 6 | enable 7 | false 8 | Exe 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | PreserveNewest 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestKitBaseTests/RemainingTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | 12 | namespace Akka.Hosting.TestKit.Tests.TestKitBaseTests; 13 | 14 | public class RemainingTests : TestKit 15 | { 16 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 17 | { 18 | 19 | } 20 | 21 | [Fact] 22 | public void Throw_if_remaining_is_called_outside_Within() 23 | { 24 | Assert.Throws(() => Remaining); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestKitBaseTests/WithinTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | 12 | namespace Akka.Hosting.TestKit.Tests.TestKitBaseTests; 13 | 14 | public class WithinTests : TestKit 15 | { 16 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 17 | { 18 | 19 | } 20 | 21 | [Fact] 22 | public void Within_should_increase_max_timeout_by_the_provided_epsilon_value() 23 | { 24 | Within(TimeSpan.FromSeconds(1), () => ExpectNoMsg(), TimeSpan.FromMilliseconds(50)); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Akka.Hosting/Akka.Hosting.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(LibraryFramework);$(NetLibraryFramework) 4 | README.md 5 | Akka.NET Microsoft.Extensions.Hosting support. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.Asp.LoggingDemo/TestHealthCheck.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Diagnostics.HealthChecks; 2 | 3 | namespace Akka.Hosting.Asp.LoggingDemo 4 | { 5 | public class TestHealth : IAkkaHealthCheck 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public TestHealth(ILogger logger) 10 | { 11 | _logger = logger; 12 | } 13 | 14 | public Task CheckHealthAsync(AkkaHealthCheckContext context, CancellationToken cancellationToken = default) 15 | { 16 | try 17 | { 18 | _logger.LogInformation("DI healthcheck is running"); 19 | return Task.FromResult(HealthCheckResult.Healthy("Test is healthy")); 20 | } 21 | catch (Exception ex) 22 | { 23 | _logger.LogError(ex, "Health check failed for test"); 24 | return Task.FromResult(HealthCheckResult.Unhealthy($"Test health check failed: {ex.Message}")); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /scripts/bumpVersion.ps1: -------------------------------------------------------------------------------- 1 | function UpdateVersionAndReleaseNotes { 2 | param ( 3 | [Parameter(Mandatory=$true)] 4 | [PSCustomObject]$ReleaseNotesResult, 5 | 6 | [Parameter(Mandatory=$true)] 7 | [string]$XmlFilePath 8 | ) 9 | 10 | # Load XML 11 | $xmlContent = New-Object XML 12 | $xmlContent.Load($XmlFilePath) 13 | 14 | # Update VersionPrefix and PackageReleaseNotes 15 | $versionPrefixElement = $xmlContent.SelectSingleNode("//VersionPrefix") 16 | $versionPrefixElement.InnerText = $ReleaseNotesResult.Version 17 | 18 | $packageReleaseNotesElement = $xmlContent.SelectSingleNode("//PackageReleaseNotes") 19 | $packageReleaseNotesElement.InnerText = $ReleaseNotesResult.ReleaseNotes 20 | 21 | # Save the updated XML 22 | $xmlContent.Save($XmlFilePath) 23 | } 24 | 25 | # Usage example: 26 | # $notes = Get-ReleaseNotes -MarkdownFile "$PSScriptRoot\RELEASE_NOTES.md" 27 | # $propsPath = Join-Path -Path (Get-Item $PSScriptRoot).Parent.FullName -ChildPath "Directory.Build.props" 28 | # UpdateVersionAndReleaseNotes -ReleaseNotesResult $notes -XmlFilePath $propsPath 29 | -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestActorRefTests/WorkerActor.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using Akka.Actor; 9 | 10 | namespace Akka.Hosting.TestKit.Tests.TestActorRefTests; 11 | 12 | public class WorkerActor : TActorBase 13 | { 14 | protected override bool ReceiveMessage(object message) 15 | { 16 | if((message as string) == "work") 17 | { 18 | Sender.Tell("workDone"); 19 | Context.Stop(Self); 20 | return true; 21 | 22 | } 23 | //TODO: case replyTo: Promise[_] ⇒ replyTo.asInstanceOf[Promise[Any]].success("complexReply") 24 | if(message is IActorRef) 25 | { 26 | ((IActorRef)message).Tell("complexReply", Self); 27 | return true; 28 | } 29 | return false; 30 | } 31 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestActorRefTests/TActorBase.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System.Threading; 9 | using Akka.Actor; 10 | 11 | namespace Akka.Hosting.TestKit.Tests.TestActorRefTests; 12 | 13 | // ReSharper disable once InconsistentNaming 14 | public abstract class TActorBase : ActorBase 15 | { 16 | protected sealed override bool Receive(object message) 17 | { 18 | var currentThread = Thread.CurrentThread; 19 | if(currentThread != TestActorRefSpec.Thread) 20 | TestActorRefSpec.OtherThread = currentThread; 21 | return ReceiveMessage(message); 22 | } 23 | 24 | protected abstract bool ReceiveMessage(object message); 25 | 26 | protected ActorSystem System 27 | { 28 | get { return ((LocalActorRef)Self).Cell.System; } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Akka.Hosting/HealthChecks/DelegateHealthCheck.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Akka.Actor; 5 | using Microsoft.Extensions.Diagnostics.HealthChecks; 6 | 7 | namespace Akka.Hosting.HealthChecks; 8 | 9 | /// 10 | /// INTERNAL API 11 | /// 12 | /// Delegate-based health check implementation. Lowest ceremony API for defining health checks on the 13 | /// . 14 | /// 15 | internal sealed class DelegateHealthCheck : IAkkaHealthCheck 16 | { 17 | private readonly Func> _healthCheckFunc; 18 | 19 | public DelegateHealthCheck(Func> healthCheckFunc) 20 | { 21 | _healthCheckFunc = healthCheckFunc ?? throw new ArgumentNullException(nameof(healthCheckFunc)); 22 | } 23 | 24 | public Task CheckHealthAsync(AkkaHealthCheckContext context, CancellationToken cancellationToken = default) 25 | { 26 | return _healthCheckFunc(context.ActorSystem, context.ActorRegistry, cancellationToken); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests.Performance/CombinedPerfSpecs.cs: -------------------------------------------------------------------------------- 1 | using NBench; 2 | 3 | namespace Akka.Hosting.Tests.Performance 4 | { 5 | /// 6 | /// Test to see gauge the impact of having multiple things to measure on a benchmark 7 | /// 8 | public class CombinedPerfSpecs 9 | { 10 | private Counter? _counter; 11 | 12 | [PerfSetup] 13 | public void Setup(BenchmarkContext context) 14 | { 15 | _counter = context.GetCounter("TestCounter"); 16 | } 17 | 18 | [PerfBenchmark(Description = "Test to gauge the impact of having multiple things to measure on a benchmark.", 19 | NumberOfIterations = 3, RunMode = RunMode.Throughput, RunTimeMilliseconds = 1000, TestMode = TestMode.Test)] 20 | [CounterThroughputAssertion("TestCounter", MustBe.GreaterThan, 10000000.0d)] 21 | [MemoryAssertion(MemoryMetric.TotalBytesAllocated, MustBe.LessThanOrEqualTo, ByteConstants.ThirtyTwoKb)] 22 | [GcTotalAssertion(GcMetric.TotalCollections, GcGeneration.Gen2, MustBe.ExactlyEqualTo, 0.0d)] 23 | public void Benchmark() 24 | { 25 | _counter!.Increment(); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/Akka.Hosting.TestKit.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(TestsNetCoreFramework) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | Always 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Akka.Hosting/HealthChecks/HealthCheckAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Akka.Actor; 4 | using Microsoft.Extensions.Diagnostics.HealthChecks; 5 | 6 | namespace Akka.Hosting.HealthChecks; 7 | 8 | /// 9 | /// INTERNAL API 10 | /// 11 | /// Adapter wrapper around the to make it API-compatible with 12 | /// 13 | internal sealed class HealthCheckAdapter : IHealthCheck 14 | { 15 | private readonly IAkkaHealthCheck _healthCheck; 16 | private readonly ActorSystem _actorSystem; 17 | public HealthCheckAdapter(IAkkaHealthCheck healthCheck, ActorSystem actorSystem) 18 | { 19 | _healthCheck = healthCheck; 20 | _actorSystem = actorSystem; 21 | } 22 | 23 | public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken()) 24 | { 25 | var akkaHealthCheckContext = new AkkaHealthCheckContext(_actorSystem) 26 | { 27 | Registration = context.Registration 28 | }; 29 | 30 | return _healthCheck.CheckHealthAsync(akkaHealthCheckContext, cancellationToken); 31 | } 32 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestActorRefTests/WatchAndForwardActor.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using Akka.Actor; 9 | 10 | namespace Akka.Hosting.TestKit.Tests.TestActorRefTests; 11 | 12 | public class WatchAndForwardActor : ActorBase 13 | { 14 | private readonly IActorRef _forwardToActor; 15 | 16 | public WatchAndForwardActor(IActorRef watchedActor, IActorRef forwardToActor) 17 | { 18 | _forwardToActor = forwardToActor; 19 | Context.Watch(watchedActor); 20 | } 21 | 22 | protected override bool Receive(object message) 23 | { 24 | var terminated = message as Terminated; 25 | if(terminated != null) 26 | _forwardToActor.Tell(new WrappedTerminated(terminated), Sender); 27 | else 28 | _forwardToActor.Tell(message, Sender); 29 | return true; 30 | } 31 | } -------------------------------------------------------------------------------- /src/Akka.Hosting/HealthChecks/AkkaHealthCheckExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Akka.Actor; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Diagnostics.HealthChecks; 5 | 6 | namespace Akka.Hosting.HealthChecks; 7 | 8 | internal static class AkkaHealthCheckExtensions 9 | { 10 | public const string AkkaTag = "akka"; 11 | 12 | /// 13 | /// Converts an to a 14 | /// 15 | /// the original Akka.NET health check registration. 16 | public static HealthCheckRegistration ToHealthCheckRegistration(this AkkaHealthCheckRegistration registration) 17 | { 18 | // func for lazily instantiating the health check registration 19 | Func adapter = provider => 20 | new HealthCheckAdapter(registration.Factory(provider), provider.GetRequiredService()); 21 | 22 | var tags = registration.Tags; 23 | tags.Add(AkkaTag); 24 | 25 | return new HealthCheckRegistration(registration.Name, adapter, registration.FailureStatus, tags, 26 | registration.Timeout); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestActorRefTests/NestingActor.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using Akka.Actor; 9 | using Akka.TestKit; 10 | 11 | namespace Akka.Hosting.TestKit.Tests.TestActorRefTests; 12 | 13 | public class NestingActor : ActorBase 14 | { 15 | private readonly IActorRef _nested; 16 | 17 | public NestingActor(bool createTestActorRef) 18 | { 19 | _nested = createTestActorRef ? Context.ActorOf() : new TestActorRef(Context.System, Props.Create(), null, null); 20 | } 21 | 22 | protected override bool Receive(object message) 23 | { 24 | Sender.Tell(_nested, Self); 25 | return true; 26 | } 27 | 28 | private class NestedActor : ActorBase 29 | { 30 | protected override bool Receive(object message) 31 | { 32 | return true; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /.github/workflows/pr_validation.yml: -------------------------------------------------------------------------------- 1 | name: pr_validation 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - dev 8 | - main 9 | - feature/* 10 | pull_request: 11 | branches: 12 | - master 13 | - dev 14 | - main 15 | - feature/* 16 | 17 | jobs: 18 | test: 19 | name: Test-${{matrix.os}} 20 | runs-on: ${{matrix.os}} 21 | 22 | strategy: 23 | matrix: 24 | os: [ubuntu-latest, windows-latest] 25 | 26 | steps: 27 | - name: "Checkout" 28 | uses: actions/checkout@v6 29 | with: 30 | lfs: true 31 | fetch-depth: 0 32 | 33 | - name: "Install .NET SDK" 34 | uses: actions/setup-dotnet@v5 35 | with: 36 | global-json-file: "./global.json" 37 | 38 | - name: "Update release notes" 39 | shell: pwsh 40 | run: | 41 | ./build.ps1 42 | 43 | - name: "dotnet build" 44 | run: dotnet build -c Release 45 | 46 | # .NET Framework tests can't run reliably on Linux, so we only do .NET 8 47 | 48 | - name: "dotnet test" 49 | shell: bash 50 | run: dotnet test -c Release 51 | 52 | - name: "dotnet pack" 53 | run: dotnet pack -c Release -o ./bin/nuget 54 | -------------------------------------------------------------------------------- /src/Akka.Persistence.Hosting/Utils.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2013-2022 .NET Foundation 4 | // 5 | // ----------------------------------------------------------------------- 6 | 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | namespace Akka.Persistence.Hosting 11 | { 12 | internal static class Utils 13 | { 14 | // This illegal character list is conservative. Normally '.' and '/' is allowed in a HOCON literal, but we'll 15 | // ban it just because it'll make our life harder in the future. 16 | private const string IllegalChars = "$\"{}[]:=,#`^?!@*&\\./"; 17 | 18 | public static string[] IsIllegalHoconKey(this string s) 19 | { 20 | var illegals = new List(); 21 | foreach (var c in s) 22 | { 23 | if(IllegalChars.Contains(c)) 24 | illegals.Add($"{c}"); 25 | else if(char.IsWhiteSpace(c)) 26 | illegals.Add($"\\u{(int)c:X4}"); 27 | } 28 | 29 | return illegals.Distinct().ToArray(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestEventListenerTests/ConfigTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Linq; 10 | using System.Threading.Tasks; 11 | using FluentAssertions; 12 | using Xunit; 13 | 14 | namespace Akka.Hosting.TestKit.Tests.TestEventListenerTests 15 | { 16 | public class ConfigTests : TestKit 17 | { 18 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 19 | { 20 | } 21 | 22 | [Fact] 23 | public void TestEventListener_is_in_config_by_default() 24 | { 25 | var configLoggers = Sys.Settings.Config.GetStringList("akka.loggers", new string[] { }); 26 | configLoggers.Any(logger => logger.Contains("Akka.TestKit.TestEventListener")).Should().BeTrue(); 27 | configLoggers.Any(logger => logger.Contains("Akka.Event.DefaultLogger")).Should().BeFalse(); 28 | } 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit/Akka.Hosting.TestKit.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TestKit for writing tests for Akka.NET using Akka.Hosting and xUnit. 5 | $(LibraryFramework);$(NetLibraryFramework) 6 | true 7 | true 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit/Internals/TestKitLoggerFactoryLogger.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2013-2022 .NET Foundation 4 | // 5 | // ----------------------------------------------------------------------- 6 | 7 | using Akka.Actor; 8 | using Akka.Event; 9 | using Akka.Hosting.Logging; 10 | 11 | namespace Akka.Hosting.TestKit.Internals 12 | { 13 | public class TestKitLoggerFactoryLogger: LoggerFactoryLogger 14 | { 15 | protected override bool Receive(object message) 16 | { 17 | switch (message) 18 | { 19 | case InitializeLogger init: 20 | InternalLogger.Info($"{nameof(TestKitLoggerFactoryLogger)} started"); 21 | ((EventStream)init.LoggingBus).Subscribe(Self); 22 | // Only reply if there's an actual sender waiting (not NoSender) 23 | if (!Sender.Equals(ActorRefs.NoSender)) 24 | Sender.Tell(new LoggerInitialized()); 25 | return true; 26 | 27 | default: 28 | return base.Receive(message); 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/TestHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Akka.Actor; 4 | using Akka.Event; 5 | using Akka.TestKit.Xunit2.Internals; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | using Xunit.Abstractions; 9 | 10 | namespace Akka.Hosting.Tests; 11 | 12 | public static class TestHelpers 13 | { 14 | public static async Task StartHost(Action testSetup) 15 | { 16 | var host = new HostBuilder() 17 | .ConfigureServices(services => 18 | { 19 | services.AddSingleton(); 20 | testSetup(services); 21 | }).Build(); 22 | 23 | await host.StartAsync(); 24 | return host; 25 | } 26 | 27 | public static AkkaConfigurationBuilder AddTestOutputLogger(this AkkaConfigurationBuilder builder, 28 | ITestOutputHelper output) 29 | { 30 | builder.WithActors((system, registry) => 31 | { 32 | var extSystem = (ExtendedActorSystem)system; 33 | var logger = extSystem.SystemActorOf(Props.Create(() => new TestOutputLogger(output)), "log-test"); 34 | logger.Tell(new InitializeLogger(system.EventStream)); 35 | }); 36 | 37 | return builder; 38 | } 39 | } -------------------------------------------------------------------------------- /src/Akka.Persistence.Hosting.Tests/Akka.Persistence.Hosting.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(TestsNetCoreFramework) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Always 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestEventListenerTests/ForwardAllEventsTestEventListener.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using Akka.Actor; 9 | using Akka.Event; 10 | using Akka.TestKit; 11 | 12 | namespace Akka.Hosting.TestKit.Tests.TestEventListenerTests; 13 | 14 | public class ForwardAllEventsTestEventListener : TestEventListener 15 | { 16 | private IActorRef? _forwarder; 17 | 18 | protected override void Print(LogEvent m) 19 | { 20 | if(m.Message is ForwardAllEventsTo to) 21 | { 22 | _forwarder = to.Forwarder; 23 | _forwarder.Tell("OK"); 24 | } 25 | else if(_forwarder != null) 26 | { 27 | _forwarder.Forward(m); 28 | } 29 | else 30 | { 31 | base.Print(m); 32 | } 33 | } 34 | 35 | public class ForwardAllEventsTo 36 | { 37 | public ForwardAllEventsTo(IActorRef forwarder) 38 | { 39 | Forwarder = forwarder; 40 | } 41 | 42 | public IActorRef Forwarder { get; } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Akka.Remote.Hosting.Tests/Akka.Remote.Hosting.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(TestsNetCoreFramework) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Always 25 | 26 | 27 | Always 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /scripts/getReleaseNotes.ps1: -------------------------------------------------------------------------------- 1 | function Get-ReleaseNotes { 2 | param ( 3 | [Parameter(Mandatory=$true)] 4 | [string]$MarkdownFile 5 | ) 6 | 7 | # Read markdown file content 8 | $content = Get-Content -Path $MarkdownFile -Raw 9 | 10 | # Split content based on headers 11 | $sections = $content -split "####" 12 | 13 | # Output object to store result 14 | $outputObject = [PSCustomObject]@{ 15 | Version = $null 16 | Date = $null 17 | ReleaseNotes = $null 18 | } 19 | 20 | # Check if we have at least 3 sections (1. Before the header, 2. Header, 3. Release notes) 21 | if ($sections.Count -ge 3) { 22 | $header = $sections[1].Trim() 23 | $releaseNotes = $sections[2].Trim() 24 | 25 | # Extract version and date from the header 26 | $headerParts = $header -split " ", 2 27 | if ($headerParts.Count -eq 2) { 28 | $outputObject.Version = $headerParts[0] 29 | $outputObject.Date = $headerParts[1] 30 | } 31 | 32 | $outputObject.ReleaseNotes = $releaseNotes 33 | } 34 | 35 | # Return the output object 36 | return $outputObject 37 | } 38 | 39 | # Call function example: 40 | #$result = Get-ReleaseNotes -MarkdownFile "$PSScriptRoot\RELEASE_NOTES.md" 41 | #Write-Output "Version: $($result.Version)" 42 | #Write-Output "Date: $($result.Date)" 43 | #Write-Output "Release Notes:" 44 | #Write-Output $result.ReleaseNotes 45 | -------------------------------------------------------------------------------- /.github/workflows/publish_nuget.yml: -------------------------------------------------------------------------------- 1 | name: Publish NuGet 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | publish-nuget: 10 | 11 | name: publish-nuget 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | 17 | steps: 18 | - uses: actions/checkout@v6 19 | - name: Setup .NET Core 20 | uses: actions/setup-dotnet@v5 21 | with: 22 | dotnet-version: ${{ env.DOTNET_VERSION }} 23 | 24 | - name: "Update release notes" 25 | shell: pwsh 26 | run: | 27 | ./build.ps1 28 | 29 | - name: Create Packages 30 | run: dotnet pack /p:PackageVersion=${{ github.ref_name }} -c Release -o ./output 31 | 32 | - name: Push Packages 33 | run: dotnet nuget push "output/*.nupkg" -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json 34 | 35 | - name: release 36 | uses: actions/create-release@v1 37 | id: create_release 38 | with: 39 | draft: false 40 | prerelease: false 41 | release_name: 'Akka.Hosting ${{ github.ref_name }}' 42 | tag_name: ${{ github.ref }} 43 | body_path: RELEASE_NOTES.md 44 | env: 45 | GITHUB_TOKEN: ${{ github.token }} 46 | 47 | - name: Upload Release Asset 48 | uses: AButler/upload-release-assets@v3.0 49 | with: 50 | repo-token: ${{ github.token }} 51 | release-tag: ${{ github.ref_name }} 52 | files: 'output/*.nupkg' -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting.Tests/ClusterClientSpecs.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Akka.Actor; 3 | using Akka.Cluster.Tools.Client; 4 | using Xunit; 5 | 6 | namespace Akka.Cluster.Hosting.Tests; 7 | 8 | public class ClusterClientSpecs 9 | { 10 | [Fact(DisplayName = "ClusterClientReceptionistSettings should be set correctly")] 11 | public void ClusterClientReceptionistSettingsSpec() 12 | { 13 | var config = AkkaClusterHostingExtensions.CreateReceptionistConfig("customName", "customRole") 14 | .GetConfig("akka.cluster.client.receptionist"); 15 | var settings = ClusterReceptionistSettings.Create(config); 16 | 17 | Assert.Equal("customName", config.GetString("name")); 18 | Assert.Equal("customRole", settings.Role); 19 | } 20 | 21 | [Fact(DisplayName = "ClusterClientSettings should be set correctly")] 22 | public void ClusterClientSettingsSpec() 23 | { 24 | var contacts = new List 25 | { 26 | ActorPath.Parse("akka.tcp://one@localhost:1111/system/receptionist"), 27 | ActorPath.Parse("akka.tcp://two@localhost:1111/system/receptionist"), 28 | ActorPath.Parse("akka.tcp://three@localhost:1111/system/receptionist"), 29 | }; 30 | 31 | var settings = AkkaClusterHostingExtensions.CreateClusterClientSettings( 32 | ClusterClientReceptionist.DefaultConfig(), 33 | contacts); 34 | 35 | contacts.CollectionEquals(settings.InitialContacts); 36 | } 37 | } -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting.Tests/Akka.Cluster.Hosting.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(TestsNetCoreFramework) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Always 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestKitBaseTests/AwaitAssertTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Threading.Tasks; 10 | using Akka.Configuration; 11 | using Xunit; 12 | using Xunit.Sdk; 13 | 14 | namespace Akka.Hosting.TestKit.Tests.TestKitBaseTests; 15 | 16 | public class AwaitAssertTests : TestKit 17 | { 18 | protected override Config Config { get; } = "akka.test.timefactor=2"; 19 | 20 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 21 | { 22 | } 23 | 24 | [Fact] 25 | public void AwaitAssert_must_not_throw_any_exception_when_assertion_is_valid() 26 | { 27 | AwaitAssert(() => Assert.Equal("foo", "foo")); 28 | } 29 | 30 | [Fact] 31 | public void AwaitAssert_must_throw_exception_when_assertion_is_invalid() 32 | { 33 | Within(TimeSpan.FromMilliseconds(300), TimeSpan.FromSeconds(1), () => 34 | { 35 | Assert.Throws(() => 36 | AwaitAssert(() => Assert.Equal("foo", "bar"), TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(300))); 37 | }); 38 | } 39 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.API.Tests/CoreApiSpec.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Akka.Cluster.Hosting.SBR; 3 | using Akka.Persistence.Hosting; 4 | using Akka.Remote.Hosting; 5 | using VerifyTests; 6 | using VerifyXunit; 7 | using Xunit; 8 | using static PublicApiGenerator.ApiGenerator; 9 | using static VerifyXunit.Verifier; 10 | 11 | namespace Akka.Hosting.API.Tests; 12 | 13 | [UsesVerify] 14 | public class CoreApiSpec 15 | { 16 | static CoreApiSpec() 17 | { 18 | VerifierSettings.ScrubLinesContaining("\"RepositoryUrl\""); 19 | VerifierSettings.ScrubLinesContaining("Versioning.TargetFramework"); 20 | VerifyDiffPlex.Initialize(); 21 | } 22 | 23 | private static Task VerifyAssembly() 24 | { 25 | var settings = new VerifySettings(); 26 | settings.UseDirectory("verify"); 27 | return Verify(typeof(T).Assembly.GeneratePublicApi(), settings); 28 | } 29 | 30 | [Fact] 31 | public Task ApproveCore() 32 | { 33 | return VerifyAssembly(); 34 | } 35 | 36 | [Fact] 37 | public Task ApproveTestKit() 38 | { 39 | return VerifyAssembly(); 40 | } 41 | 42 | [Fact] 43 | public Task ApproveCluster() 44 | { 45 | return VerifyAssembly(); 46 | } 47 | 48 | [Fact] 49 | public Task ApproveRemoting() 50 | { 51 | return VerifyAssembly(); 52 | } 53 | 54 | [Fact] 55 | public Task ApprovePersistence() 56 | { 57 | return VerifyAssembly(); 58 | } 59 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestActorRefTests/SenderActor.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using Akka.Actor; 9 | 10 | namespace Akka.Hosting.TestKit.Tests.TestActorRefTests; 11 | 12 | public class SenderActor : TActorBase 13 | { 14 | private readonly IActorRef _replyActor; 15 | 16 | public SenderActor(IActorRef replyActor) 17 | { 18 | _replyActor = replyActor; 19 | } 20 | 21 | protected override bool ReceiveMessage(object message) 22 | { 23 | var strMessage = message as string; 24 | switch(strMessage) 25 | { 26 | case "complex": 27 | _replyActor.Tell("complexRequest", Self); 28 | return true; 29 | case "complex2": 30 | _replyActor.Tell("complexRequest2", Self); 31 | return true; 32 | case "simple": 33 | _replyActor.Tell("simpleRequest", Self); 34 | return true; 35 | case "complexReply": 36 | TestActorRefSpec.Counter--; 37 | return true; 38 | case "simpleReply": 39 | TestActorRefSpec.Counter--; 40 | return true; 41 | } 42 | return false; 43 | } 44 | } -------------------------------------------------------------------------------- /docs/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "files": [ "**/*.csproj" ], 7 | "exclude": [ 8 | "**/obj/**", 9 | "**/bin/**", 10 | "_site/**", 11 | "**/*Tests*.csproj", 12 | "**/*Tests.*.csproj" 13 | ], 14 | "src": "../src" 15 | } 16 | ], 17 | "dest": "api" 18 | } 19 | ], 20 | "build": { 21 | "content": [ 22 | { 23 | "files": [ 24 | "api/**.yml", 25 | "api/index.md" 26 | ] 27 | }, 28 | { 29 | "files": [ 30 | "articles/**.md", 31 | "articles/**/toc.yml", 32 | "toc.yml", 33 | "*.md" 34 | ], 35 | "exclude": [ 36 | "obj/**", 37 | "_site/**" 38 | ] 39 | }, 40 | ], 41 | "resource": [ 42 | { 43 | "files": [ 44 | "images/**", 45 | "web.config", 46 | ], 47 | "exclude": [ 48 | "obj/**", 49 | "_site/**" 50 | ] 51 | } 52 | ], 53 | "sitemap": { 54 | "baseUrl": "https://yoursite.github.io/" 55 | }, 56 | "dest": "_site", 57 | "globalMetadata": { 58 | "_appTitle": "Akka.Hosting", 59 | "_disableContribution": "true", 60 | "_appLogoPath": "/images/icon.png", 61 | }, 62 | "globalMetadataFiles": [], 63 | "fileMetadataFiles": [], 64 | "template": [ 65 | "default", 66 | "template" 67 | ], 68 | "postProcessors": ["ExtractSearchIndex"], 69 | "noLangKeyword": false 70 | } 71 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/StartFailureSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Akka.Actor; 4 | using FluentAssertions; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | using Microsoft.Extensions.Logging; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | using static FluentAssertions.FluentActions; 11 | 12 | namespace Akka.Hosting.Tests; 13 | 14 | public class StartFailureSpec 15 | { 16 | private readonly ITestOutputHelper _output; 17 | 18 | public StartFailureSpec(ITestOutputHelper output) 19 | { 20 | _output = output; 21 | } 22 | 23 | [Fact] 24 | public async Task ShouldThrowWhenActorSystemFailedToStart() 25 | { 26 | // arrange 27 | var host = new HostBuilder() 28 | .ConfigureLogging(builder => 29 | { 30 | builder.ClearProviders(); 31 | builder.AddProvider(new XUnitLoggerProvider(_output, LogLevel.Debug)); 32 | }) 33 | .ConfigureServices(services => 34 | { 35 | services.AddAkka("MySys", (builder, provider) => 36 | { 37 | builder.AddStartup((_, _) => throw new TestException("BOOM")); 38 | }); 39 | }) 40 | .Build(); 41 | 42 | await Awaiting(async () => await host.StartAsync()).Should() 43 | .ThrowExactlyAsync().WithMessage("BOOM"); 44 | } 45 | 46 | private class TestException: Exception 47 | { 48 | public TestException(string? message) : base(message) 49 | { 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System.Reflection; 9 | using System.Runtime.InteropServices; 10 | 11 | // General Information about an assembly is controlled through the following 12 | // set of attributes. Change these attribute values to modify the information 13 | // associated with an assembly. 14 | using Xunit; 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("b21496c0-a536-4953-9253-d2d0d526e42d")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | 35 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)] 36 | -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestActorRefTests/FsmActor.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using Akka.Actor; 9 | 10 | namespace Akka.Hosting.TestKit.Tests.TestActorRefTests; 11 | 12 | public enum TestFsmState 13 | { 14 | First, 15 | Last 16 | } 17 | 18 | public class FsmActor : FSM 19 | { 20 | private readonly IActorRef _replyActor; 21 | 22 | public FsmActor(IActorRef replyActor) 23 | { 24 | _replyActor = replyActor; 25 | 26 | When(TestFsmState.First, e => 27 | { 28 | if (e.FsmEvent.Equals("check")) 29 | { 30 | _replyActor.Tell("first"); 31 | } 32 | else if (e.FsmEvent.Equals("next")) 33 | { 34 | return GoTo(TestFsmState.Last); 35 | } 36 | 37 | return Stay(); 38 | }); 39 | 40 | When(TestFsmState.Last, e => 41 | { 42 | if (e.FsmEvent.Equals("check")) 43 | { 44 | _replyActor.Tell("last"); 45 | } 46 | else if (e.FsmEvent.Equals("next")) 47 | { 48 | return GoTo(TestFsmState.First); 49 | } 50 | 51 | return Stay(); 52 | }); 53 | 54 | StartWith(TestFsmState.First, "foo"); 55 | } 56 | } -------------------------------------------------------------------------------- /src/Akka.Persistence.Hosting/Extensions.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2013-2023 .NET Foundation 4 | // 5 | // ----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Data; 9 | using System.Runtime.CompilerServices; 10 | using Akka.Hosting; 11 | 12 | namespace Akka.Persistence.Hosting 13 | { 14 | public static class Extensions 15 | { 16 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 17 | public static string ToHocon(this IsolationLevel? level) 18 | { 19 | if (level is null) 20 | throw new ArgumentNullException(nameof(level)); 21 | return level.Value.ToHocon(); 22 | } 23 | 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | public static string ToHocon(this IsolationLevel level) 26 | { 27 | return level switch 28 | { 29 | IsolationLevel.Unspecified => "unspecified".ToHocon(), 30 | IsolationLevel.ReadCommitted => "read-committed".ToHocon(), 31 | IsolationLevel.ReadUncommitted => "read-uncommitted".ToHocon(), 32 | IsolationLevel.RepeatableRead => "repeatable-read".ToHocon(), 33 | IsolationLevel.Serializable => "serializable".ToHocon(), 34 | IsolationLevel.Snapshot => "snapshot".ToHocon(), 35 | IsolationLevel.Chaos => "chaos".ToHocon(), 36 | _ => throw new IndexOutOfRangeException($"Unknown IsolationLevel value: {level}"), 37 | }; 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Akka.Persistence.Hosting/README.md: -------------------------------------------------------------------------------- 1 | # Akka.Persistence.Hosting 2 | 3 | ## Akka.Persistence Extension Method 4 | 5 | ### WithJournal() Method 6 | 7 | Used to configure a specific Akka.Persistence.Journal instance, primarily to support [Event Adapters](https://getakka.net/articles/persistence/event-adapters.html). 8 | 9 | ```csharp 10 | public static AkkaConfigurationBuilder WithJournal( 11 | this AkkaConfigurationBuilder builder, 12 | string journalId, 13 | Action journalBuilder); 14 | ``` 15 | 16 | ### Parameters 17 | 18 | * `journalId` __string__ 19 | 20 | The id of the journal. i.e. if you want to apply this adapter to the `akka.persistence.journal.sql` journal, just use `"sql"`. 21 | 22 | * `journalBuilder` __Action\__ 23 | 24 | Configuration method for configuring the journal. 25 | 26 | ### WithInMemoryJournal() Method 27 | 28 | Add an in-memory journal to the `ActorSystem`, usually for testing purposes. 29 | 30 | ```csharp 31 | public static AkkaConfigurationBuilder WithInMemoryJournal( 32 | this AkkaConfigurationBuilder builder); 33 | ``` 34 | 35 | ```csharp 36 | public static AkkaConfigurationBuilder WithInMemoryJournal( 37 | this AkkaConfigurationBuilder builder, 38 | Action journalBuilder); 39 | ``` 40 | 41 | ### Parameters 42 | 43 | * `journalBuilder` __Action\__ 44 | 45 | Configuration method for configuring the journal. 46 | 47 | ### WithInMemorySnapshotStore() Method 48 | 49 | Add an in-memory snapshot store to the `ActorSystem`, usually for testing purposes. 50 | 51 | ```csharp 52 | public static AkkaConfigurationBuilder WithInMemorySnapshotStore( 53 | this AkkaConfigurationBuilder builder); 54 | ``` 55 | -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestActorRefTests/ReplyActor.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using Akka.Actor; 10 | using Akka.TestKit; 11 | 12 | namespace Akka.Hosting.TestKit.Tests.TestActorRefTests; 13 | 14 | public class ReplyActor : TActorBase 15 | { 16 | private IActorRef? _replyTo; 17 | 18 | protected override bool ReceiveMessage(object message) 19 | { 20 | var strMessage = message as string; 21 | switch(strMessage) 22 | { 23 | case "complexRequest": 24 | _replyTo = Sender; 25 | var worker = new TestActorRef(System, Props.Create()); 26 | worker.Tell("work"); 27 | return true; 28 | case "complexRequest2": 29 | var worker2 = new TestActorRef(System, Props.Create()); 30 | worker2.Tell(Sender, Self); 31 | return true; 32 | case "workDone": 33 | if (_replyTo is null) 34 | throw new NullReferenceException("_replyTo is null, make sure that \"complexRequest\" is sent first"); 35 | 36 | _replyTo.Tell("complexReply", Self); 37 | return true; 38 | case "simpleRequest": 39 | Sender.Tell("simpleReply", Self); 40 | return true; 41 | } 42 | return false; 43 | } 44 | } -------------------------------------------------------------------------------- /src/Akka.Persistence.Hosting.Tests/HoconKeyValidatorSpec.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2013-2022 .NET Foundation 4 | // 5 | // ----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using FluentAssertions; 10 | using Xunit; 11 | 12 | namespace Akka.Persistence.Hosting.Tests; 13 | 14 | public class HoconKeyValidatorSpec 15 | { 16 | [MemberData(nameof(StringFactory))] 17 | [Theory(DisplayName = "HOCON key validator should detect illegal characters")] 18 | public void ValidatorTest(string input, string[] illegals) 19 | { 20 | var illegalChars = input.IsIllegalHoconKey(); 21 | illegalChars.Length.Should().Be(illegals.Length); 22 | illegalChars.Should().BeEquivalentTo(illegals); 23 | } 24 | 25 | public static IEnumerable StringFactory() 26 | { 27 | yield return new object[] { "a.:\u0020", new []{".", ":", "\\u0020"} }; 28 | yield return new object[] { "a.b.c:d:", new []{".", ":"} }; 29 | yield return new object[] { "a..c::", new []{".", ":"} }; 30 | yield return new object[] { "\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006", new []{"\\u00A0", "\\u1680", "\\u2000", "\\u2001", "\\u2002", "\\u2003", "\\u2004", "\\u2005", "\\u2006"} }; 31 | yield return new object[] { "=,#`^", new []{"=", ",", "#", "`", "^"} }; 32 | yield return new object[] { "[x]y{z}", new []{"[", "]", "{", "}"} }; 33 | yield return new object[] { "-_%()'~|+01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ<>", Array.Empty() }; 34 | } 35 | } -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.LoggingDemo/WorkerService.cs: -------------------------------------------------------------------------------- 1 | using Akka.Actor; 2 | using Microsoft.Extensions.Hosting; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Akka.Hosting.LoggingDemo; 6 | 7 | public class WorkerService: IHostedService 8 | { 9 | private readonly ILogger _logger; 10 | private readonly IRequiredActor _reqEcho; 11 | private readonly CancellationTokenSource _cancellationTokenSource; 12 | private Task? _timerTask; 13 | 14 | public WorkerService(IRequiredActor reqActor, ILogger logger) 15 | { 16 | _reqEcho = reqActor; 17 | _logger = logger; 18 | _cancellationTokenSource = new CancellationTokenSource(); 19 | } 20 | 21 | public Task StartAsync(CancellationToken cancellationToken) 22 | { 23 | _timerTask = StartTimerTask(); 24 | return Task.CompletedTask; 25 | } 26 | 27 | public async Task StopAsync(CancellationToken cancellationToken) 28 | { 29 | _cancellationTokenSource.Cancel(); 30 | if(_timerTask != null) 31 | await _timerTask; 32 | } 33 | 34 | private async Task StartTimerTask() 35 | { 36 | var echoActor = await _reqEcho.GetAsync(); 37 | var periodicTimer = new PeriodicTimer(TimeSpan.FromSeconds(2)); 38 | 39 | try 40 | { 41 | while (true) 42 | { 43 | if (await periodicTimer.WaitForNextTickAsync(_cancellationTokenSource.Token)) 44 | { 45 | var response = await echoActor.Ask(Guid.NewGuid().ToString(), _cancellationTokenSource.Token); 46 | _logger.LogInformation(response); 47 | } 48 | } 49 | } 50 | catch 51 | { 52 | // no-op 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/Akka.Hosting.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(TestsNetCoreFramework) 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Always 31 | 32 | 33 | Always 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestKit_Config_Tests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Reflection; 10 | using System.Threading.Tasks; 11 | using Akka.TestKit; 12 | using FluentAssertions; 13 | using Xunit; 14 | 15 | namespace Akka.Hosting.TestKit.Tests; 16 | 17 | // ReSharper disable once InconsistentNaming 18 | public class TestKit_Config_Tests : TestKit 19 | { 20 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 21 | { 22 | } 23 | 24 | [Fact] 25 | public void DefaultValues_should_be_correct() 26 | { 27 | TestKitSettings.DefaultTimeout.Should().Be(TimeSpan.FromSeconds(5)); 28 | TestKitSettings.SingleExpectDefault.Should().Be(TimeSpan.FromSeconds(3)); 29 | TestKitSettings.TestEventFilterLeeway.Should().Be(TimeSpan.FromSeconds(3)); 30 | TestKitSettings.TestTimeFactor.Should().Be(1); 31 | var callingThreadDispatcherTypeName = typeof(CallingThreadDispatcherConfigurator).FullName + ", " + typeof(CallingThreadDispatcher).GetTypeInfo().Assembly.GetName().Name; 32 | Assert.False(Sys.Settings.Config.IsEmpty); 33 | Sys.Settings.Config.GetString("akka.test.calling-thread-dispatcher.type", null).Should().Be(callingThreadDispatcherTypeName); 34 | Sys.Settings.Config.GetString("akka.test.test-actor.dispatcher.type", null).Should().Be(callingThreadDispatcherTypeName); 35 | CallingThreadDispatcher.Id.Should().Be("akka.test.calling-thread-dispatcher"); 36 | } 37 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestActorRefTests/BossActor.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using Akka.Actor; 10 | using Akka.TestKit; 11 | 12 | namespace Akka.Hosting.TestKit.Tests.TestActorRefTests; 13 | 14 | public class BossActor : TActorBase 15 | { 16 | private TestActorRef _child; 17 | 18 | public BossActor() 19 | { 20 | _child = new TestActorRef(Context.System, Props.Create(), Self, "child"); 21 | } 22 | 23 | protected override SupervisorStrategy SupervisorStrategy() 24 | { 25 | return new OneForOneStrategy(maxNrOfRetries: 5, withinTimeRange: TimeSpan.FromSeconds(1), localOnlyDecider: ex => ex is ActorKilledException ? Directive.Restart : Directive.Escalate); 26 | } 27 | 28 | protected override bool ReceiveMessage(object message) 29 | { 30 | if(message is string && ((string)message) == "sendKill") 31 | { 32 | _child.Tell(Kill.Instance); 33 | return true; 34 | } 35 | return false; 36 | } 37 | 38 | private class InternalActor : TActorBase 39 | { 40 | protected override void PreRestart(Exception reason, object message) 41 | { 42 | TestActorRefSpec.Counter--; 43 | } 44 | 45 | protected override void PostRestart(Exception reason) 46 | { 47 | TestActorRefSpec.Counter--; 48 | } 49 | 50 | protected override bool ReceiveMessage(object message) 51 | { 52 | return true; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/DiPropsFailTest.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2013-2023 .NET Foundation 4 | // 5 | // ----------------------------------------------------------------------- 6 | 7 | using System; 8 | using Akka.Actor; 9 | using Akka.DependencyInjection; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Hosting; 12 | using Microsoft.Extensions.Logging; 13 | using Xunit; 14 | using Xunit.Abstractions; 15 | 16 | namespace Akka.Hosting.TestKit.Tests; 17 | 18 | // Regression test for https://github.com/akkadotnet/Akka.Hosting/issues/343 19 | public class DiPropsFailTest: TestKit 20 | { 21 | public DiPropsFailTest(ITestOutputHelper output) : base(nameof(DiPropsFailTest), output) 22 | {} 23 | 24 | protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) 25 | { 26 | base.ConfigureServices(context, services); 27 | services.AddLogging(); 28 | } 29 | 30 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 31 | { } 32 | 33 | [Fact] 34 | public void DiTest() 35 | { 36 | var actor = Sys.ActorOf(NonRootActorWithDi.Props(Sys)); 37 | actor.Tell("test"); 38 | ExpectMsg("test"); 39 | } 40 | 41 | private class NonRootActorWithDi: ReceiveActor 42 | { 43 | public static Props Props(ActorSystem system) => DependencyResolver.For(system).Props(); 44 | 45 | public NonRootActorWithDi(ILogger log) 46 | { 47 | ReceiveAny(msg => 48 | { 49 | log.LogInformation("Received {Msg}", msg); 50 | Sender.Tell(msg); 51 | }); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.API.Tests/Akka.Hosting.API.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(TestsNetCoreFramework) 5 | enable 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Always 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/Akka.Hosting/Util.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2022 Lightbend Inc. 4 | // Copyright (C) 2013-2022 .NET Foundation 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Linq; 10 | using Akka.Configuration; 11 | using Akka.Configuration.Hocon; 12 | using Akka.Util.Internal; 13 | 14 | namespace Akka.Hosting 15 | { 16 | public static class Util 17 | { 18 | // HACK: MAUI runtime detection 19 | private static bool? _runningInMaui; 20 | internal static bool IsRunningInMaui 21 | { 22 | get 23 | { 24 | _runningInMaui ??= AppDomain.CurrentDomain.GetAssemblies().Any(asm => asm?.GetName()?.Name?.StartsWith("Microsoft.Maui") ?? false); 25 | return _runningInMaui.Value; 26 | } 27 | } 28 | 29 | public static Config MoveTo(this Config config, string path) 30 | { 31 | var rootObj = new HoconObject(); 32 | var rootValue = new HoconValue(); 33 | rootValue.Values.Add(rootObj); 34 | 35 | var lastObject = rootObj; 36 | 37 | var keys = path.SplitDottedPathHonouringQuotes().ToArray(); 38 | for (var i = 0; i < keys.Length - 1; i++) 39 | { 40 | var key = keys[i]; 41 | var innerObject = new HoconObject(); 42 | var innerValue = new HoconValue(); 43 | innerValue.Values.Add(innerObject); 44 | 45 | lastObject.GetOrCreateKey(key); 46 | lastObject.Items[key] = innerValue; 47 | lastObject = innerObject; 48 | } 49 | lastObject.Items[keys[keys.Length - 1]] = config.Root; 50 | 51 | return new Config(new HoconRoot(rootValue)); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/HostingSpecSpec.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2022 Lightbend Inc. 4 | // Copyright (C) 2013-2022 .NET Foundation 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Threading.Tasks; 10 | using Akka.Actor; 11 | using Akka.Event; 12 | using Akka.TestKit.TestActors; 13 | using Xunit; 14 | using Xunit.Abstractions; 15 | using LogLevel = Microsoft.Extensions.Logging.LogLevel; 16 | 17 | namespace Akka.Hosting.TestKit.Tests 18 | { 19 | public class HostingSpecSpec: TestKit 20 | { 21 | private enum Echo 22 | { } 23 | 24 | public HostingSpecSpec(ITestOutputHelper output) 25 | : base(nameof(HostingSpecSpec), output, logLevel: LogLevel.Debug) 26 | { 27 | } 28 | 29 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 30 | { 31 | builder.WithActors((system, registry) => 32 | { 33 | var echo = system.ActorOf(Props.Create(() => new SimpleEchoActor())); 34 | registry.Register(echo); 35 | }); 36 | } 37 | 38 | [Fact] 39 | public void ActorTest() 40 | { 41 | var echo = ActorRegistry.Get(); 42 | var probe = CreateTestProbe(); 43 | 44 | echo.Tell("TestMessage", probe); 45 | var msg = probe.ExpectMsg("TestMessage"); 46 | Log.Info(msg); 47 | } 48 | 49 | private class SimpleEchoActor : ReceiveActor 50 | { 51 | public SimpleEchoActor() 52 | { 53 | var log = Context.GetLogger(); 54 | 55 | ReceiveAny(msg => 56 | { 57 | log.Info($"Received {msg}"); 58 | Sender.Tell(msg); 59 | }); 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestKitBaseTests/ExpectTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Threading.Tasks; 10 | using Akka.Actor; 11 | using FluentAssertions; 12 | using Xunit; 13 | using static FluentAssertions.FluentActions; 14 | 15 | namespace Akka.Hosting.TestKit.Tests.TestKitBaseTests; 16 | 17 | public class ExpectTests : TestKit 18 | { 19 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 20 | { 21 | } 22 | 23 | [Fact] 24 | public void ExpectMsgAllOf_should_receive_correct_messages() 25 | { 26 | TestActor.Tell("1"); 27 | TestActor.Tell("2"); 28 | TestActor.Tell("3"); 29 | TestActor.Tell("4"); 30 | ExpectMsgAllOf(new []{"3", "1", "4", "2"}).Should() 31 | .BeEquivalentTo(new[] { "1", "2", "3", "4" }, opt => opt.WithStrictOrdering()); 32 | } 33 | 34 | [Fact] 35 | public void ExpectMsgAllOf_should_fail_when_receiving_unexpected() 36 | { 37 | TestActor.Tell("1"); 38 | TestActor.Tell("2"); 39 | TestActor.Tell("Totally unexpected"); 40 | TestActor.Tell("3"); 41 | Invoking(() => ExpectMsgAllOf(new []{"3", "1", "2"} )) 42 | .Should().Throw(); 43 | } 44 | 45 | [Fact] 46 | public void ExpectMsgAllOf_should_timeout_when_not_receiving_any_messages() 47 | { 48 | Invoking(() => ExpectMsgAllOf(TimeSpan.FromMilliseconds(100), new []{"3", "1", "2"} )) 49 | .Should().Throw(); 50 | } 51 | 52 | [Fact] 53 | public void ExpectMsgAllOf_should_timeout_if_to_few_messages() 54 | { 55 | TestActor.Tell("1"); 56 | TestActor.Tell("2"); 57 | Invoking(() => ExpectMsgAllOf(TimeSpan.FromMilliseconds(100), new[]{"3", "1", "2"} )) 58 | .Should().Throw(); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestActorRefTests/PersistActor.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2022 Lightbend Inc. 4 | // Copyright (C) 2013-2025 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using Akka.Persistence; 9 | 10 | namespace Akka.Hosting.TestKit.Tests.TestActorRefTests 11 | { 12 | using System; 13 | using Actor; 14 | 15 | public class PersistActor : UntypedPersistentActor 16 | { 17 | public PersistActor(IActorRef probe) 18 | { 19 | _probe = probe; 20 | } 21 | 22 | private readonly IActorRef _probe; 23 | 24 | public override string PersistenceId => "foo"; 25 | 26 | protected override void OnCommand(object message) 27 | { 28 | switch (message) 29 | { 30 | case WriteMessage msg: 31 | Persist(msg.Data, _ => 32 | { 33 | _probe.Tell("ack"); 34 | }); 35 | 36 | break; 37 | 38 | default: 39 | return; 40 | } 41 | } 42 | 43 | protected override void OnRecover(object message) 44 | { 45 | _probe.Tell(message); 46 | } 47 | 48 | protected override void OnPersistFailure(Exception cause, object @event, long sequenceNr) 49 | { 50 | _probe.Tell("failure"); 51 | 52 | base.OnPersistFailure(cause, @event, sequenceNr); 53 | } 54 | 55 | protected override void OnPersistRejected(Exception cause, object @event, long sequenceNr) 56 | { 57 | _probe.Tell("rejected"); 58 | 59 | base.OnPersistRejected(cause, @event, sequenceNr); 60 | } 61 | 62 | public class WriteMessage 63 | { 64 | public string Data { get; } 65 | 66 | public WriteMessage(string data) 67 | { 68 | Data = data; 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/SerializerRegistrationSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Akka.Actor; 3 | using Akka.Serialization; 4 | using FluentAssertions; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Xunit; 7 | 8 | namespace Akka.Hosting.Tests 9 | { 10 | public class SerializerRegistrationSpecs 11 | { 12 | public interface IUseCustomSerializer 13 | { 14 | } 15 | 16 | public sealed class CustomSerializedMessage : IUseCustomSerializer 17 | { 18 | public static readonly CustomSerializedMessage Instance = new CustomSerializedMessage(); 19 | 20 | private CustomSerializedMessage() 21 | { 22 | } 23 | } 24 | 25 | public sealed class CustomSerializer : SerializerWithStringManifest 26 | { 27 | public CustomSerializer(ExtendedActorSystem system) : base(system) 28 | { 29 | } 30 | 31 | public override int Identifier => 435; 32 | 33 | public override byte[] ToBinary(object obj) 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | 38 | public override object FromBinary(byte[] bytes, string manifest) 39 | { 40 | throw new NotImplementedException(); 41 | } 42 | 43 | public override string Manifest(object o) 44 | { 45 | throw new NotImplementedException(); 46 | } 47 | } 48 | 49 | [Fact] 50 | public void ShouldAddCustomSerializer() 51 | { 52 | var serviceCollection = new ServiceCollection(); 53 | 54 | serviceCollection.AddAkka("TestSys", (builder, provider) => 55 | { 56 | builder.WithCustomSerializer("my-serializer", new[] { typeof(IUseCustomSerializer) }, 57 | system => new CustomSerializer(system)); 58 | }); 59 | using var sp = serviceCollection.BuildServiceProvider(); 60 | using var actorSystem = sp.GetRequiredService(); 61 | var serializer = actorSystem.Serialization.FindSerializerFor(CustomSerializedMessage.Instance); 62 | serializer.Should().BeOfType(); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestEventListenerTests/CustomEventFilterTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using Akka.Event; 9 | using Akka.TestKit; 10 | using Xunit; 11 | 12 | namespace Akka.Hosting.TestKit.Tests.TestEventListenerTests; 13 | 14 | public abstract class CustomEventFilterTestsBase : EventFilterTestBase 15 | { 16 | // ReSharper disable ConvertToLambdaExpression 17 | public CustomEventFilterTestsBase() : base(Event.LogLevel.ErrorLevel) { } 18 | 19 | protected override void SendRawLogEventMessage(object message) 20 | { 21 | Sys.EventStream.Publish(new Error(null, "CustomEventFilterTests", GetType(), message)); 22 | } 23 | 24 | protected abstract EventFilterFactory CreateTestingEventFilter(); 25 | 26 | [Fact] 27 | public void Custom_filter_should_match() 28 | { 29 | var eventFilter = CreateTestingEventFilter(); 30 | eventFilter.Custom(logEvent => logEvent is Error && (string) logEvent.Message == "whatever").ExpectOne(() => 31 | { 32 | Log.Error("whatever"); 33 | }); 34 | } 35 | 36 | [Fact] 37 | public void Custom_filter_should_match2() 38 | { 39 | var eventFilter = CreateTestingEventFilter(); 40 | eventFilter.Custom(logEvent => (string)logEvent.Message == "whatever").ExpectOne(() => 41 | { 42 | Log.Error("whatever"); 43 | }); 44 | } 45 | // ReSharper restore ConvertToLambdaExpression 46 | } 47 | 48 | public class CustomEventFilterTests : CustomEventFilterTestsBase 49 | { 50 | protected override EventFilterFactory CreateTestingEventFilter() 51 | { 52 | return EventFilter; 53 | } 54 | } 55 | 56 | public class CustomEventFilterCustomFilterTests : CustomEventFilterTestsBase 57 | { 58 | protected override EventFilterFactory CreateTestingEventFilter() 59 | { 60 | return CreateEventFilter(Sys); 61 | } 62 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestKitBaseTests/IgnoreMessagesTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using Akka.Actor; 10 | using FluentAssertions; 11 | using Xunit; 12 | 13 | namespace Akka.Hosting.TestKit.Tests.TestKitBaseTests; 14 | 15 | public class IgnoreMessagesTests : TestKit 16 | { 17 | public class IgnoredMessage 18 | { 19 | public IgnoredMessage(string? ignoreMe = null) 20 | { 21 | IgnoreMe = ignoreMe; 22 | } 23 | 24 | public string? IgnoreMe { get; } 25 | } 26 | 27 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 28 | { 29 | } 30 | 31 | [Fact] 32 | public void IgnoreMessages_should_ignore_messages() 33 | { 34 | IgnoreMessages(o => o is 1); 35 | TestActor.Tell(1); 36 | TestActor.Tell("1"); 37 | string.Equals((string)ReceiveOne(), "1").Should().BeTrue(); 38 | HasMessages.Should().BeFalse(); 39 | } 40 | 41 | [Fact] 42 | public void IgnoreMessages_should_ignore_messages_T() 43 | { 44 | IgnoreMessages(); 45 | 46 | TestActor.Tell("1"); 47 | TestActor.Tell(new IgnoredMessage(), TestActor); 48 | TestActor.Tell("2"); 49 | ReceiveN(2).Should().BeEquivalentTo(new[] { "1", "2" }, opt => opt.WithStrictOrdering()); 50 | HasMessages.Should().BeFalse(); 51 | } 52 | 53 | [Fact] 54 | public void IgnoreMessages_should_ignore_messages_T_with_Func() 55 | { 56 | IgnoreMessages(m => String.IsNullOrWhiteSpace(m.IgnoreMe)); 57 | 58 | var msg = new IgnoredMessage("not ignored!"); 59 | 60 | TestActor.Tell("1"); 61 | TestActor.Tell(msg, TestActor); 62 | TestActor.Tell("2"); 63 | ReceiveN(3).Should().BeEquivalentTo(new object[] { "1", msg, "2" }, opt => opt.WithStrictOrdering()); 64 | HasMessages.Should().BeFalse(); 65 | } 66 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestEventListenerTests/DeadLettersEventFilterTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System.Threading.Tasks; 9 | using Akka.Actor; 10 | using Akka.Event; 11 | using Akka.TestKit; 12 | using Akka.TestKit.TestActors; 13 | using Xunit; 14 | 15 | namespace Akka.Hosting.TestKit.Tests.TestEventListenerTests; 16 | 17 | public abstract class DeadLettersEventFilterTestsBase : EventFilterTestBase 18 | { 19 | private IActorRef? _deadActor; 20 | 21 | // ReSharper disable ConvertToLambdaExpression 22 | protected DeadLettersEventFilterTestsBase() : base(Event.LogLevel.ErrorLevel) 23 | { 24 | } 25 | 26 | protected override async Task BeforeTestStart() 27 | { 28 | await base.BeforeTestStart(); 29 | _deadActor = Sys.ActorOf(BlackHoleActor.Props, "dead-actor"); 30 | Watch(_deadActor); 31 | Sys.Stop(_deadActor); 32 | ExpectTerminated(_deadActor); 33 | } 34 | 35 | protected override void SendRawLogEventMessage(object message) 36 | { 37 | Sys.EventStream.Publish(new Error(null, "DeadLettersEventFilterTests", GetType(), message)); 38 | } 39 | 40 | protected abstract EventFilterFactory CreateTestingEventFilter(); 41 | 42 | [Fact] 43 | public void Should_be_able_to_filter_dead_letters() 44 | { 45 | var eventFilter = CreateTestingEventFilter(); 46 | eventFilter.DeadLetter().ExpectOne(() => 47 | { 48 | _deadActor.Tell("whatever"); 49 | }); 50 | } 51 | 52 | 53 | // ReSharper restore ConvertToLambdaExpression 54 | } 55 | 56 | public class DeadLettersEventFilterTests : DeadLettersEventFilterTestsBase 57 | { 58 | protected override EventFilterFactory CreateTestingEventFilter() 59 | { 60 | return EventFilter; 61 | } 62 | } 63 | 64 | public class DeadLettersCustomEventFilterTests : DeadLettersEventFilterTestsBase 65 | { 66 | protected override EventFilterFactory CreateTestingEventFilter() 67 | { 68 | return CreateEventFilter(Sys); 69 | } 70 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/HostingExtensionsSpec.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2022 Lightbend Inc. 4 | // Copyright (C) 2013-2022 .NET Foundation 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Linq; 10 | using Akka.Event; 11 | using FluentAssertions; 12 | using FluentAssertions.Extensions; 13 | using Microsoft.Extensions.DependencyInjection; 14 | using Xunit; 15 | 16 | namespace Akka.Hosting.Tests; 17 | 18 | public class HostingExtensionsSpec 19 | { 20 | [Fact(DisplayName = "WithActorAskTimeout should inject proper HOCON")] 21 | public void WithActorAskTimeoutTest() 22 | { 23 | var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "fake") 24 | .WithActorAskTimeout(10.Seconds()); 25 | builder.Configuration.HasValue.Should().BeTrue(); 26 | builder.Configuration.Value.GetTimeSpan("akka.actor.ask-timeout").Should().Be(10.Seconds()); 27 | } 28 | 29 | [Fact(DisplayName = "WithActorAskTimeout should be able to infer infinite timespan")] 30 | public void WithActorAskTimeoutInfiniteTest() 31 | { 32 | var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "fake") 33 | .WithActorAskTimeout(TimeSpan.Zero); 34 | builder.Configuration.HasValue.Should().BeTrue(); 35 | builder.Configuration.Value.GetString("akka.actor.ask-timeout").Should().Be("infinite"); 36 | } 37 | 38 | [Fact(DisplayName = "ConfigureLogger WithLogFilter should inject LogFilterSetup")] 39 | public void ConfigureLoggerWithLogFilterSetupTest() 40 | { 41 | var builder = new AkkaConfigurationBuilder(new ServiceCollection(), "fake") 42 | .ConfigureLoggers(logger => 43 | { 44 | logger.WithLogFilter(filterBuilder => 45 | { 46 | filterBuilder.ExcludeMessageContaining("Test"); 47 | }); 48 | }); 49 | var filterSetup = builder.Setups.OfType().First(); 50 | filterSetup.Filters.Length.Should().Be(1); 51 | filterSetup.Filters.Any(f => f is RegexLogMessageFilter).Should().BeTrue(); 52 | } 53 | } -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting.Tests/ClusterShardingDistributedDataSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Akka.Actor; 5 | using Akka.DistributedData; 6 | using Akka.Hosting; 7 | using Akka.Remote.Hosting; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace Akka.Cluster.Hosting.Tests; 12 | 13 | public class ClusterShardingDistributedDataSpecs: Akka.Hosting.TestKit.TestKit 14 | { 15 | private const string ReplicatorName = "dDataReplicator"; 16 | 17 | public ClusterShardingDistributedDataSpecs(ITestOutputHelper output): base(output: output) 18 | { 19 | } 20 | 21 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 22 | { 23 | builder 24 | .WithRemoting() 25 | .WithClustering() 26 | .WithDistributedData(opt => 27 | { 28 | opt.Name = ReplicatorName; 29 | }); 30 | } 31 | 32 | [Fact(DisplayName = "WithDistributedData should start DistributedData extension automatically")] 33 | public async Task WithDistributedDataStartsAutomaticallyTest() 34 | { 35 | var cluster = Cluster.Get(Sys); 36 | await cluster.JoinAsync(cluster.SelfAddress); 37 | await AwaitAssertAsync(() => 38 | Assert.Equal(1, cluster.State.Members.Count(m => m.Status == MemberStatus.Up)), 39 | interval: TimeSpan.FromMilliseconds(200), 40 | duration: TimeSpan.FromSeconds(10)); 41 | 42 | var settings = ReplicatorSettings.Create(Sys); 43 | var coordinatorName = settings.RestartReplicatorOnFailure ? $"{ReplicatorName}Supervisor" : ReplicatorName; 44 | 45 | var actorSelection = Sys.ActorSelection(new RootActorPath(cluster.SelfAddress) / "user" / coordinatorName); 46 | 47 | await AwaitAssertAsync(async () => 48 | { 49 | actorSelection.Tell(new Identify("coordinator"), TestActor); 50 | var identity = await ExpectMsgAsync(TimeSpan.FromSeconds(3)); 51 | 52 | // The DData replicator should be running 53 | // * This actor is created inside DistributedData extension .ctor 54 | // * Marks that the extension is running 55 | // * We can't use DistributedData.Get() because that defeats the purpose. 56 | Assert.NotNull(identity.Subject); 57 | }); 58 | } 59 | } -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.LoggingDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using Akka.Hosting; 2 | using Akka.Actor; 3 | using Akka.Actor.Dsl; 4 | using Akka.Cluster.Hosting; 5 | using Akka.Event; 6 | using Akka.Hosting.LoggingDemo; 7 | using Akka.Remote.Hosting; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Hosting; 10 | using LogLevel = Akka.Event.LogLevel; 11 | 12 | var builder = Host.CreateDefaultBuilder(args); 13 | 14 | await builder 15 | .ConfigureServices(services => 16 | { 17 | services 18 | .AddSingleton() 19 | .AddHostedService() 20 | .AddAkka("MyActorSystem", (configurationBuilder, serviceProvider) => 21 | { 22 | configurationBuilder 23 | .ConfigureLoggers(setup => 24 | { 25 | // This sets the minimum log level 26 | setup.LogLevel = LogLevel.DebugLevel; 27 | 28 | // Clear all loggers (remove the default console logger) 29 | setup.ClearLoggers(); 30 | 31 | // Add the ILoggerFactory logger 32 | // NOTE: 33 | // - You can also use setup.AddLogger(); 34 | // - To use a specific ILoggerFactory instance, you can use setup.AddLoggerFactory(myILoggerFactory); 35 | setup.AddLoggerFactory(); 36 | }) 37 | .WithRemoting("localhost", 8110) 38 | .WithClustering(new ClusterOptions { 39 | Roles = ["myRole"], 40 | SeedNodes = ["akka.tcp://MyActorSystem@localhost:8110"] 41 | }) 42 | .WithActors((system, registry) => 43 | { 44 | var echo = system.ActorOf(act => 45 | { 46 | act.ReceiveAny((o, context) => 47 | { 48 | Logging.GetLogger(context.System, "echo").Info($"Actor received {o}"); 49 | context.Sender.Tell($"{context.Self} rcv {o}"); 50 | }); 51 | }, "echo"); 52 | registry.TryRegister(echo); // register for DI 53 | }); 54 | }); 55 | }) 56 | .UseConsoleLifetime() 57 | .RunConsoleAsync(); -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting/AkkaClusterHealthCheck.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Akka.Hosting; 6 | using Microsoft.Extensions.Diagnostics.HealthChecks; 7 | 8 | namespace Akka.Cluster.Hosting; 9 | 10 | internal static class ClusterHealthCheckHelpers 11 | { 12 | public static IReadOnlyDictionary DumpClusterState(this ClusterEvent.CurrentClusterState state) 13 | { 14 | return new Dictionary 15 | { 16 | {"cluster.members", state.Members.Count}, 17 | {"cluster.unreachable", state.Unreachable.Count}, 18 | {"cluster.leader", state.Leader.ToString()} 19 | }; 20 | } 21 | } 22 | 23 | /// 24 | /// Checks to see if we've joined a cluster and have been marked as 25 | /// or 26 | /// 27 | public sealed class AkkaClusterReadinessCheck : IAkkaHealthCheck 28 | { 29 | /// 30 | /// Have we successfully joined the cluster? 31 | /// 32 | public bool WeHaveJoined { get; private set; } 33 | 34 | public DateTime BeganJoining { get; } = DateTime.UtcNow; 35 | 36 | public DateTime? FinishedJoining { get; private set; } 37 | 38 | public HealthCheckResult HealthyResult(DateTime finishedJoining) => HealthCheckResult.Healthy( 39 | $"Observed successful cluster join after [{finishedJoining - BeganJoining:g}] - actual join duration was probably faster, but this is how quickly the health check observed it."); 40 | 41 | public HealthCheckResult UnhealthyResult(DateTime now, HealthStatus failureStatus) => 42 | new HealthCheckResult(failureStatus, $"Have not yet joined Akka.NET cluster [{now - BeganJoining:g}] elapsed"); 43 | 44 | public Task CheckHealthAsync(AkkaHealthCheckContext context, 45 | CancellationToken cancellationToken = default) 46 | { 47 | if (WeHaveJoined && FinishedJoining != null) 48 | return Task.FromResult(HealthyResult(FinishedJoining.Value)); 49 | 50 | var cluster = Cluster.Get(context.ActorSystem); 51 | WeHaveJoined = cluster.SelfMember.Status is MemberStatus.Up or MemberStatus.WeaklyUp; 52 | 53 | if (WeHaveJoined) 54 | { 55 | FinishedJoining = DateTime.UtcNow; 56 | return Task.FromResult(HealthyResult(FinishedJoining.Value)); 57 | } 58 | 59 | return Task.FromResult(UnhealthyResult(DateTime.UtcNow, context.Registration.FailureStatus)); 60 | } 61 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/NoImplicitSenderSpec.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Threading.Tasks; 10 | using Akka.Actor; 11 | using Akka.Actor.Dsl; 12 | using Akka.TestKit; 13 | using FluentAssertions; 14 | using Xunit; 15 | 16 | namespace Akka.Hosting.TestKit.Tests; 17 | 18 | public class NoImplicitSenderSpec : TestKit, INoImplicitSender 19 | { 20 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 21 | { 22 | 23 | } 24 | 25 | [Fact] 26 | public void When_Not_ImplicitSender_then_testActor_is_not_sender() 27 | { 28 | var echoActor = Sys.ActorOf(c => c.ReceiveAny((m, ctx) => TestActor.Tell(ctx.Sender))); 29 | echoActor.Tell("message"); 30 | var actorRef = ExpectMsg(); 31 | actorRef.Should().Be(Sys.DeadLetters); 32 | } 33 | 34 | } 35 | 36 | public class ImplicitSenderSpec : TestKit 37 | { 38 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 39 | { 40 | 41 | } 42 | 43 | [Fact] 44 | public void ImplicitSender_should_have_testActor_as_sender() 45 | { 46 | var echoActor = Sys.ActorOf(c => c.ReceiveAny((m, ctx) => TestActor.Tell(ctx.Sender))); 47 | echoActor.Tell("message"); 48 | ExpectMsg(actorRef => Equals(actorRef, TestActor)); 49 | 50 | //Test that it works after we know that context has been changed 51 | echoActor.Tell("message"); 52 | ExpectMsg(actorRef => Equals(actorRef, TestActor)); 53 | 54 | } 55 | 56 | 57 | [Fact] 58 | public void ImplicitSender_should_not_change_when_creating_Testprobes() 59 | { 60 | //Verifies that bug #459 has been fixed 61 | var testProbe = CreateTestProbe(); 62 | TestActor.Tell("message"); 63 | ReceiveOne(); 64 | LastSender.Should().Be(TestActor); 65 | } 66 | 67 | [Fact] 68 | public void ImplicitSender_should_not_change_when_creating_TestActors() 69 | { 70 | var testActor2 = CreateTestActor("test2"); 71 | TestActor.Tell("message"); 72 | ReceiveOne(); 73 | LastSender.Should().Be(TestActor); 74 | } 75 | } -------------------------------------------------------------------------------- /src/Akka.Persistence.Hosting/HealthChecks.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Akka.Hosting; 4 | using Microsoft.Extensions.Diagnostics.HealthChecks; 5 | 6 | namespace Akka.Persistence.Hosting; 7 | 8 | internal static class HealthCheckExt 9 | { 10 | public static HealthCheckResult ToHealthCheckResult(this PersistenceHealthCheckResult persistenceHealthStatus) 11 | => new(persistenceHealthStatus.Status.ToHealthStatus(), persistenceHealthStatus.Description, 12 | persistenceHealthStatus.Exception, persistenceHealthStatus.Data); 13 | 14 | public static HealthStatus ToHealthStatus(this PersistenceHealthStatus persistenceHealthStatus) => persistenceHealthStatus switch 15 | { 16 | PersistenceHealthStatus.Healthy => HealthStatus.Healthy, 17 | PersistenceHealthStatus.Degraded => HealthStatus.Degraded, 18 | _ => HealthStatus.Unhealthy 19 | }; 20 | } 21 | 22 | /// 23 | /// INTERNAL API 24 | /// 25 | /// Leverages internal Akka.Persistence APIs to perform a health check on a journal. 26 | /// 27 | internal sealed class JournalHealthCheck : IAkkaHealthCheck 28 | { 29 | private readonly string _journalPluginId; 30 | 31 | public JournalHealthCheck(string journalPluginId) 32 | { 33 | _journalPluginId = journalPluginId; 34 | } 35 | 36 | public async Task CheckHealthAsync(AkkaHealthCheckContext context, CancellationToken cancellationToken = default) 37 | { 38 | var persistence = Persistence.Instance.Apply(context.ActorSystem); 39 | var journalResult = await persistence.CheckJournalHealthAsync(_journalPluginId, cancellationToken); 40 | return journalResult.ToHealthCheckResult(); 41 | } 42 | } 43 | 44 | /// 45 | /// INTERNAL API 46 | /// 47 | /// Leverages internal Akka.Persistence APIs to perform a health check on the snapshot store. 48 | /// 49 | internal sealed class SnapshotStoreHealthCheck : IAkkaHealthCheck 50 | { 51 | private readonly string _snapshotStorePluginId; 52 | 53 | public SnapshotStoreHealthCheck(string snapshotStorePluginId) 54 | { 55 | _snapshotStorePluginId = snapshotStorePluginId; 56 | } 57 | 58 | public async Task CheckHealthAsync(AkkaHealthCheckContext context, CancellationToken cancellationToken = default) 59 | { 60 | var persistence = Persistence.Instance.Apply(context.ActorSystem); 61 | var ssResult = await persistence.CheckSnapshotStoreHealthAsync(_snapshotStorePluginId, cancellationToken); 62 | return ssResult.ToHealthCheckResult(); 63 | } 64 | } -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.SimpleDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using Akka.Actor; 2 | using Akka.Cluster.Hosting; 3 | using Akka.Cluster.Sharding; 4 | using Akka.Remote.Hosting; 5 | using Akka.Util; 6 | 7 | namespace Akka.Hosting.SimpleDemo; 8 | 9 | public interface IReplyGenerator 10 | { 11 | string Reply(object input); 12 | } 13 | 14 | public class DefaultReplyGenerator : IReplyGenerator 15 | { 16 | public string Reply(object input) 17 | { 18 | return input.ToString()!; 19 | } 20 | } 21 | 22 | public class EchoActor : ReceiveActor 23 | { 24 | private readonly string _entityId; 25 | private readonly IReplyGenerator _replyGenerator; 26 | public EchoActor(string entityId, IReplyGenerator replyGenerator) 27 | { 28 | _entityId = entityId; 29 | _replyGenerator = replyGenerator; 30 | ReceiveAny(message => { 31 | Sender.Tell($"{Self} rcv {_replyGenerator.Reply(message)}"); 32 | }); 33 | } 34 | } 35 | 36 | public class Program 37 | { 38 | private const int NumberOfShards = 5; 39 | 40 | private static IMessageExtractor Extractor { get; } = HashCodeMessageExtractor.Create(NumberOfShards, msg => 41 | { 42 | if (msg is string id) 43 | return id; 44 | return string.Empty; 45 | }); 46 | 47 | public static void Main(params string[] args) 48 | { 49 | var builder = WebApplication.CreateBuilder(args); 50 | 51 | builder.Services.AddTransient(); 52 | builder.Services.AddAkka("MyActorSystem", configurationBuilder => 53 | { 54 | configurationBuilder 55 | .WithRemoting(hostname: "localhost", port: 8110) 56 | .WithClustering(new ClusterOptions{SeedNodes = new []{ "akka.tcp://MyActorSystem@localhost:8110", }}) 57 | .WithShardRegion( 58 | typeName: "myRegion", 59 | entityPropsFactory: (_, _, resolver) => 60 | { 61 | return s => resolver.Props(s); 62 | }, 63 | messageExtractor: Extractor, 64 | shardOptions: new ShardOptions()); 65 | }); 66 | 67 | var app = builder.Build(); 68 | 69 | app.MapGet("/", async (HttpContext context, IRequiredActor echoActor) => 70 | { 71 | var echo = echoActor.ActorRef; 72 | var body = await echo.Ask( 73 | message: context.TraceIdentifier, 74 | cancellationToken: context.RequestAborted) 75 | .ConfigureAwait(false); 76 | await context.Response.WriteAsync(body); 77 | }); 78 | 79 | app.Run(); 80 | } 81 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/XUnitLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using Xunit.Abstractions; 4 | 5 | namespace Akka.Hosting.Tests; 6 | 7 | public class XUnitLogger: ILogger 8 | { 9 | private const string NullFormatted = "[null]"; 10 | 11 | private readonly string _category; 12 | private readonly ITestOutputHelper _helper; 13 | private readonly LogLevel _logLevel; 14 | 15 | public XUnitLogger(string category, ITestOutputHelper helper, LogLevel logLevel) 16 | { 17 | _category = category; 18 | _helper = helper; 19 | _logLevel = logLevel; 20 | } 21 | 22 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) 23 | { 24 | if (!IsEnabled(logLevel)) 25 | return; 26 | 27 | if (!TryFormatMessage(state, exception, formatter, out var formattedMessage)) 28 | return; 29 | 30 | WriteLogEntry(logLevel, eventId, formattedMessage, exception); 31 | } 32 | 33 | private void WriteLogEntry(LogLevel logLevel, EventId eventId, string? message, Exception? exception) 34 | { 35 | var level = logLevel switch 36 | { 37 | LogLevel.Critical => "CRT", 38 | LogLevel.Debug => "DBG", 39 | LogLevel.Error => "ERR", 40 | LogLevel.Information => "INF", 41 | LogLevel.Warning => "WRN", 42 | LogLevel.Trace => "DBG", 43 | _ => "???" 44 | }; 45 | 46 | var msg = $"{DateTime.Now}:{level}:{_category}:{eventId} {message}"; 47 | if (exception != null) 48 | msg += $"\n{exception.GetType()} {exception.Message}\n{exception.StackTrace}"; 49 | _helper.WriteLine(msg); 50 | } 51 | 52 | public bool IsEnabled(LogLevel logLevel) 53 | { 54 | return logLevel switch 55 | { 56 | LogLevel.None => false, 57 | _ => logLevel >= _logLevel 58 | }; 59 | } 60 | 61 | public IDisposable BeginScope(TState state) 62 | { 63 | throw new NotImplementedException(); 64 | } 65 | 66 | private static bool TryFormatMessage( 67 | TState state, 68 | Exception? exception, 69 | Func formatter, 70 | out string? result) 71 | { 72 | formatter = formatter ?? throw new ArgumentNullException(nameof(formatter)); 73 | 74 | var formattedMessage = formatter(state, exception); 75 | if (formattedMessage == NullFormatted) 76 | { 77 | result = null; 78 | return false; 79 | } 80 | 81 | result = formattedMessage; 82 | return true; 83 | } 84 | } -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting.Tests/ClusterSingletonWithDiSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Akka.Actor; 4 | using Akka.Hosting; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace Akka.Cluster.Hosting.Tests; 11 | 12 | public class ClusterSingletonWithDiSpecs : Akka.Hosting.TestKit.TestKit 13 | { 14 | #region Actor and DI impls 15 | 16 | 17 | public interface IMyThing 18 | { 19 | string ThingId { get; } 20 | } 21 | 22 | public sealed class ThingImpl : IMyThing 23 | { 24 | public ThingImpl(string thingId) 25 | { 26 | ThingId = thingId; 27 | } 28 | 29 | public string ThingId { get; } 30 | } 31 | 32 | private class MySingletonDiActor : ReceiveActor 33 | { 34 | private readonly IMyThing _thing; 35 | 36 | public MySingletonDiActor(IMyThing thing) 37 | { 38 | _thing = thing; 39 | ReceiveAny(_ => Sender.Tell(_thing.ThingId)); 40 | } 41 | } 42 | 43 | #endregion 44 | 45 | private readonly TaskCompletionSource _tcs = new(TimeSpan.FromSeconds(3)); 46 | 47 | public ClusterSingletonWithDiSpecs(ITestOutputHelper output) : base(output: output) 48 | { 49 | } 50 | 51 | protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) 52 | { 53 | services.AddSingleton(new ThingImpl("foo1")); 54 | base.ConfigureServices(context, services); 55 | } 56 | 57 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 58 | { 59 | builder.ConfigureHost(configurationBuilder => 60 | { 61 | configurationBuilder.WithSingleton("my-singleton", 62 | (_, _, dependencyResolver) => dependencyResolver.Props()); 63 | }, new ClusterOptions(){ Roles = new[] { "my-host" }}, _tcs, Output!); 64 | } 65 | 66 | [Fact] 67 | public async Task Should_launch_ClusterSingletonAndProxy_with_DI_delegate() 68 | { 69 | // arrange 70 | await _tcs.Task; // wait for cluster to start 71 | 72 | var registry = Host.Services.GetRequiredService(); 73 | var singletonProxy = registry.Get(); 74 | var thing = Host.Services.GetRequiredService(); 75 | 76 | // act 77 | 78 | // verify round-trip to the singleton proxy and back 79 | var respond = await singletonProxy.Ask("hit", TimeSpan.FromSeconds(3)); 80 | 81 | // assert 82 | Assert.Equal(thing.ThingId, respond); 83 | } 84 | } -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting.Tests/XUnitLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using Xunit.Abstractions; 4 | 5 | namespace Akka.Cluster.Hosting.Tests; 6 | 7 | public class XUnitLogger: ILogger 8 | { 9 | private const string NullFormatted = "[null]"; 10 | 11 | private readonly string _category; 12 | private readonly ITestOutputHelper _helper; 13 | private readonly LogLevel _logLevel; 14 | 15 | public XUnitLogger(string category, ITestOutputHelper helper, LogLevel logLevel) 16 | { 17 | _category = category; 18 | _helper = helper; 19 | _logLevel = logLevel; 20 | } 21 | 22 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) 23 | { 24 | if (!IsEnabled(logLevel)) 25 | return; 26 | 27 | if (!TryFormatMessage(state, exception, formatter, out var formattedMessage)) 28 | return; 29 | 30 | WriteLogEntry(logLevel, eventId, formattedMessage, exception); 31 | } 32 | 33 | private void WriteLogEntry(LogLevel logLevel, EventId eventId, string? message, Exception? exception) 34 | { 35 | var level = logLevel switch 36 | { 37 | LogLevel.Critical => "CRT", 38 | LogLevel.Debug => "DBG", 39 | LogLevel.Error => "ERR", 40 | LogLevel.Information => "INF", 41 | LogLevel.Warning => "WRN", 42 | LogLevel.Trace => "DBG", 43 | _ => "???" 44 | }; 45 | 46 | var msg = $"{DateTime.Now}:{level}:{_category}:{eventId} {message}"; 47 | if (exception != null) 48 | msg += $"\n{exception.GetType()} {exception.Message}\n{exception.StackTrace}"; 49 | _helper.WriteLine(msg); 50 | } 51 | 52 | public bool IsEnabled(LogLevel logLevel) 53 | { 54 | return logLevel switch 55 | { 56 | LogLevel.None => false, 57 | _ => logLevel >= _logLevel 58 | }; 59 | } 60 | 61 | public IDisposable BeginScope(TState state) 62 | { 63 | throw new NotImplementedException(); 64 | } 65 | 66 | private static bool TryFormatMessage( 67 | TState state, 68 | Exception? exception, 69 | Func formatter, 70 | out string? result) 71 | { 72 | formatter = formatter ?? throw new ArgumentNullException(nameof(formatter)); 73 | 74 | var formattedMessage = formatter(state, exception); 75 | if (formattedMessage == NullFormatted) 76 | { 77 | result = null; 78 | return false; 79 | } 80 | 81 | result = formattedMessage; 82 | return true; 83 | } 84 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/Logging/TestLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Extensions.Logging; 4 | using Xunit.Abstractions; 5 | 6 | namespace Akka.Hosting.Tests.Logging; 7 | 8 | public class TestLogger : ILogger 9 | { 10 | private readonly ITestOutputHelper _helper; 11 | public bool Recording { get; private set; } 12 | private string? _stopsWhen; 13 | 14 | public readonly List Debugs = new(); 15 | public readonly List Infos = new(); 16 | public readonly List Warnings = new(); 17 | public readonly List Errors = new(); 18 | 19 | public TestLogger(ITestOutputHelper helper) 20 | { 21 | _helper = helper; 22 | } 23 | 24 | public int TotalLogs => Debugs.Count + Infos.Count + Warnings.Count + Errors.Count; 25 | 26 | public int ReceivedLogs { get; private set; } 27 | 28 | public void StartRecording() 29 | { 30 | _helper.WriteLine("Logger starts recording"); 31 | Recording = true; 32 | } 33 | 34 | public void StopWhenReceives(string message) 35 | { 36 | _stopsWhen = message; 37 | } 38 | 39 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, 40 | Func formatter) 41 | { 42 | var message = formatter(state, exception); 43 | _helper.WriteLine($"[{logLevel}] {message}"); 44 | ReceivedLogs++; 45 | 46 | if (!Recording) 47 | return; 48 | 49 | if (!string.IsNullOrEmpty(_stopsWhen) && message.Contains(_stopsWhen)) 50 | { 51 | _helper.WriteLine("Logger stops recording"); 52 | Recording = false; 53 | } 54 | 55 | switch (logLevel) 56 | { 57 | case LogLevel.Debug: 58 | Debugs.Add(message); 59 | break; 60 | case LogLevel.Information: 61 | Infos.Add(message); 62 | break; 63 | case LogLevel.Warning: 64 | Warnings.Add(message); 65 | break; 66 | case LogLevel.Error: 67 | Errors.Add(message); 68 | break; 69 | default: 70 | throw new Exception($"Unsupported LogLevel: {logLevel}"); 71 | } 72 | } 73 | 74 | public bool IsEnabled(LogLevel logLevel) 75 | { 76 | return true; 77 | } 78 | 79 | public IDisposable BeginScope(TState state) 80 | { 81 | return EmptyDisposable.Instance; 82 | } 83 | } 84 | 85 | public class EmptyDisposable : IDisposable 86 | { 87 | public static readonly EmptyDisposable Instance = new EmptyDisposable(); 88 | 89 | private EmptyDisposable() 90 | { 91 | } 92 | 93 | #pragma warning disable CA1816 94 | public void Dispose() 95 | #pragma warning restore CA1816 96 | { 97 | } 98 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | Copyright © 2013-$([System.DateTime]::Now.Year) Akka.NET Team 4 | Akka.NET Team 5 | 1.5.57 6 | **New Features** 7 | * [Add semantic logging support for Akka.NET 1.5.56+](https://github.com/akkadotnet/Akka.Hosting/pull/693) - enables Microsoft.Extensions.Logging to receive properly structured state dictionaries instead of pre-formatted strings. When using Akka.NET 1.5.56+, log messages now include structured properties from the semantic logging API along with Akka metadata (ActorPath, Timestamp, Thread, LogSource). Fully backwards compatible with older Akka.NET versions. 8 | **Updates** 9 | * [Bump Akka version from 1.5.55 to 1.5.57](https://github.com/akkadotnet/akka.net/releases/tag/1.5.57) 10 | akkalogo.png 11 | 12 | https://github.com/akkadotnet/Akka.Hosting 13 | 14 | Apache-2.0 15 | $(NoWarn);CS1591 16 | README.md 17 | 18 | 19 | true 20 | 12.0 21 | enable 22 | 23 | 24 | netstandard2.0 25 | net6.0 26 | net8.0 27 | 2.8.1 28 | 17.11.1 29 | 6.0.3 30 | 3.1.5 31 | 1.5.57 32 | [6.0.0,) 33 | [6.0.10,) 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | true 45 | 46 | true 47 | 48 | true 49 | snupkg 50 | 51 | -------------------------------------------------------------------------------- /src/Akka.Remote.Hosting/README.md: -------------------------------------------------------------------------------- 1 | # Akka Remoting Akka.Hosting Extensions 2 | 3 | ## WithRemoting() Method 4 | 5 | An extension method to add [Akka.Remote](https://getakka.net/articles/remoting/index.html) support to the `ActorSystem`. 6 | 7 | ```csharp 8 | public static AkkaConfigurationBuilder WithRemoting( 9 | this AkkaConfigurationBuilder builder, 10 | string hostname = null, 11 | int? port = null, 12 | string publicHostname = null, 13 | int? publicPort = null); 14 | ``` 15 | 16 | ### Parameters 17 | * `hostname` __string__ 18 | 19 | Optional. The hostname to bind Akka.Remote upon. 20 | 21 | __Default__: `IPAddress.Any` or "0.0.0.0" 22 | 23 | * `port` __int?__ 24 | 25 | Optional. The port to bind Akka.Remote upon. 26 | 27 | __Default__: 2552 28 | 29 | * `publicHostname` __string__ 30 | 31 | Optional. If using hostname aliasing, this is the host we will advertise. 32 | 33 | __Default__: Fallback to `hostname` 34 | 35 | * `publicPort` __int?__ 36 | 37 | Optional. If using port aliasing, this is the port we will advertise. 38 | 39 | __Default__: Fallback to `port` 40 | 41 | ### Example 42 | 43 | ```csharp 44 | using var host = new HostBuilder() 45 | .ConfigureServices((context, services) => 46 | { 47 | services.AddAkka("remotingDemo", (builder, provider) => 48 | { 49 | builder.WithRemoting("127.0.0.1", 4053); 50 | }); 51 | }).Build(); 52 | 53 | await host.RunAsync(); 54 | ``` 55 | 56 | ## SSL/TLS Configuration 57 | 58 | Akka.Remote supports SSL/TLS encryption for secure communication between actor systems. Starting with Akka.NET v1.5.55, you can provide custom certificate validation callbacks using the `CertificateValidation` helper class. 59 | 60 | ```csharp 61 | using System.Security.Cryptography.X509Certificates; 62 | using Akka.Remote.Transport.DotNetty; 63 | 64 | var certificate = new X509Certificate2("/path/to/certificate.pfx", "certificate-password"); 65 | 66 | using var host = new HostBuilder() 67 | .ConfigureServices((context, services) => 68 | { 69 | services.AddAkka("secureSystem", (builder, provider) => 70 | { 71 | builder.WithRemoting(options => 72 | { 73 | options.HostName = "127.0.0.1"; 74 | options.Port = 4053; 75 | options.EnableSsl = true; 76 | options.Ssl.X509Certificate = certificate; 77 | 78 | // Use built-in validators for common scenarios 79 | options.Ssl.CustomValidator = CertificateValidation.Combine( 80 | CertificateValidation.ValidateChain(), 81 | CertificateValidation.ValidateSubject("CN=*.mycompany.com") 82 | ); 83 | }); 84 | }); 85 | }).Build(); 86 | 87 | await host.RunAsync(); 88 | ``` 89 | 90 | Available `CertificateValidation` methods: `ValidateChain()`, `ValidateHostname()`, `PinnedCertificate()`, `ValidateSubject()`, `ValidateIssuer()`, and `Combine()`. 91 | -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/LoggerSpec.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2022 Lightbend Inc. 4 | // Copyright (C) 2013-2022 .NET Foundation 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Threading.Tasks; 10 | using Akka.Actor; 11 | using Akka.Event; 12 | using Akka.Hosting.TestKit.Internals; 13 | using Xunit; 14 | using Xunit.Abstractions; 15 | using LogLevel = Microsoft.Extensions.Logging.LogLevel; 16 | 17 | namespace Akka.Hosting.TestKit.Tests; 18 | 19 | public class LoggerSpec: TestKit 20 | { 21 | public LoggerSpec(ITestOutputHelper output): base(output: output, logLevel: LogLevel.Debug) 22 | { 23 | } 24 | 25 | internal override async Task LoggerHook(ActorSystem system, IActorRegistry registry) 26 | { 27 | var extSystem = (ExtendedActorSystem)system; 28 | var logger = extSystem.SystemActorOf(Props.Create(() => new MockLogger()), "log-test"); 29 | registry.Register(logger); 30 | await logger.Ask(new InitializeLogger(system.EventStream)); 31 | } 32 | 33 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 34 | { 35 | } 36 | 37 | [Fact(DisplayName = "TestKit ILoggerFactory logger should log messages")] 38 | public void TestKitLoggerFactoryLoggerTest() 39 | { 40 | var loggerActor = ActorRegistry.Get(); 41 | loggerActor.Tell(TestActor); 42 | 43 | var logger = Event.Logging.GetLogger(Sys, "log-test"); 44 | 45 | logger.Debug("debug"); 46 | ExpectMsg(i => i.Message.ToString() == "debug"); 47 | 48 | logger.Info("info"); 49 | ExpectMsg(i => i.Message.ToString() == "info"); 50 | 51 | logger.Warning("warn"); 52 | ExpectMsg(i => i.Message.ToString() == "warn"); 53 | 54 | logger.Error("err"); 55 | ExpectMsg(i => i.Message.ToString() == "err"); 56 | } 57 | 58 | private class MockLogger: TestKitLoggerFactoryLogger 59 | { 60 | private IActorRef? _probe; 61 | 62 | protected override bool Receive(object message) 63 | { 64 | switch (message) 65 | { 66 | case IActorRef actor: 67 | _probe = actor; 68 | return true; 69 | default: 70 | return base.Receive(message); 71 | } 72 | } 73 | 74 | protected override void Log(LogEvent log, ActorPath path) 75 | { 76 | if(log.LogSource.StartsWith("log-test")) 77 | _probe.Tell(log); 78 | base.Log(log, path); 79 | } 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting.Tests/TestHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Akka.Actor; 7 | using Akka.Event; 8 | using Akka.Hosting; 9 | using Akka.Remote.Hosting; 10 | using Akka.TestKit.Xunit2.Internals; 11 | using Microsoft.Extensions.Hosting; 12 | using Xunit; 13 | using Xunit.Abstractions; 14 | 15 | namespace Akka.Cluster.Hosting.Tests; 16 | 17 | public static class TestHelper 18 | { 19 | 20 | public static void ConfigureHost(this AkkaConfigurationBuilder builder, 21 | Action specBuilder, 22 | ClusterOptions options, TaskCompletionSource tcs, ITestOutputHelper output) 23 | { 24 | builder 25 | .WithRemoting("localhost", 0) 26 | .WithClustering(options) 27 | .WithActors((system, registry) => 28 | { 29 | var extSystem = (ExtendedActorSystem)system; 30 | var logger = extSystem.SystemActorOf(Props.Create(() => new TestOutputLogger(output))); 31 | logger.Tell(new InitializeLogger(system.EventStream)); 32 | }) 33 | .AddStartup(async (system, registry) => 34 | { 35 | var cluster = Cluster.Get(system); 36 | cluster.RegisterOnMemberUp(tcs.SetResult); 37 | if (options.SeedNodes == null || options.SeedNodes.Length == 0) 38 | { 39 | var myAddress = cluster.SelfAddress; 40 | await cluster.JoinAsync(myAddress); // force system to wait until we're up 41 | } 42 | }); 43 | specBuilder(builder); 44 | } 45 | 46 | public static async Task CreateHost(Action specBuilder, ClusterOptions options, ITestOutputHelper output) 47 | { 48 | var tcs = new TaskCompletionSource(); 49 | using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10)); 50 | 51 | var host = new HostBuilder() 52 | .ConfigureServices(collection => 53 | { 54 | collection.AddAkka("TestSys", (configurationBuilder, provider) => 55 | { 56 | configurationBuilder.ConfigureHost(specBuilder, options, tcs, output); 57 | }); 58 | }).Build(); 59 | 60 | await host.StartAsync(cancellationTokenSource.Token); 61 | await (tcs.Task.WaitAsync(cancellationTokenSource.Token)); 62 | 63 | return host; 64 | } 65 | 66 | public static TimeSpan Seconds(this double value) 67 | => TimeSpan.FromSeconds(value); 68 | 69 | public static TimeSpan Seconds(this int value) 70 | => TimeSpan.FromSeconds(value); 71 | 72 | public static TimeSpan Milliseconds(this double value) 73 | => TimeSpan.FromMilliseconds(value); 74 | 75 | public static TimeSpan Milliseconds(this int value) 76 | => TimeSpan.FromMilliseconds(value); 77 | 78 | public static void CollectionEquals(this IEnumerable list1, IEnumerable list2) 79 | { 80 | Assert.Equal(list1.OrderBy(a => a), list2.OrderBy(a => a)); 81 | } 82 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestFSMRefTests/TestFSMRefSpec.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Threading.Tasks; 10 | using Akka.Actor; 11 | using FluentAssertions; 12 | using Xunit; 13 | 14 | namespace Akka.Hosting.TestKit.Tests.TestFSMRefTests; 15 | 16 | public class TestFSMRefSpec : TestKit 17 | { 18 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 19 | { 20 | 21 | } 22 | 23 | [Fact] 24 | public void A_TestFSMRef_must_allow_access_to_internal_state() 25 | { 26 | var fsm = ActorOfAsTestFSMRef("test-fsm-ref-1"); 27 | 28 | fsm.StateName.Should().Be(1); 29 | fsm.StateData.Should().Be(""); 30 | 31 | fsm.Tell("go"); 32 | fsm.StateName.Should().Be(2); 33 | fsm.StateData.Should().Be("go"); 34 | 35 | fsm.SetState(1); 36 | fsm.StateName.Should().Be(1); 37 | fsm.StateData.Should().Be("go"); 38 | 39 | fsm.SetStateData("buh"); 40 | fsm.StateName.Should().Be(1); 41 | fsm.StateData.Should().Be("buh"); 42 | 43 | fsm.SetStateTimeout(TimeSpan.FromMilliseconds(100)); 44 | Within(TimeSpan.FromMilliseconds(80), TimeSpan.FromMilliseconds(500), () => 45 | AwaitCondition(() => fsm is { StateName: 2, StateData: "timeout" }) 46 | ); 47 | } 48 | 49 | [Fact] 50 | public void A_TestFSMRef_must_allow_access_to_timers() 51 | { 52 | var fsm = ActorOfAsTestFSMRef("test-fsm-ref-2"); 53 | fsm.IsTimerActive("test").Should().Be(false); 54 | fsm.SetTimer("test", 12, TimeSpan.FromMilliseconds(10), true); 55 | fsm.IsTimerActive("test").Should().Be(true); 56 | fsm.CancelTimer("test"); 57 | fsm.IsTimerActive("test").Should().Be(false); 58 | } 59 | 60 | private class StateTestFsm : FSM 61 | { 62 | public StateTestFsm() 63 | { 64 | StartWith(1, ""); 65 | When(1, e => 66 | { 67 | var fsmEvent = e.FsmEvent; 68 | if(Equals(fsmEvent, "go")) 69 | return GoTo(2).Using("go"); 70 | if(fsmEvent is StateTimeout) 71 | return GoTo(2).Using("timeout"); 72 | return null; 73 | }); 74 | When(2, e => 75 | { 76 | var fsmEvent = e.FsmEvent; 77 | if(Equals(fsmEvent, "back")) 78 | return GoTo(1).Using("back"); 79 | return null; 80 | }); 81 | } 82 | } 83 | private class TimerTestFsm : FSM 84 | { 85 | public TimerTestFsm() 86 | { 87 | StartWith(1, ""); 88 | When(1, e => Stay()); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestActorRefTests/SnapshotActor.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2022 Lightbend Inc. 4 | // Copyright (C) 2013-2025 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | namespace Akka.Persistence.TestKit.Tests 9 | { 10 | using System; 11 | using Actor; 12 | 13 | public class SnapshotActor : UntypedPersistentActor 14 | { 15 | public SnapshotActor(IActorRef probe) 16 | { 17 | _probe = probe; 18 | } 19 | 20 | private readonly IActorRef _probe; 21 | 22 | public override string PersistenceId => "bar"; 23 | 24 | protected override void OnCommand(object message) 25 | { 26 | switch (message) 27 | { 28 | case "save": 29 | SaveSnapshot(message); 30 | return; 31 | 32 | case DeleteOne del: 33 | DeleteSnapshot(del.SequenceNr); 34 | return; 35 | 36 | case DeleteMany del: 37 | DeleteSnapshots(del.Criteria); 38 | return; 39 | 40 | case SaveSnapshotSuccess _: 41 | case SaveSnapshotFailure _: 42 | case DeleteSnapshotSuccess _: 43 | case DeleteSnapshotFailure _: 44 | case DeleteSnapshotsSuccess _: 45 | case DeleteSnapshotsFailure _: 46 | _probe.Tell(message); 47 | return; 48 | 49 | default: 50 | return; 51 | } 52 | } 53 | 54 | protected override void OnRecover(object message) 55 | { 56 | if (message is SnapshotOffer snapshot) 57 | { 58 | _probe.Tell(message); 59 | } 60 | } 61 | 62 | protected override void OnRecoveryFailure(Exception reason, object message) 63 | { 64 | _probe.Tell(new RecoveryFailure(reason, message)); 65 | base.OnRecoveryFailure(reason, message); 66 | } 67 | 68 | public class DeleteOne 69 | { 70 | public DeleteOne(long sequenceNr) 71 | { 72 | SequenceNr = sequenceNr; 73 | } 74 | 75 | public long SequenceNr { get; } 76 | } 77 | 78 | public class DeleteMany 79 | { 80 | public DeleteMany(SnapshotSelectionCriteria criteria) 81 | { 82 | Criteria = criteria; 83 | } 84 | 85 | public SnapshotSelectionCriteria Criteria { get; } 86 | } 87 | 88 | public class RecoveryFailure 89 | { 90 | public RecoveryFailure(Exception reason, object message) 91 | { 92 | Reason = reason; 93 | Message = message; 94 | } 95 | 96 | public Exception Reason { get; } 97 | public object Message { get; } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/Logging/LoggerConfigEnd2EndSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Akka.Actor; 5 | using Akka.Event; 6 | using Akka.Hosting.Logging; 7 | using FluentAssertions; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | using Xunit; 11 | using Xunit.Abstractions; 12 | using static Akka.Hosting.Tests.TestHelpers; 13 | 14 | namespace Akka.Hosting.Tests.Logging; 15 | 16 | public class LoggerConfigEnd2EndSpecs : Akka.TestKit.Xunit2.TestKit 17 | { 18 | private class CustomLoggingProvider : ILoggerProvider 19 | { 20 | private readonly TestLogger _logger; 21 | 22 | public bool Created { get; private set; } 23 | 24 | public CustomLoggingProvider(TestLogger logger) 25 | { 26 | _logger = logger; 27 | } 28 | 29 | public void Dispose() 30 | { 31 | } 32 | 33 | public ILogger CreateLogger(string categoryName) 34 | { 35 | if (categoryName.Contains(nameof(ActorSystem))) 36 | { 37 | Created = true; 38 | _logger.LogInformation("ActorSystem logger created."); 39 | } 40 | return _logger; 41 | } 42 | } 43 | 44 | private readonly ITestOutputHelper _output; 45 | private readonly TestLogger _logger; 46 | 47 | public LoggerConfigEnd2EndSpecs(ITestOutputHelper output) 48 | { 49 | _output = output; 50 | _logger = new TestLogger(output); 51 | } 52 | 53 | [Fact] 54 | public async Task Should_configure_LoggerFactoryLogger() 55 | { 56 | var loggingProvider = new CustomLoggingProvider(_logger); 57 | 58 | // arrange 59 | using var host = await StartHost(collection => 60 | { 61 | collection.AddLogging(builder => { builder.AddProvider(loggingProvider); }); 62 | 63 | collection.AddAkka("MySys", (builder, provider) => 64 | { 65 | builder.ConfigureLoggers(configBuilder => { configBuilder.AddLogger(); }); 66 | builder.AddTestOutputLogger(_output); 67 | }); 68 | }); 69 | 70 | // Make sure that the logger has already been created 71 | await AwaitConditionAsync(() => Task.FromResult(loggingProvider.Created)); 72 | var actorSystem = host.Services.GetRequiredService(); 73 | 74 | // act 75 | _logger.StartRecording(); 76 | actorSystem.Log.Info("foo"); 77 | 78 | // assert 79 | await AwaitAssertAsync(() => 80 | _logger.Infos.Where(c => c.Contains("foo")).Should().HaveCount(1)); 81 | } 82 | 83 | [Fact] 84 | public async Task Should_ActorSystem_without_LoggerFactoryLogger() 85 | { 86 | // arrange 87 | using var host = await StartHost(collection => 88 | { 89 | collection.AddAkka("MySys", (builder, provider) => { builder.AddTestOutputLogger(_output); }); 90 | }); 91 | 92 | Action getActorSystem = () => 93 | { 94 | var actorSystem = host.Services.GetRequiredService(); 95 | }; 96 | 97 | 98 | // act 99 | getActorSystem.Should().NotThrow(); 100 | } 101 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/UtilSpec.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2022 Lightbend Inc. 4 | // Copyright (C) 2013-2022 .NET Foundation 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | using System; 9 | using Akka.Configuration; 10 | using FluentAssertions; 11 | using Xunit; 12 | using static FluentAssertions.FluentActions; 13 | 14 | namespace Akka.Hosting.Tests; 15 | 16 | public class UtilSpec 17 | { 18 | [Fact(DisplayName = "Config.MoveTo() should move config to a new path properly")] 19 | public void MoveToSpec() 20 | { 21 | var hocon = (Config)"a: 1, b: { c: 2 }"; 22 | var moved = hocon.MoveTo("x.y.z"); 23 | moved.GetInt("x.y.z.a").Should().Be(1); 24 | moved.GetInt("x.y.z.b.c").Should().Be(2); 25 | } 26 | 27 | [Fact(DisplayName = "HoconExtensions TimeSpan.ToHocon should work properly")] 28 | public void TimeSpanToHoconSpec() 29 | { 30 | TimeSpan? nullTs = null; 31 | Invoking(() => nullTs.ToHocon()).Should() 32 | .Throw() 33 | .WithMessage("Value can not be null", "Null value is not allowed"); 34 | 35 | var ts = TimeSpan.Zero; 36 | ts.ToHocon().Should().Be("0"); 37 | ts.ToHocon(true, false).Should().Be("0"); 38 | ts.ToHocon(true, true).Should().Be("infinite"); 39 | ts.ToHocon(false, false).Should().Be("0"); 40 | Invoking(() => ts.ToHocon(false, true)).Should() 41 | .Throw("Infinite value is not allowed", "Infinite value is not allowed, zero is considered as infinite"); 42 | 43 | ts = TimeSpan.FromTicks(-1); 44 | ts.ToHocon(true, false).Should().Be("infinite"); 45 | ts.ToHocon(true, true).Should().Be("infinite"); 46 | Invoking(() => ts.ToHocon(false, true)).Should() 47 | .Throw("Infinite value is not allowed", "Infinite value is not allowed"); 48 | Invoking(() => ts.ToHocon(false, false)).Should() 49 | .Throw("Infinite value is not allowed", "Infinite value is not allowed"); 50 | 51 | } 52 | 53 | [InlineData("$")] 54 | [InlineData("\"")] 55 | [InlineData("{")] 56 | [InlineData("}")] 57 | [InlineData("[")] 58 | [InlineData("]")] 59 | [InlineData(":")] 60 | [InlineData("=")] 61 | [InlineData(",")] 62 | [InlineData("#")] 63 | [InlineData("`")] 64 | [InlineData("^")] 65 | [InlineData("?")] 66 | [InlineData("!")] 67 | [InlineData("@")] 68 | [InlineData("*")] 69 | [InlineData("&")] 70 | [InlineData("\\")] 71 | [Theory(DisplayName = "HoconExtensions String.ToHocon should put illegal characters in quotes")] 72 | public void StringToHoconTest(string input) 73 | { 74 | var result = input.ToHocon(); 75 | 76 | switch (input) 77 | { 78 | case "\"": 79 | // special case for quote 80 | result.Length.Should().Be(4); 81 | break; 82 | case "\\": 83 | // special case for backslash 84 | result.Length.Should().Be(4); 85 | break; 86 | default: 87 | result.Length.Should().Be(3); 88 | break; 89 | } 90 | 91 | result.Should().StartWith("\"").And.EndWith("\""); 92 | } 93 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestKitBaseTests/DilatedTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Diagnostics; 10 | using Akka.Configuration; 11 | using Xunit; 12 | using Xunit.Sdk; 13 | using FluentAssertions; 14 | using static FluentAssertions.FluentActions; 15 | 16 | namespace Akka.Hosting.TestKit.Tests.TestKitBaseTests; 17 | 18 | public class DilatedTests : TestKit 19 | { 20 | private const int TimeFactor = 4; 21 | private const int Timeout = 1000; 22 | private const int ExpectedTimeout = Timeout * TimeFactor; 23 | private const int Margin = 1000; // margin for GC 24 | private const int DiffDelta = 100; 25 | 26 | protected override Config Config { get; } = $"akka.test.timefactor={TimeFactor}"; 27 | 28 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 29 | { 30 | } 31 | 32 | [Fact] 33 | public void Dilates_correctly_using_timeFactor() 34 | { 35 | Assert.Equal(Dilated(TimeSpan.FromMilliseconds(Timeout)), TimeSpan.FromMilliseconds(ExpectedTimeout)); 36 | } 37 | 38 | [Fact] 39 | public void AwaitCondition_should_dilate_timeout() 40 | { 41 | var stopwatch = Stopwatch.StartNew(); 42 | Invoking(() => AwaitCondition(() => false, TimeSpan.FromMilliseconds(Timeout))) 43 | .Should().Throw(); 44 | stopwatch.Stop(); 45 | AssertDilated(stopwatch.ElapsedMilliseconds, $"Expected the timeout to be {ExpectedTimeout} but in fact it was {stopwatch.ElapsedMilliseconds}."); 46 | } 47 | 48 | [Fact] 49 | public void ReceiveN_should_dilate_timeout() 50 | { 51 | var stopwatch = Stopwatch.StartNew(); 52 | Invoking(() => ReceiveN(42, TimeSpan.FromMilliseconds(Timeout))) 53 | .Should().Throw(); 54 | stopwatch.Stop(); 55 | AssertDilated(stopwatch.ElapsedMilliseconds, $"Expected the timeout to be {ExpectedTimeout} but in fact it was {stopwatch.ElapsedMilliseconds}."); 56 | } 57 | 58 | [Fact] 59 | public void ExpectMsgAllOf_should_dilate_timeout() 60 | { 61 | var stopwatch = Stopwatch.StartNew(); 62 | Invoking(() => ExpectMsgAllOf(TimeSpan.FromMilliseconds(Timeout), new[]{"1", "2"} )) 63 | .Should().Throw(); 64 | stopwatch.Stop(); 65 | AssertDilated(stopwatch.ElapsedMilliseconds, $"Expected the timeout to be {ExpectedTimeout} but in fact it was {stopwatch.ElapsedMilliseconds}."); 66 | } 67 | 68 | [Fact] 69 | public void FishForMessage_should_dilate_timeout() 70 | { 71 | var stopwatch = Stopwatch.StartNew(); 72 | Invoking(() => FishForMessage(_=>false, TimeSpan.FromMilliseconds(Timeout))) 73 | .Should().Throw(); 74 | stopwatch.Stop(); 75 | AssertDilated(stopwatch.ElapsedMilliseconds, $"Expected the timeout to be {ExpectedTimeout} but in fact it was {stopwatch.ElapsedMilliseconds}."); 76 | } 77 | 78 | private static void AssertDilated(double diff, string? message = null) 79 | { 80 | Assert.True(diff >= ExpectedTimeout - DiffDelta, message); 81 | Assert.True(diff < ExpectedTimeout + Margin, message); // margin for GC 82 | } 83 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.API.Tests/verify/CoreApiSpec.ApproveRemoting.verified.txt: -------------------------------------------------------------------------------- 1 | namespace Akka.Remote.Hosting 2 | { 3 | public static class AkkaRemoteHostingExtensions 4 | { 5 | public static Akka.Hosting.AkkaConfigurationBuilder WithRemoting(this Akka.Hosting.AkkaConfigurationBuilder builder, Akka.Remote.Hosting.RemoteOptions options) { } 6 | public static Akka.Hosting.AkkaConfigurationBuilder WithRemoting(this Akka.Hosting.AkkaConfigurationBuilder builder, System.Action configure) { } 7 | public static Akka.Hosting.AkkaConfigurationBuilder WithRemoting(this Akka.Hosting.AkkaConfigurationBuilder builder, string? hostname = null, int? port = default, string? publicHostname = null, int? publicPort = default) { } 8 | } 9 | public class DeadlineFailureDetectorOptions 10 | { 11 | public DeadlineFailureDetectorOptions() { } 12 | public System.TimeSpan? AcceptableHeartbeatPause { get; set; } 13 | public System.TimeSpan? HeartbeatInterval { get; set; } 14 | public System.Text.StringBuilder ToHocon() { } 15 | } 16 | public class PhiAccrualFailureDetectorOptions 17 | { 18 | public PhiAccrualFailureDetectorOptions() { } 19 | public System.TimeSpan? AcceptableHeartbeatPause { get; set; } 20 | public System.TimeSpan? ExpectedResponseAfter { get; set; } 21 | public System.TimeSpan? HeartbeatInterval { get; set; } 22 | public int? MaxSampleSize { get; set; } 23 | public System.TimeSpan? MinStandardDeviation { get; set; } 24 | public double? Threshold { get; set; } 25 | public System.TimeSpan? UnreachableNodesReaperInterval { get; set; } 26 | public System.Text.StringBuilder ToHocon() { } 27 | } 28 | public class RemoteOptions 29 | { 30 | public RemoteOptions() { } 31 | public bool? EnableSsl { get; set; } 32 | public string? HostName { get; set; } 33 | public long? MaxFrameSize { get; set; } 34 | public int? Port { get; set; } 35 | public string? PublicHostName { get; set; } 36 | public int? PublicPort { get; set; } 37 | public long? ReceiveBufferSize { get; set; } 38 | public long? SendBufferSize { get; set; } 39 | public Akka.Remote.Hosting.SslOptions Ssl { get; set; } 40 | public Akka.Remote.Hosting.DeadlineFailureDetectorOptions? TransportFailureDetector { get; set; } 41 | public Akka.Remote.Hosting.PhiAccrualFailureDetectorOptions? WatchFailureDetector { get; set; } 42 | } 43 | public sealed class SslCertificateOptions 44 | { 45 | public SslCertificateOptions() { } 46 | public string? Password { get; set; } 47 | public string? Path { get; set; } 48 | public string? StoreLocation { get; set; } 49 | public string? StoreName { get; set; } 50 | public string? Thumbprint { get; set; } 51 | public bool? UseThumbprintOverFile { get; set; } 52 | } 53 | public sealed class SslOptions 54 | { 55 | public SslOptions() { } 56 | public Akka.Remote.Hosting.SslCertificateOptions CertificateOptions { get; set; } 57 | public Akka.Remote.Transport.DotNetty.CertificateValidationCallback? CustomValidator { get; set; } 58 | public bool? RequireMutualAuthentication { get; set; } 59 | public bool? SuppressValidation { get; set; } 60 | public bool? ValidateCertificateHostname { get; set; } 61 | public System.Security.Cryptography.X509Certificates.X509Certificate2? X509Certificate { get; set; } 62 | } 63 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/Bugfix208Specs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Akka.Actor; 5 | using Akka.Actor.Dsl; 6 | using FluentAssertions; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using Xunit; 10 | 11 | namespace Akka.Hosting.Tests; 12 | 13 | public class Bugfix208Specs : TestKit.TestKit 14 | { 15 | private class MyTestActor : ReceiveActor 16 | { 17 | public record SetData(string Data); 18 | 19 | public record GetData(); 20 | 21 | private string _data = string.Empty; 22 | 23 | public MyTestActor() 24 | { 25 | Receive(s => 26 | { 27 | _data = s.Data; 28 | }); 29 | 30 | Receive(g => 31 | { 32 | Sender.Tell(_data); 33 | }); 34 | } 35 | } 36 | 37 | private class TestActorKey{} 38 | 39 | private class MyBackgroundService : BackgroundService 40 | { 41 | private readonly IRequiredActor _testActor; 42 | 43 | public MyBackgroundService(IRequiredActor requiredActor) 44 | { 45 | _testActor = requiredActor; 46 | } 47 | 48 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 49 | { 50 | var myRef = await _testActor.GetAsync(stoppingToken); 51 | myRef.Tell("BackgroundService started"); 52 | } 53 | } 54 | 55 | protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) 56 | { 57 | services.AddHostedService(); 58 | base.ConfigureServices(context, services); 59 | } 60 | 61 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 62 | { 63 | builder.WithActors((system, registry, arg3) => 64 | { 65 | registry.Register(system.ActorOf(Props.Create(() => new MyTestActor()), "test-actor")); 66 | }); 67 | } 68 | 69 | /// 70 | /// Reproduction for https://github.com/akkadotnet/Akka.Hosting/issues/208 71 | /// 72 | [Fact] 73 | public async Task ShouldStartHostedServiceThatDependsOnActor() 74 | { 75 | // arrange 76 | var testActorRef = ActorRegistry.Get(); 77 | 78 | // act 79 | 80 | // assert 81 | 82 | // workaround for https://github.com/akkadotnet/Akka.Hosting/issues/265 83 | var attempts = 5; 84 | do 85 | { 86 | attempts--; 87 | try 88 | { 89 | var r = await testActorRef.Ask(new MyTestActor.GetData(), TimeSpan.FromMilliseconds(100)); 90 | r.Should().Be("BackgroundService started"); 91 | } 92 | catch (Exception) 93 | { 94 | attempts--; 95 | if (attempts == 0) 96 | { 97 | throw; 98 | } 99 | } 100 | } while (attempts > 0); 101 | 102 | // await AwaitAssertAsync(async () => 103 | // { 104 | // var r = await testActorRef.Ask(new MyTestActor.GetData(), TimeSpan.FromMilliseconds(100)); 105 | // r.Should().Be("BackgroundService started"); 106 | // }, RemainingOrDefault, TimeSpan.FromMilliseconds(150)); 107 | } 108 | } -------------------------------------------------------------------------------- /src/Examples/Akka.Hosting.Asp.LoggingDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using Akka.Hosting; 3 | using Akka.Actor; 4 | using Akka.Actor.Dsl; 5 | using Akka.Cluster.Hosting; 6 | using Akka.Event; 7 | using Akka.Hosting.Asp.LoggingDemo; 8 | using Akka.Remote.Hosting; 9 | using Microsoft.AspNetCore.Diagnostics.HealthChecks; 10 | using Microsoft.Extensions.Diagnostics.HealthChecks; 11 | using LogLevel = Akka.Event.LogLevel; 12 | 13 | var builder = WebApplication.CreateBuilder(args); 14 | builder.Services.AddHealthChecks(); 15 | 16 | builder.Services.AddAkka("MyActorSystem", (configurationBuilder, serviceProvider) => 17 | { 18 | configurationBuilder 19 | .ConfigureLoggers(setup => 20 | { 21 | // This sets the minimum log level 22 | setup.LogLevel = LogLevel.DebugLevel; 23 | 24 | // Clear all loggers (remove the default console logger) 25 | setup.ClearLoggers(); 26 | 27 | // Add the ILoggerFactory logger 28 | // NOTE: 29 | // - You can also use setup.AddLogger(); 30 | // - To use a specific ILoggerFactory instance, you can use setup.AddLoggerFactory(myILoggerFactory); 31 | setup.AddLoggerFactory(); 32 | }) 33 | .WithRemoting("localhost", 8110) 34 | .WithClustering(new ClusterOptions { 35 | Roles = ["myRole"], 36 | SeedNodes = ["akka.tcp://MyActorSystem@localhost:8110"] 37 | }) 38 | .WithAkkaClusterReadinessCheck() 39 | .WithActorSystemLivenessCheck() 40 | .WithHealthCheck("di-test", HealthStatus.Unhealthy, new[] { "test", "custom" }) 41 | .WithActors((system, registry) => 42 | { 43 | var echo = system.ActorOf(act => 44 | { 45 | act.ReceiveAny((o, context) => 46 | { 47 | Logging.GetLogger(context.System, "echo").Info($"Actor received {o}"); 48 | context.Sender.Tell($"{context.Self} rcv {o}"); 49 | }); 50 | }, "echo"); 51 | registry.TryRegister(echo); // register for DI 52 | }); 53 | }); 54 | 55 | var app = builder.Build(); 56 | 57 | app.MapGet("/", async context => 58 | { 59 | var echo = context.RequestServices.GetRequiredService().Get(); 60 | var body = await echo.Ask(context.TraceIdentifier, context.RequestAborted).ConfigureAwait(false); 61 | await context.Response.WriteAsync(body); 62 | }); 63 | 64 | app.MapHealthChecks("/healthz", new HealthCheckOptions 65 | { 66 | Predicate = _ => true, // include all checks 67 | ResponseWriter = async (ctx, report) => 68 | { 69 | ctx.Response.ContentType = "application/json; charset=utf-8"; 70 | 71 | var payload = new 72 | { 73 | status = report.Status.ToString(), 74 | totalDuration = report.TotalDuration, 75 | checks = report.Entries.Select(e => new 76 | { 77 | name = e.Key, 78 | status = e.Value.Status.ToString(), 79 | duration = e.Value.Duration, 80 | description = e.Value.Description, 81 | tags = e.Value.Tags, 82 | data = e.Value.Data // anything you added via context.Registration 83 | }) 84 | }; 85 | 86 | await ctx.Response.WriteAsync(JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true })); 87 | // or in .NET 8+: await ctx.Response.WriteAsJsonAsync(payload); 88 | } 89 | }); 90 | 91 | app.Run(); -------------------------------------------------------------------------------- /src/Akka.Hosting/LoggingExtensions.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2013-2022 .NET Foundation 4 | // 5 | // ----------------------------------------------------------------------- 6 | 7 | using System; 8 | using Akka.Event; 9 | using Akka.Hosting.Logging; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Logging; 12 | 13 | namespace Akka.Hosting 14 | { 15 | public static class LoggingExtensions 16 | { 17 | /// 18 | /// Fluent interface to configure the Akka.NET logger system 19 | /// 20 | /// The being configured 21 | /// An action that can be used to modify the logging configuration 22 | /// The original instance 23 | public static AkkaConfigurationBuilder ConfigureLoggers(this AkkaConfigurationBuilder builder, Action configurator) 24 | { 25 | var setup = new LoggerConfigBuilder(builder); 26 | configurator(setup); 27 | return setup.Build(builder); 28 | } 29 | 30 | /// 31 | /// Add the default Akka.NET logger that sinks all log events to the console 32 | /// 33 | /// The instance 34 | /// the original used to configure the logger system 35 | public static LoggerConfigBuilder AddDefaultLogger(this LoggerConfigBuilder configBuilder) 36 | { 37 | configBuilder.AddLogger(); 38 | return configBuilder; 39 | } 40 | 41 | /// 42 | /// Add the logger that sinks all log events to the default 43 | /// instance registered in the host 44 | /// 45 | /// The instance 46 | /// the original used to configure the logger system 47 | public static LoggerConfigBuilder AddLoggerFactory(this LoggerConfigBuilder configBuilder) 48 | { 49 | configBuilder.AddLogger(typeof(LoggerFactoryLogger)); 50 | return configBuilder; 51 | } 52 | 53 | /// 54 | /// Add the logger that sinks all log events to the provided 55 | /// 56 | /// The instance 57 | /// The instance to be used as the log sink 58 | /// the original used to configure the logger system 59 | public static LoggerConfigBuilder AddLoggerFactory(this LoggerConfigBuilder configBuilder, ILoggerFactory loggerFactory) 60 | { 61 | var builder = configBuilder.Builder; 62 | builder.AddSetup(new LoggerFactorySetup(loggerFactory)); 63 | configBuilder.AddLogger(typeof(LoggerFactoryLogger)); 64 | return configBuilder; 65 | } 66 | 67 | } 68 | } -------------------------------------------------------------------------------- /src/Akka.Hosting/IHoconOption.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2013-2022 .NET Foundation 4 | // 5 | // ----------------------------------------------------------------------- 6 | 7 | using System; 8 | using Akka.Actor.Setup; 9 | 10 | namespace Akka.Hosting 11 | { 12 | /// 13 | /// 14 | /// Standardized interface template for a common HOCON configuration pattern where a configuration takes 15 | /// a HOCON config path and the config path contains a class FQCN property and other settings that 16 | /// are needed by said class. 17 | /// 18 | /// 19 | /// The pattern looks like this: 20 | /// 21 | /// # This HOCON property references to a config block below 22 | /// akka.discovery.method = akka.discovery.config 23 | /// 24 | /// akka.discovery.config { 25 | /// class = "Akka.Discovery.Config.ConfigServiceDiscovery, Akka.Discovery" 26 | /// # other options goes here 27 | /// } 28 | /// 29 | /// 30 | /// 31 | /// 32 | /// Example implementation for the pattern described in the summary 33 | /// 34 | /// // The base class for the option 35 | /// public abstract class DiscoveryOptionBase : IOption 36 | /// { } 37 | /// 38 | /// // The actual option implementation 39 | /// public class ConfigOption : DiscoveryOptionBase 40 | /// { 41 | /// // Actual option implementation here 42 | /// public void Apply(AkkaConfigurationBuilder builder) 43 | /// { 44 | /// // Modifies Akka.NET configuration either via HOCON or setup class 45 | /// builder.AddHocon($"akka.discovery.method = {ConfigPath}", HoconAddMode.Prepend); 46 | /// 47 | /// // Rest of configuration goes here 48 | /// } 49 | /// } 50 | /// 51 | /// // Akka.Hosting extension implementation 52 | /// public static AkkaConfigurationBuilder WithDiscovery( 53 | /// this AkkaConfigurationBuilder builder, 54 | /// DiscoveryOptionBase discOption) 55 | /// { 56 | /// var setup = new DiscoverySetup(); 57 | /// 58 | /// // gets called here 59 | /// discOption.Apply(builder, setup); 60 | /// } 61 | /// 62 | /// 63 | public interface IHoconOption 64 | { 65 | /// 66 | /// The HOCON value of the HOCON path property 67 | /// 68 | string ConfigPath { get; } 69 | 70 | /// 71 | /// The class that will be used for the HOCON class FQCN value 72 | /// 73 | Type Class { get; } 74 | 75 | /// 76 | /// Apply this option to the 77 | /// 78 | /// 79 | /// The to be applied to 80 | /// 81 | /// 82 | /// The to be applied to, if needed. 83 | /// 84 | /// 85 | /// Thrown when requires a setup but it was null 86 | /// 87 | void Apply(AkkaConfigurationBuilder builder, Setup? setup = null); 88 | } 89 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit/Internals/XUnitLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using Xunit.Abstractions; 4 | 5 | namespace Akka.Hosting.TestKit.Internals 6 | { 7 | public class XUnitLogger: ILogger 8 | { 9 | private const string NullFormatted = "[null]"; 10 | 11 | private readonly string _category; 12 | private readonly ITestOutputHelper _helper; 13 | private readonly LogLevel _logLevel; 14 | 15 | public XUnitLogger(string category, ITestOutputHelper helper, LogLevel logLevel) 16 | { 17 | _category = category; 18 | _helper = helper; 19 | _logLevel = logLevel; 20 | } 21 | 22 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) 23 | { 24 | if (!IsEnabled(logLevel)) 25 | return; 26 | 27 | if (!TryFormatMessage(state, exception, formatter, out var formattedMessage)) 28 | return; 29 | 30 | WriteLogEntry(logLevel, eventId, formattedMessage, exception); 31 | } 32 | 33 | private void WriteLogEntry(LogLevel logLevel, EventId eventId, string? message, Exception? exception) 34 | { 35 | var level = logLevel switch 36 | { 37 | LogLevel.Critical => "CRT", 38 | LogLevel.Debug => "DBG", 39 | LogLevel.Error => "ERR", 40 | LogLevel.Information => "INF", 41 | LogLevel.Warning => "WRN", 42 | LogLevel.Trace => "DBG", 43 | _ => "???" 44 | }; 45 | 46 | var msg = $"{DateTime.Now}:{level}:{_category}:{eventId} {message}"; 47 | if (exception != null) 48 | msg += $"\n{exception.GetType()} {exception.Message}\n{exception.StackTrace}"; 49 | 50 | try 51 | { 52 | 53 | _helper.WriteLine(msg); 54 | } 55 | catch 56 | { 57 | // no active xUnit test available 58 | Console.WriteLine("No active xUnit test available, but logging was attempted. Message: " + msg); 59 | } 60 | } 61 | 62 | public bool IsEnabled(LogLevel logLevel) 63 | { 64 | return logLevel switch 65 | { 66 | LogLevel.None => false, 67 | _ => logLevel >= _logLevel 68 | }; 69 | } 70 | 71 | public IDisposable BeginScope(TState state) 72 | { 73 | return NullScope.Instance; 74 | } 75 | 76 | private static bool TryFormatMessage( 77 | TState state, 78 | Exception? exception, 79 | Func formatter, 80 | out string? result) 81 | { 82 | formatter = formatter ?? throw new ArgumentNullException(nameof(formatter)); 83 | 84 | var formattedMessage = formatter(state, exception); 85 | if (formattedMessage == NullFormatted) 86 | { 87 | result = null; 88 | return false; 89 | } 90 | 91 | result = formattedMessage; 92 | return true; 93 | } 94 | 95 | private class NullScope : IDisposable 96 | { 97 | private NullScope() { } 98 | public static NullScope Instance { get; } = new NullScope(); 99 | public void Dispose() { } 100 | } 101 | } 102 | } 103 | 104 | -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestEventListenerTests/EventFilterTestBase.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2021 Lightbend Inc. 4 | // Copyright (C) 2013-2021 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Threading.Tasks; 10 | using Akka.Event; 11 | using Xunit.Abstractions; 12 | 13 | namespace Akka.Hosting.TestKit.Tests.TestEventListenerTests 14 | { 15 | public abstract class EventFilterTestBase : TestKit 16 | { 17 | private readonly LogLevel _logLevel; 18 | 19 | /// 20 | /// Used to signal that the test was successful and that we should ensure no more messages were logged 21 | /// 22 | protected bool TestSuccessful; 23 | 24 | protected EventFilterTestBase(LogLevel logLevel, ITestOutputHelper? output = null) : base(output: output) 25 | { 26 | _logLevel = logLevel; 27 | } 28 | 29 | protected abstract void SendRawLogEventMessage(object message); 30 | 31 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 32 | { 33 | builder.ConfigureLoggers(logger => 34 | { 35 | logger.LogLevel = _logLevel; 36 | logger.ClearLoggers(); 37 | logger.AddLogger(); 38 | }); 39 | } 40 | 41 | protected override async Task BeforeTestStart() 42 | { 43 | await base.BeforeTestStart(); 44 | 45 | //We send a ForwardAllEventsTo containing message to the TestEventListenerToForwarder logger (configured as a logger above). 46 | //It should respond with an "OK" message when it has received the message. 47 | var initLoggerMessage = new ForwardAllEventsTestEventListener.ForwardAllEventsTo(TestActor); 48 | // ReSharper disable once DoNotCallOverridableMethodsInConstructor 49 | SendRawLogEventMessage(initLoggerMessage); 50 | try 51 | { 52 | await ExpectMsgAsync("OK", TimeSpan.FromSeconds(10)); 53 | } 54 | catch (Exception e) 55 | { 56 | throw new Exception( 57 | "Failed to receive an OK signal from ForwardAllEventsTestEventListener logger during test start " + 58 | $"inside EventFilterTestBase. Running loggers: [{string.Join(", ", Sys.Settings.Loggers)}]", e); 59 | } 60 | //From now on we know that all messages will be forwarded to TestActor 61 | } 62 | 63 | protected override async Task AfterAllAsync() 64 | { 65 | //After every test we make sure no uncatched messages have been logged 66 | if(TestSuccessful) 67 | { 68 | EnsureNoMoreLoggedMessages(); 69 | } 70 | await base.AfterAllAsync(); 71 | } 72 | 73 | private void EnsureNoMoreLoggedMessages() 74 | { 75 | //We log a Finished message. When it arrives to TestActor we know no other message has been logged. 76 | //If we receive something else it means another message was logged, and ExpectMsg will fail 77 | const string message = "<>"; 78 | SendRawLogEventMessage(message); 79 | ExpectMsg(err => (string) err.Message == message,hint: "message to be \"" + message + "\""); 80 | } 81 | 82 | } 83 | } 84 | 85 | -------------------------------------------------------------------------------- /src/Akka.Cluster.Hosting/ClusterClientDiscoveryOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Akka.Cluster.Tools.Client; 4 | using Akka.Configuration; 5 | using Akka.Hosting; 6 | 7 | namespace Akka.Cluster.Hosting; 8 | 9 | public sealed class ClusterClientDiscoveryOptions 10 | { 11 | /// 12 | /// 13 | /// The discovery sub-system that will be used to discover cluster client contacts. 14 | /// Must be discovery options class instance from Akka.Management v1.5.27 and higher. 15 | /// 16 | /// Note that if you're also using Akka.Discovery for ClusterBootstrap, in order for 17 | /// to work, you will need to set 18 | /// DiscoveryOptions.IsDefaultPlugin to false 19 | /// 20 | public IDiscoveryOptions DiscoveryOptions { get; set; } = null!; 21 | 22 | /// 23 | /// The service name that are being discovered. This setting is not optional. 24 | /// 25 | public string ServiceName { get; set; } = string.Empty; 26 | 27 | /// 28 | /// The Akka.Management port name, usually used in conjunction with Akka.Discovery.KubernetesApi 29 | /// 30 | public string? PortName { get; set; } 31 | 32 | /// 33 | /// Interval at which service discovery will be polled in search for new initial contacts 34 | /// 35 | public TimeSpan? RetryInterval { get; set; } 36 | 37 | /// 38 | /// Timeout for getting a reply from the service-discovery subsystem 39 | /// 40 | public TimeSpan? Timeout { get; set; } 41 | 42 | /// 43 | /// The number of initial contacts will be trimmed down to this number of contact points to the client 44 | /// 45 | public int? NumberOfContacts { get; set; } 46 | 47 | /// 48 | /// The name of the cluster client actor 49 | /// 50 | public string? ClientActorName { get; set; } 51 | 52 | public override string ToString() 53 | { 54 | Validate(); 55 | 56 | var sb = new StringBuilder(); 57 | sb.AppendLine("use-initial-contacts-discovery = true"); 58 | sb.AppendLine("discovery {"); 59 | sb.AppendLine($"method = {DiscoveryOptions.ConfigPath.ToHocon()}"); 60 | sb.AppendLine($"service-name = {ServiceName.ToHocon()}"); 61 | if (!string.IsNullOrWhiteSpace(PortName)) 62 | sb.AppendLine($"port-name = {PortName.ToHocon()}"); 63 | if (NumberOfContacts is not null) 64 | sb.AppendLine($"number-of-contacts = {NumberOfContacts.ToHocon()}"); 65 | if (RetryInterval is not null) 66 | sb.AppendLine($"interval = {RetryInterval.ToHocon()}"); 67 | if (Timeout is not null) 68 | sb.AppendLine($"resolve-timeout = {Timeout.ToHocon()}"); 69 | sb.AppendLine("}"); 70 | 71 | return sb.ToString(); 72 | } 73 | 74 | private void Validate() 75 | { 76 | if (string.IsNullOrWhiteSpace(ServiceName)) 77 | throw new ArgumentException("Service name must be provided", nameof(ServiceName)); 78 | 79 | if (RetryInterval is not null && RetryInterval < TimeSpan.Zero) 80 | throw new ArgumentException("Retry interval must be greater than zero", nameof(RetryInterval)); 81 | 82 | if(Timeout is not null && Timeout < TimeSpan.Zero) 83 | throw new ArgumentException("Timeout must be greater than zero", nameof(Timeout)); 84 | 85 | if (NumberOfContacts < 1) 86 | throw new ArgumentException("Number of contacts must be greater than zero", nameof(NumberOfContacts)); 87 | 88 | if (ClientActorName is not null && string.IsNullOrWhiteSpace(ClientActorName)) 89 | throw new ArgumentException("Cluster client actor name must not be empty or whitespace", nameof(ClientActorName)); 90 | } 91 | } -------------------------------------------------------------------------------- /src/Akka.Remote.Hosting/AkkaRemoteHostingExtensions.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2013-2022 .NET Foundation 4 | // 5 | // ----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Text; 9 | using Akka.Actor; 10 | using Akka.Hosting; 11 | 12 | namespace Akka.Remote.Hosting 13 | { 14 | public static class AkkaRemoteHostingExtensions 15 | { 16 | /// 17 | /// Adds Akka.Remote support to this . 18 | /// 19 | /// A configuration delegate. 20 | /// Optional. The hostname to bind Akka.Remote upon. Default: "0.0.0.0" 21 | /// Optional. The port to bind Akka.Remote upon. Default: 2552 22 | /// Optional. If using hostname aliasing, this is the host we will advertise. 23 | /// Optional. If using port aliasing, this is the port we will advertise. 24 | /// The same instance originally passed in. 25 | public static AkkaConfigurationBuilder WithRemoting( 26 | this AkkaConfigurationBuilder builder, 27 | string? hostname = null, 28 | int? port = null, 29 | string? publicHostname = null, 30 | int? publicPort = null) 31 | => builder.WithRemoting(new RemoteOptions 32 | { 33 | HostName = hostname, 34 | Port = port, 35 | PublicHostName = publicHostname, 36 | PublicPort = publicPort 37 | }); 38 | 39 | /// 40 | /// Adds Akka.Remote support to this . 41 | /// 42 | /// A configuration delegate. 43 | /// 44 | /// An delegate used to configure an 45 | /// instance to configure Akka.Remote 46 | /// 47 | /// The same instance originally passed in. 48 | public static AkkaConfigurationBuilder WithRemoting( 49 | this AkkaConfigurationBuilder builder, 50 | Action configure) 51 | { 52 | var options = new RemoteOptions(); 53 | configure(options); 54 | return builder.WithRemoting(options); 55 | } 56 | 57 | /// 58 | /// Adds Akka.Remote support to this . 59 | /// 60 | /// A configuration delegate. 61 | /// 62 | /// A instance to configure Akka.Remote 63 | /// 64 | /// The same instance originally passed in. 65 | public static AkkaConfigurationBuilder WithRemoting( 66 | this AkkaConfigurationBuilder builder, 67 | RemoteOptions options) 68 | { 69 | options.Build(builder); 70 | 71 | if (builder.ActorRefProvider.HasValue) 72 | { 73 | switch (builder.ActorRefProvider.Value) 74 | { 75 | case ProviderSelection.Cluster _: 76 | case ProviderSelection.Remote _: 77 | case ProviderSelection.Custom _: 78 | return builder; // no-op 79 | } 80 | } 81 | 82 | return builder.WithActorRefProvider(ProviderSelection.Remote.Instance); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/Logging/SerilogLoggerEnd2EndSpecs.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2013-2024 .NET Foundation 4 | // 5 | // ----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Collections.Concurrent; 9 | using Akka.Event; 10 | using Akka.Logger.Serilog; 11 | using FluentAssertions; 12 | using Serilog; 13 | using Serilog.Core; 14 | using Xunit; 15 | using Xunit.Abstractions; 16 | 17 | namespace Akka.Hosting.Tests.Logging; 18 | 19 | public class SerilogLoggerEnd2EndSpecs : TestKit.TestKit 20 | { 21 | /// 22 | /// 23 | /// Basic concurrent sink implementation for testing the final output from Serilog 24 | /// 25 | public sealed class TestSink : ILogEventSink 26 | { 27 | public ConcurrentQueue Writes { get; private set; } = new(); 28 | 29 | private readonly ITestOutputHelper? _output; 30 | private int _count; 31 | 32 | public TestSink() : this(null) 33 | { 34 | } 35 | 36 | public TestSink(ITestOutputHelper? output) 37 | { 38 | _output = output; 39 | } 40 | 41 | 42 | /// 43 | /// Resets the contents of the queue 44 | /// 45 | public void Clear() 46 | { 47 | Writes.Clear(); 48 | } 49 | 50 | public void Emit(Serilog.Events.LogEvent logEvent) 51 | { 52 | _count++; 53 | _output?.WriteLine($"[{nameof(TestSink)}][{_count}]: {logEvent.RenderMessage()}"); 54 | Writes.Enqueue(logEvent); 55 | } 56 | } 57 | 58 | private readonly TestSink _sink = new(); 59 | 60 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 61 | { 62 | Serilog.Log.Logger = new LoggerConfiguration() 63 | .WriteTo.Sink(_sink) 64 | .MinimumLevel.Information() 65 | .CreateLogger(); 66 | 67 | builder.ConfigureLoggers(setup => 68 | { 69 | setup.ClearLoggers(); 70 | setup.AddLogger(); 71 | setup.LogLevel = Event.LogLevel.DebugLevel; 72 | setup.WithDefaultLogMessageFormatter(); 73 | }); 74 | } 75 | 76 | [Theory] 77 | [InlineData(Event.LogLevel.DebugLevel, "test case {0}", new object[] { 1 })] 78 | [InlineData(Event.LogLevel.DebugLevel, "test case {myNum}", new object[] { 1 })] 79 | [InlineData(Event.LogLevel.InfoLevel, "test case {myNum} {myStr}", new object[] { 1, "foo" })] 80 | public void ShouldHandleSerilogFormats(LogLevel level, string formatStr, object[] args) 81 | { 82 | Sys.EventStream.Subscribe(TestActor, typeof(LogEvent)); 83 | 84 | var logWrite = () => 85 | { 86 | Sys.Log.Log(level, formatStr, args); 87 | 88 | var logEvent = ExpectMsg(); 89 | logEvent.LogLevel().Should().Be(level); 90 | logEvent.ToString().Should().NotBeEmpty(); 91 | }; 92 | 93 | logWrite.Should().NotThrow(); 94 | } 95 | 96 | [Fact(Skip = "Does not work right now")] 97 | public void ShouldHaveEnrichedContext() 98 | { 99 | Sys.EventStream.Subscribe(TestActor, typeof(LogEvent)); 100 | 101 | var contextedLogger = Sys.Log.ForContext("TestContext", "Testy"); 102 | _sink.Clear(); 103 | AwaitCondition(() => _sink.Writes.IsEmpty); 104 | 105 | contextedLogger.Info("test case {0}", 1); 106 | AwaitCondition(() => _sink.Writes.Count == 1); 107 | 108 | _sink.Writes.TryDequeue(out var logEvent).Should().BeTrue(); 109 | logEvent!.Properties.ContainsKey("TestContext").Should().BeTrue(); 110 | } 111 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.TestKit.Tests/TestPersistenceTestKistTests/TestSnapshotStoreSpec.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2022 Lightbend Inc. 4 | // Copyright (C) 2013-2025 .NET Foundation 5 | // 6 | //----------------------------------------------------------------------- 7 | 8 | using Xunit.Abstractions; 9 | 10 | namespace Akka.Hosting.TestKit.Tests.TestPersistenceTestKistTests; 11 | 12 | using Actor; 13 | using Akka.Persistence; 14 | using Akka.Persistence.TestKit; 15 | using Akka.Persistence.TestKit.Tests; 16 | using Akka.TestKit; 17 | using System; 18 | using System.Threading.Tasks; 19 | using Xunit; 20 | 21 | public sealed class TestSnapshotStoreSpec : PersistenceTestKit 22 | { 23 | public TestSnapshotStoreSpec(ITestOutputHelper output) : base(nameof(TestSnapshotStoreSpec), output: output) 24 | { 25 | } 26 | 27 | protected override Task BeforeTestStart() 28 | { 29 | _probe = CreateTestProbe(); 30 | return Task.CompletedTask; 31 | } 32 | 33 | private TestProbe _probe = null!; 34 | 35 | [Fact] 36 | public async Task send_ack_after_load_interceptor_is_set() 37 | { 38 | SnapshotsActorRef.Tell(new TestSnapshotStore.UseLoadInterceptor(null), TestActor); 39 | await ExpectMsgAsync(); 40 | } 41 | 42 | [Fact] 43 | public async Task send_ack_after_save_interceptor_is_set() 44 | { 45 | SnapshotsActorRef.Tell(new TestSnapshotStore.UseSaveInterceptor(null), TestActor); 46 | await ExpectMsgAsync(); 47 | } 48 | 49 | [Fact] 50 | public async Task send_ack_after_delete_interceptor_is_set() 51 | { 52 | SnapshotsActorRef.Tell(new TestSnapshotStore.UseDeleteInterceptor(null), TestActor); 53 | await ExpectMsgAsync(); 54 | } 55 | 56 | [Fact] 57 | public async Task after_load_behavior_was_executed_store_is_back_to_pass_mode() 58 | { 59 | // create snapshot 60 | var actor = ActorOf(() => new SnapshotActor(_probe)); 61 | actor.Tell("save"); 62 | await _probe.ExpectMsgAsync(); 63 | await actor.GracefulStop(TimeSpan.FromSeconds(3)); 64 | 65 | await WithSnapshotLoad(load => load.Fail(), async () => 66 | { 67 | ActorOf(() => new SnapshotActor(_probe)); 68 | await _probe.ExpectMsgAsync(); 69 | }); 70 | 71 | ActorOf(() => new SnapshotActor(_probe)); 72 | await _probe.ExpectMsgAsync(); 73 | } 74 | 75 | [Fact] 76 | public async Task after_save_behavior_was_executed_store_is_back_to_pass_mode() 77 | { 78 | // create snapshot 79 | var actor = ActorOf(() => new SnapshotActor(_probe)); 80 | 81 | await WithSnapshotSave(save => save.Fail(), async () => 82 | { 83 | actor.Tell("save"); 84 | await _probe.ExpectMsgAsync(); 85 | }); 86 | 87 | actor.Tell("save"); 88 | await _probe.ExpectMsgAsync(); 89 | } 90 | 91 | [Fact] 92 | public async Task after_delete_behavior_was_executed_store_is_back_to_pass_mode() 93 | { 94 | // create snapshot 95 | var actor = ActorOf(() => new SnapshotActor(_probe)); 96 | actor.Tell("save"); 97 | 98 | var success = await _probe.ExpectMsgAsync(); 99 | var nr = success.Metadata.SequenceNr; 100 | 101 | await WithSnapshotDelete(del => del.Fail(), async () => 102 | { 103 | actor.Tell(new SnapshotActor.DeleteOne(nr), TestActor); 104 | await _probe.ExpectMsgAsync(); 105 | }); 106 | 107 | actor.Tell(new SnapshotActor.DeleteOne(nr), TestActor); 108 | await _probe.ExpectMsgAsync(); 109 | } 110 | } -------------------------------------------------------------------------------- /src/Akka.Hosting.Tests/Logging/LogMessageFormatterSpec.cs: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2009-2023 Lightbend Inc. 4 | // Copyright (C) 2013-2023 .NET Foundation 5 | // 6 | // ----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Threading.Tasks; 12 | using Akka.Actor; 13 | using Akka.Configuration; 14 | using Akka.Event; 15 | using FluentAssertions; 16 | using Microsoft.Extensions.DependencyInjection; 17 | using Microsoft.Extensions.Hosting; 18 | using Microsoft.Extensions.Logging; 19 | using Xunit; 20 | using Xunit.Abstractions; 21 | using LogLevel = Microsoft.Extensions.Logging.LogLevel; 22 | using static FluentAssertions.FluentActions; 23 | 24 | namespace Akka.Hosting.Tests.Logging; 25 | 26 | public class LogMessageFormatterSpec 27 | { 28 | private ITestOutputHelper _helper; 29 | 30 | public LogMessageFormatterSpec(ITestOutputHelper helper) 31 | { 32 | _helper = helper; 33 | } 34 | 35 | [Fact(DisplayName = "ILogMessageFormatter should transform log messages")] 36 | public async Task TransformMessagesTest() 37 | { 38 | using var host = await SetupHost(); 39 | 40 | try 41 | { 42 | var sys = host.Services.GetRequiredService(); 43 | var testKit = new Akka.TestKit.Xunit2.TestKit(sys); 44 | 45 | var probe = testKit.CreateTestProbe(); 46 | sys.EventStream.Subscribe(probe, typeof(Error)); 47 | sys.Log.Error("This is a test {0}", 1); 48 | 49 | var msg = probe.ExpectMsg(); 50 | msg.Message.Should().BeAssignableTo(); 51 | msg.ToString().Should().Contain("++TestLogMessageFormatter++"); 52 | } 53 | finally 54 | { 55 | await host.StopAsync(); 56 | } 57 | } 58 | 59 | [Fact(DisplayName = "Invalid LogMessageFormatter property should throw")] 60 | public async Task InvalidLogMessageFormatterThrowsTest() 61 | { 62 | await Awaiting(async () => await SetupHost()) 63 | .Should().ThrowAsync().WithMessage("*must have an empty constructor*"); 64 | } 65 | 66 | private async Task SetupHost() where TFormatter : ILogMessageFormatter 67 | { 68 | var host = new HostBuilder() 69 | .ConfigureLogging(builder => 70 | { 71 | builder.AddProvider(new XUnitLoggerProvider(_helper, LogLevel.Information)); 72 | }) 73 | .ConfigureServices(collection => 74 | { 75 | collection.AddAkka("TestSys", configurationBuilder => 76 | { 77 | configurationBuilder 78 | .ConfigureLoggers(setup => 79 | { 80 | setup.LogLevel = Event.LogLevel.DebugLevel; 81 | setup.AddLoggerFactory(); 82 | setup.WithDefaultLogMessageFormatter(); 83 | }); 84 | }); 85 | }).Build(); 86 | await host.StartAsync(); 87 | return host; 88 | } 89 | } 90 | 91 | public class TestLogMessageFormatter : ILogMessageFormatter 92 | { 93 | public string Format(string format, params object[] args) 94 | { 95 | return string.Format($"++TestLogMessageFormatter++{format}", args); 96 | } 97 | 98 | public string Format(string format, IEnumerable args) 99 | => Format(format, args.ToArray()); 100 | } 101 | 102 | public class InvalidLogMessageFormatter : ILogMessageFormatter 103 | { 104 | public InvalidLogMessageFormatter(string doesNotMatter) 105 | { 106 | } 107 | 108 | public string Format(string format, params object[] args) 109 | { 110 | throw new NotImplementedException(); 111 | } 112 | 113 | public string Format(string format, IEnumerable args) 114 | { 115 | throw new NotImplementedException(); 116 | } 117 | } -------------------------------------------------------------------------------- /src/Akka.Persistence.Hosting.Tests/InMemoryPersistenceSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Akka.Actor; 5 | using Akka.Event; 6 | using Akka.Hosting; 7 | using Akka.TestKit.Xunit2.Internals; 8 | using FluentAssertions; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Hosting; 11 | using Xunit; 12 | using Xunit.Abstractions; 13 | 14 | namespace Akka.Persistence.Hosting.Tests 15 | { 16 | public class InMemoryPersistenceSpecs: Akka.Hosting.TestKit.TestKit 17 | { 18 | 19 | private readonly ITestOutputHelper _output; 20 | 21 | public InMemoryPersistenceSpecs(ITestOutputHelper output) 22 | { 23 | _output = output; 24 | } 25 | 26 | public sealed class MyPersistenceActor : ReceivePersistentActor 27 | { 28 | private List _values = new List(); 29 | 30 | public MyPersistenceActor(string persistenceId) 31 | { 32 | PersistenceId = persistenceId; 33 | 34 | Recover(offer => 35 | { 36 | if (offer.Snapshot is IEnumerable ints) 37 | { 38 | _values = new List(ints); 39 | } 40 | }); 41 | 42 | Recover(i => 43 | { 44 | _values.Add(i); 45 | }); 46 | 47 | Command(i => 48 | { 49 | Persist(i, i1 => 50 | { 51 | _values.Add(i); 52 | if (LastSequenceNr % 2 == 0) 53 | { 54 | SaveSnapshot(_values); 55 | } 56 | Sender.Tell("ACK"); 57 | }); 58 | }); 59 | 60 | Command(str => str.Equals("getall"), s => 61 | { 62 | Sender.Tell(_values.ToArray()); 63 | }); 64 | 65 | Command(s => {}); 66 | } 67 | 68 | public override string PersistenceId { get; } 69 | } 70 | 71 | 72 | protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider) 73 | { 74 | builder 75 | .WithInMemoryJournal() 76 | .WithInMemorySnapshotStore() 77 | .StartActors((system, registry) => 78 | { 79 | var myActor = system.ActorOf(Props.Create(() => new MyPersistenceActor("ac1")), "actor1"); 80 | registry.Register(myActor); 81 | }); 82 | } 83 | 84 | [Fact] 85 | public async Task Should_Start_ActorSystem_wth_InMemory_Persistence() 86 | { 87 | // arrange 88 | var myPersistentActor = ActorRegistry.Get(); 89 | 90 | // act 91 | var resp1 = await myPersistentActor.Ask(1, TimeSpan.FromSeconds(3)); 92 | var resp2 = await myPersistentActor.Ask(2, TimeSpan.FromSeconds(3)); 93 | var snapshot = await myPersistentActor.Ask("getall", TimeSpan.FromSeconds(3)); 94 | 95 | // assert 96 | snapshot.Should().BeEquivalentTo(new[] {1, 2}); 97 | 98 | // kill + recreate actor with same PersistentId 99 | await myPersistentActor.GracefulStop(TimeSpan.FromSeconds(3)); 100 | var myPersistentActor2 = Sys.ActorOf(Props.Create(() => new MyPersistenceActor("ac1")), "actor1a"); 101 | 102 | var snapshot2 = await myPersistentActor2.Ask("getall", TimeSpan.FromSeconds(3)); 103 | snapshot2.Should().BeEquivalentTo(new[] {1, 2}); 104 | 105 | // validate configs 106 | var config = Sys.Settings.Config; 107 | config.GetString("akka.persistence.journal.plugin").Should().Be("akka.persistence.journal.inmem"); 108 | config.GetString("akka.persistence.snapshot-store.plugin").Should().Be("akka.persistence.snapshot-store.inmem"); 109 | } 110 | } 111 | } --------------------------------------------------------------------------------