Git Product home page Git Product logo

akkadotnet / akka.hosting Goto Github PK

View Code? Open in Web Editor NEW
48.0 9.0 11.0 765 KB

Akka.Hosting - standardized Akka.NET hosting without any HOCON configuration. Ties into Microsoft.Extensions.Configuration, Logging, Hosting, and DependencyInjection.

License: Apache License 2.0

Batchfile 0.04% PowerShell 2.62% Shell 0.48% C# 96.83% Dockerfile 0.03% TSQL 0.01%
akkadotnet actor-model actors ihostedservice

akka.hosting's Introduction

Akka.Hosting

This package is now stable.

HOCON-less configuration, application lifecycle management, ActorSystem startup, and actor instantiation for Akka.NET.

See the "Introduction to Akka.Hosting - HOCON-less, "Pit of Success" Akka.NET Runtime and Configuration" video for a walkthrough of the library and how it can save you a tremendous amount of time and trouble.

Table Of Content

Supported Packages

Akka.NET Core Packages

  • Akka.Hosting - the core Akka.Hosting package, needed for everything
  • Akka.Remote.Hosting - enables Akka.Remote configuration. Documentation can be read here
  • Akka.Cluster.Hosting - used for Akka.Cluster, Akka.Cluster.Sharding, and Akka.Cluster.Tools. Documentation can be read here
  • Akka.Persistence.Hosting - used for adding persistence functionality to perform local database-less testing. Documentation can be read here

Back to top

Akka Persistence Plugins

Back to top

Embed health check functionality for environments such as Kubernetes, ASP.NET, AWS, Azure, Pivotal Cloud Foundry, and more. Documentation can be read here

Back to top

Useful tools for managing Akka.NET clusters running inside containerized or cloud based environment. Akka.Hosting is embedded in each of its packages.

Back to top

Akka.Management Core Package

  • Akka.Management - core module of the management utilities which provides a central HTTP endpoint for Akka management extensions. Documentation can be read here
  • Akka.Management.Cluster.Bootstrap - used to bootstrap a cluster formation inside dynamic deployment environments. Documentation can be read here

    NOTE

    As of version 1.0.0, cluster bootstrap came bundled inside the core Akka.Management NuGet package and are part of the default HTTP endpoint for Akka.Management. All Akka.Management.Cluster.Bootstrap NuGet package versions below 1.0.0 should now be considered deprecated.

Back to top

Akka.Discovery Plugins

Back to top

Akka.Coordination Plugins

Back to top

Summary

We want to make Akka.NET something that can be instantiated more typically per the patterns often used with the Microsoft.Extensions.Hosting APIs that are common throughout .NET.

using Akka.Hosting;
using Akka.Actor;
using Akka.Actor.Dsl;
using Akka.Cluster.Hosting;
using Akka.Remote.Hosting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAkka("MyActorSystem", configurationBuilder =>
{
    configurationBuilder
        .WithRemoting("localhost", 8110)
        .WithClustering(new ClusterOptions(){ Roles = new[]{ "myRole" },
            SeedNodes = new[]{ Address.Parse("akka.tcp://MyActorSystem@localhost:8110")}})
        .WithActors((system, registry) =>
    {
        var echo = system.ActorOf(act =>
        {
            act.ReceiveAny((o, context) =>
            {
                context.Sender.Tell($"{context.Self} rcv {o}");
            });
        }, "echo");
        registry.TryRegister<Echo>(echo); // register for DI
    });
});

var app = builder.Build();

app.MapGet("/", async (context) =>
{
    var echo = context.RequestServices.GetRequiredService<ActorRegistry>().Get<Echo>();
    var body = await echo.Ask<string>(context.TraceIdentifier, context.RequestAborted).ConfigureAwait(false);
    await context.Response.WriteAsync(body);
});

app.Run();

No HOCON. Automatically runs all Akka.NET application lifecycle best practices behind the scene. Automatically binds the ActorSystem and the ActorRegistry, another new 1.5 feature, to the IServiceCollection so they can be safely consumed via both actors and non-Akka.NET parts of users' .NET applications.

This should be open to extension in other child plugins, such as Akka.Persistence.SqlServer:

builder.Services.AddAkka("MyActorSystem", configurationBuilder =>
{
    configurationBuilder
        .WithRemoting("localhost", 8110)
        .WithClustering(new ClusterOptions()
        {
            Roles = new[] { "myRole" },
            SeedNodes = new[] { Address.Parse("akka.tcp://MyActorSystem@localhost:8110") }
        })
        .WithSqlServerPersistence(builder.Configuration.GetConnectionString("sqlServerLocal"))
        .WithShardRegion<UserActionsEntity>("userActions", s => UserActionsEntity.Props(s),
            new UserMessageExtractor(),
            new ShardOptions(){ StateStoreMode = StateStoreMode.DData, Role = "myRole"})
        .WithActors((system, registry) =>
        {
            var userActionsShard = registry.Get<UserActionsEntity>();
            var indexer = system.ActorOf(Props.Create(() => new Indexer(userActionsShard)), "index");
            registry.TryRegister<Index>(indexer); // register for DI
        });
})

Back to top

Dependency Injection Outside and Inside Akka.NET

One of the other design goals of Akka.Hosting is to make the dependency injection experience with Akka.NET as seamless as any other .NET technology. We accomplish this through two new APIs:

  • The ActorRegistry, a DI container that is designed to be populated with Types for keys and IActorRefs for values, just like the IServiceCollection does for ASP.NET services.
  • The IRequiredActor<TKey> - you can place this type the constructor of any dependency injected resource and it will automatically resolve a reference to the actor stored inside the ActorRegistry with TKey. This is how we inject actors into ASP.NET, SignalR, gRPC, and other Akka.NET actors!

N.B. The ActorRegistry and the ActorSystem are automatically registered with the IServiceCollection / IServiceProvider associated with your application.

Back to top

Registering Actors with the ActorRegistry

As part of Akka.Hosting, we need to provide a means of making it easy to pass around top-level IActorRefs via dependency injection both within the ActorSystem and outside of it.

The ActorRegistry will fulfill this role through a set of generic, typed methods that make storage and retrieval of long-lived IActorRefs easy and coherent:

  • Fetch ActorRegistry from ActorSystem manually
var registry = ActorRegistry.For(myActorSystem); 
  • Provided by the actor builder
builder.Services.AddAkka("MyActorSystem", configurationBuilder =>
{
    configurationBuilder
        .WithActors((system, actorRegistry) =>
        {
            var actor = system.ActorOf(Props.Create(() => new MyActor));
            actorRegistry.TryRegister<MyActor>(actor); // register actor for DI
        });
});
  • Obtaining the IActorRef manually
var registry = ActorRegistry.For(myActorSystem); 
registry.Get<Index>(); // use in DI

Back to top

Injecting Actors with IRequiredActor<TKey>

Suppose we have a class that depends on having a reference to a top-level actor, a router, a ShardRegion, or perhaps a ClusterSingleton (common types of actors that often interface with non-Akka.NET parts of a .NET application):

public sealed class MyConsumer
{
    private readonly IActorRef _actor;

    public MyConsumer(IRequiredActor<MyActorType> actor)
    {
        _actor = actor.ActorRef;
    }

    public async Task<string> Say(string word)
    {
        return await _actor.Ask<string>(word, TimeSpan.FromSeconds(3));
    }
}

The IRequiredActor<MyActorType> will cause the Microsoft.Extensions.DependencyInjection mechanism to resolve MyActorType from the ActorRegistry and inject it into the IRequired<Actor<MyActorType> instance passed into MyConsumer.

The IRequiredActor<TActor> exposes a single property:

public interface IRequiredActor<TActor>
{
    /// <summary>
    /// The underlying actor resolved via <see cref="ActorRegistry"/> using the given <see cref="TActor"/> key.
    /// </summary>
    IActorRef ActorRef { get; }
}

By default, you can automatically resolve any actors registered with the ActorRegistry without having to declare anything special on your IServiceCollection:

using var host = new HostBuilder()
  .ConfigureServices(services =>
  {
      services.AddAkka("MySys", (builder, provider) =>
      {
          builder.WithActors((system, registry) =>
          {
              var actor = system.ActorOf(Props.Create(() => new MyActorType()), "myactor");
              registry.Register<MyActorType>(actor);
          });
      });
      services.AddScoped<MyConsumer>();
  })
  .Build();
  await host.StartAsync();

Adding your actor and your type key into the ActorRegistry is sufficient - no additional DI registration is required to access the IRequiredActor<TActor> for that type.

Back to top

Resolving IRequiredActor<TKey> within Akka.NET

Akka.NET does not use dependency injection to start actors by default primarily because actor lifetime is unbounded by default - this means reasoning about the scope of injected dependencies isn't trivial. ASP.NET, by contrast, is trivial: all HTTP requests are request-scoped and all web socket connections are connection-scoped - these are objects have bounded and typically short lifetimes.

Therefore, users have to explicitly signal when they want to use Microsoft.Extensions.DependencyInjection via the IDependencyResolver interface in Akka.DependencyInjection - which is easy to do in most of the Akka.Hosting APIs for starting actors:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IReplyGenerator, DefaultReplyGenerator>();
builder.Services.AddAkka("MyActorSystem", configurationBuilder =>
{
    configurationBuilder
        .WithRemoting(hostname: "localhost", port: 8110)
        .WithClustering(new ClusterOptions{SeedNodes = new []{ "akka.tcp://MyActorSystem@localhost:8110", }})
        .WithShardRegion<Echo>(
            typeName: "myRegion",
            entityPropsFactory: (_, _, resolver) =>
            {
                // uses DI to inject `IReplyGenerator` into EchoActor
                return s => resolver.Props<EchoActor>(s);
            },
            extractEntityId: ExtractEntityId,
            extractShardId: ExtractShardId,
            shardOptions: new ShardOptions());
});

The dependencyResolver.Props<MySingletonDiActor>() call will leverage the ActorSystem's built-in IDependencyResolver to instantiate the MySingletonDiActor and inject it with all of the necessary dependencies, including IRequiredActor<TKey>.

Back to top

Microsoft.Extensions.Configuration Integration

IConfiguration To HOCON Adapter

The AddHocon extension method can convert Microsoft.Extensions.Configuration IConfiguration into HOCON Config instance and adds it to the ActorSystem being configured.

  • Unlike IConfiguration, all HOCON key names are case sensitive.
  • Unless enclosed inside double quotes, all "." (period) in the IConfiguration key will be treated as a HOCON object key separator
  • IConfiguration does not support object composition, if you declare the same key multiple times inside multiple configuration providers (JSON/environment variables/etc), only the last one declared will take effect.
  • For environment variable configuration provider:
    • "__" (double underline) will be converted to "." (period).
    • "_" (single underline) will be converted to "-" (dash).
    • If all keys are composed of integer parseable keys, the whole object is treated as an array

Example:

appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "akka": {
    "cluster": {
      "roles": [ "front-end", "back-end" ],
      "min-nr-of-members": 3,
      "log-info": true
    }
  }    
}

Environment variables:

AKKA__ACTOR__TELEMETRY__ENABLE=true
AKKA__CLUSTER__SEED_NODES__0=akka.tcp//mySystem@localhost:4055
AKKA__CLUSTER__SEED_NODES__1=akka.tcp//mySystem@localhost:4056
AKKA__CLUSTER__SEED_NODE_TIMEOUT=00:00:05

Note the integer parseable key inside the seed-nodes configuration, seed-nodes will be parsed as an array. These environment variables will be parsed as HOCON settings:

akka {
  actor {
    telemetry.enabled: on
  }
  cluster {
    seed-nodes: [ 
      "akka.tcp//mySystem@localhost:4055",
      "akka.tcp//mySystem@localhost:4056" 
    ]
    seed-node-timeout: 5s
  }
}

Example code:

/*
Both appsettings.json and environment variables are combined
into HOCON configuration:

akka {
  actor.telemetry.enabled: on
  cluster {
    roles: [ "front-end", "back-end" ]
    seed-nodes: [ 
      "akka.tcp//mySystem@localhost:4055",
      "akka.tcp//mySystem@localhost:4056" 
    ]
    min-nr-of-members: 3
    seed-node-timeout: 5s
    log-info: true
  }
}
*/
var host = new HostBuilder()
    .ConfigureHostConfiguration(builder =>
    {
        // Setup IConfiguration to load from appsettings.json and
        // environment variables
        builder
            .AddJsonFile("appsettings.json")
            .AddEnvironmentVariables();
    })
    .ConfigureServices((context, services) =>
    {
        services.AddAkka("mySystem", (builder, provider) =>
            {
                // convert IConfiguration to HOCON
                var akkaConfig = context.Configuration.GetSection("akka");
                builder.AddHocon(akkaConfig, HoconAddMode.Prepend); 
            });
    });

Special Characters And Case Sensitivity

This advanced usage of the IConfiguration adapter is solely used for edge cases where HOCON key capitalization needs to be preserved, such as declaring serialization binding. Note that when you're using this feature, none of the keys are normalized, you will have to write all of your keys in a HOCON compatible way.

appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "akka": {
    "\"Key.With.Dots\"": "Key Value",
    "cluster": {
      "roles": [ "front-end", "back-end" ],
      "min-nr-of-members": 3,
      "log-info": true
    }
  }    
}

Note that "Key.With.Dots" needs to be inside escaped double quotes, this is a HOCON requirement that preserves the "." (period) inside HOCON property names.

Environment variables:

PS C:/> [Environment]::SetEnvironmentVariable('akka__actor__telemetry__enabled', 'true')
PS C:/> [Environment]::SetEnvironmentVariable('akka__actor__serialization_bindings__"System.Object"', 'hyperion')
PS C:/> [Environment]::SetEnvironmentVariable('akka__cluster__seed_nodes__0', 'akka.tcp//mySystem@localhost:4055')
PS C:/> [Environment]::SetEnvironmentVariable('akka__cluster__seed_nodes__1', 'akka.tcp//mySystem@localhost:4056')
PS C:/> [Environment]::SetEnvironmentVariable('akka__cluster__seed_node_timeout', '00:00:05')

Note that:

  1. All of the environment variable names are in lower case, except "System.Object" where it needs to preserve name capitalization.
  2. To set serialization binding via environment variable, you have to use "." (period) instead of "__" (double underscore), this might be problematic for some shell scripts and there is no way of getting around this.

Example code:

/*
Both appsettings.json and environment variables are combined
into HOCON configuration:

akka {
  "Key.With.Dots": Key Value
  actor {
    telemetry.enabled: on
    serialization-bindings {
      "System.Object" = hyperion
    }
  }
  cluster {
    roles: [ "front-end", "back-end" ]
    seed-nodes: [ 
      "akka.tcp//mySystem@localhost:4055",
      "akka.tcp//mySystem@localhost:4056" 
    ]
    min-nr-of-members: 3
    seed-node-timeout: 5s
    log-info: true
  }
}
*/
var host = new HostBuilder()
    .ConfigureHostConfiguration(builder =>
    {
        // Setup IConfiguration to load from appsettings.json and
        // environment variables
        builder
            .AddJsonFile("appsettings.json")
            .AddEnvironmentVariables();
    })
    .ConfigureServices((context, services) =>
    {
        services.AddAkka("mySystem", (builder, provider) =>
            {
                // convert IConfiguration to HOCON
                var akkaConfig = context.Configuration.GetSection("akka");
                // Note the last method argument is set to false
                builder.AddHocon(akkaConfig, HoconAddMode.Prepend, false); 
            });
    });

Back to top

Microsoft.Extensions.Logging Integration

Logger Configuration Support

You can use AkkaConfigurationBuilder extension method called ConfigureLoggers(Action<LoggerConfigBuilder>) to configure how Akka.NET logger behave.

Example:

builder.Services.AddAkka("MyActorSystem", configurationBuilder =>
{
    configurationBuilder
        .ConfigureLoggers(setup =>
        {
            // Example: This sets the minimum log level
            setup.LogLevel = LogLevel.DebugLevel;
            
            // Example: Clear all loggers
            setup.ClearLoggers();
            
            // Example: Add the default logger
            // NOTE: You can also use setup.AddLogger<DefaultLogger>();
            setup.AddDefaultLogger();
            
            // Example: Add the ILoggerFactory logger
            // NOTE:
            //   - You can also use setup.AddLogger<LoggerFactoryLogger>();
            //   - To use a specific ILoggerFactory instance, you can use setup.AddLoggerFactory(myILoggerFactory);
            setup.AddLoggerFactory();
            
            // Example: Adding a serilog logger
            setup.AddLogger<SerilogLogger>();
        })
        .WithActors((system, registry) =>
        {
            var echo = system.ActorOf(act =>
            {
                act.ReceiveAny((o, context) =>
                {
                    Logging.GetLogger(context.System, "echo").Info($"Actor received {o}");
                    context.Sender.Tell($"{context.Self} rcv {o}");
                });
            }, "echo");
            registry.TryRegister<Echo>(echo); // register for DI
        });
});

A complete code sample can be viewed here.

Exposed properties are:

  • LogLevel: Configure the Akka.NET minimum log level filter, defaults to InfoLevel
  • LogConfigOnStart: When set to true, Akka.NET will log the complete HOCON settings it is using at start up, this can then be used for debugging purposes.

Currently supported logger methods:

  • ClearLoggers(): Clear all registered logger types.
  • AddLogger<TLogger>(): Add a logger type by providing its class type.
  • AddDefaultLogger(): Add the default Akka.NET console logger.
  • AddLoggerFactory(): Add the new ILoggerFactory logger.

Back to top

Microsoft.Extensions.Logging.ILoggerFactory Logging Support

You can now use ILoggerFactory from Microsoft.Extensions.Logging as one of the sinks for Akka.NET logger. This logger will use the ILoggerFactory service set up inside the dependency injection ServiceProvider as its sink.

Back to top

Serilog Message Formatting Support

If you're interested in using Akka.Logger.Serilog, you can set Akka.NET's default logger and log message formatter to allow for Serilog's semantic logging to be enabled by default:

builder.Services.AddAkka("MyActorSystem", configurationBuilder =>
{
    configurationBuilder
        .ConfigureLoggers(setup =>
        {
            // Example: This sets the minimum log level
            setup.LogLevel = LogLevel.DebugLevel;
            
            // Example: Clear all loggers
            setup.ClearLoggers();
            
            // Add Serilog
            setup.AddLogger<SerilogLogger>();
            
            // use the default SerilogFormatter everywhere
            setup.WithDefaultLogMessageFormatter<SerilogLogMessageFormatter>();
        })
        .WithActors((system, registry) =>
        {
            var echo = system.ActorOf(act =>
            {
                act.ReceiveAny((o, context) =>
                {
                    Logging.GetLogger(context.System, "echo").Info($"Actor received {o}");
                    context.Sender.Tell($"{context.Self} rcv {o}");
                });
            }, "echo");
            registry.TryRegister<Echo>(echo); // register for DI
        });
});

This will eliminate the need to have to do Context.GetLogger<SerilogLoggingAdapter>() everywhere you want to use it.

Back to top

Microsoft.Extensions.Logging Log Event Filtering

There will be two log event filters acting on the final log input, the Akka.NET akka.loglevel setting and the Microsoft.Extensions.Logging settings, make sure that both are set correctly or some log messages will be missing.

To set up the Microsoft.Extensions.Logging log filtering, you will need to edit the appsettings.json file. Note that we also set the Akka namespace to be filtered at debug level in the example below.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Akka": "Debug"
    }
  }
}

Back to top

akka.hosting's People

Contributors

aaronontheweb avatar arkatufus avatar cumpsd avatar dependabot[bot] avatar eaba avatar ingted avatar seanfarrow avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

akka.hosting's Issues

Please add failure-detector to the fluent API

We are trying to update our application to use the Fluent API for configuration and after asking about this Aaron suggested creating an issue for it.

We would like to be able to configure the following via the Fluent API

cluster
{
failure-detector
{
implementation-class="Akka.Remote.DeadlineFailureDectector.Akka.Remote"
acceptable-heartbeat-pause = 300s
heartbeat-interval = 2s
}
}

`WithCustomSerializer` needs to add ability to specify serialization identifier

Version Information
Version of Akka.NET? 1.0.1
Which Akka.NET Modules? Akka.Hosting

Describe the bug

One thing that Akka.Hosting can't currently do is specify the akka.actor.serialization-identifiers section of HOCON:

akka.actor{
   # This is to get the manifest back in the event journal
            serializers {
              json2 = "JsonSerializerWithManifest, MyInfrastructure"
            }

            serialization-bindings {
              "System.Object" = json2
            }

            serialization-identifiers {
              "JsonSerializerWithManifest, MyInfrastructure" = 410
            }
}

With the current WithCustomerSerializer method we can't specify this - instead we have to rely on the serializer hard-coding it by overriding the Serializer.Identifer property

.WithCustomSerializer("json2", new[]{ typeof(object) }, system => new JsonSerializerWithManifest(system))

Add custom `System.Object` serializer and persistence default serializer peculiarity documentation

Persistence have its own default System.Object serializer settings here:

akka.persistence.journal-plugin-fallback.serializer = json
akka.persistence.snapshot-store-plugin-fallback.serializer = json

https://github.com/akkadotnet/akka.net/blob/3f7d3980fa45565abb824fe5f192f87c1bdde113/src/core/Akka.Persistence/persistence.conf#L119
https://github.com/akkadotnet/akka.net/blob/dev/src/core/Akka.Persistence/persistence.conf#L178

This is a big gotcha for people who does not know about this peculiarity and thinks that Akka persistence would use the custom System.Object serializer they configured with Akka, as shown in issue #127

This needs to be better documented

Non-zero exit code on cluster failure

Is your feature request related to a problem? Please describe.
We are hosting Akka.Cluster in Windows services. In order for the service to automatically restart after a cluster failure it needs to set a non-zero exit code.

Describe the solution you'd like
A hook or event before StopApplication is called from AkkaHostedService.

Describe alternatives you've considered
Akka.Hosting could set the exit code in failure scenarios, but might be more inflexible.

Additional context
I'd be happy to create a pull request if any of the above sounds like a reasonable solution.

Akka.Hosting.TestKit: `TestActor` is no longer the implicit sender

Version Information
Version of Akka.NET? 1.0.3
Which Akka.NET Modules? Akka.Hosting.TestKit

Describe the bug

 foreach (var i in Enumerable.Range(0, 2))
        {
            var update1 = Dsl.Update(CounterKey, GCounter.Empty, WriteLocal.Instance,
                g => g.Increment(selfAddress));
            replicator.Tell(update1);
            UpdateSuccess success1 = ExpectMsg<UpdateSuccess>();
        }

Fails and UpdateSuccess is a DeadLetter - this is because ActorRefs.NoSender is the sender.

Expected behavior

TestActor should be the implicit sender, just like in the normal testkit.

Actual behavior

There is no implicit sender.

Workaround

Can explicitly specify the TestActor as the sender.

 foreach (var i in Enumerable.Range(0, 2))
        {
            var update1 = Dsl.Update(CounterKey, GCounter.Empty, WriteLocal.Instance,
                g => g.Increment(selfAddress));
            replicator.Tell(update1, TestActor);
            UpdateSuccess success1 = ExpectMsg<UpdateSuccess>();
        }

Akka.Hosting.TestKit: default `DisposeAsync` implementation can throw `OperationCancelledException` and erroneously fail test

Version Information
Version of Akka.NET? 1.5.6.1 and 1.5.7
Which Akka.NET Modules? Akka.Hosting.TestKit

Describe the bug

   at System.Threading.CancellationToken.ThrowOperationCanceledException()
   at System.Threading.CancellationToken.ThrowIfCancellationRequested()
   at Microsoft.Extensions.Hosting.Internal.Host.StopAsync(CancellationToken cancellationToken)
   at Akka.Hosting.TestKit.TestKit.DisposeAsync()

To Reproduce

Difficult to reproduce, but it appears to happen when tests run and exit very quickly - here's an example from the Phobos test suite that causes this issue:

public class AkkaHostingConfigSpecs : TestKit
    {
        protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IServiceProvider provider)
        {
            builder.WithPhobos(AkkaRunMode.AkkaCluster);
        }

        [Fact]
        public void ShouldEnforceDefaultConfig()
        {
            Sys.Settings.EmitActorTelemetry.Should().BeTrue();
        }
    }

Actual behavior

Test should exit cleanly

API changes roadmap for 1.0

This issue thread is made as a discussion ground on what API needs to be changed/added into Akka.Hosting before we're freezing it for 1.0 release. We encourage users to make suggestions in this thread based on their experience in using Akka.Hosting so far.

Currently, Akka.Hosting still need these API addition/changes:

Persistence and Cluster Sharding

These API changes are needed to make persistence be compatible with sharding.

  • Add API to Akka.Cluster.Hosting to allow user to specify a specific persistence plugin for cluster sharding
  • Add API to add a persistence journal/snapshot plugin settings that can be referenced elsewhere. #146
    • Maybe we can expand AkkaPersistenceJournalBuilder for journal settings?
    • If so, we need to add AkkaPersistenceSnapshotBuilder for snapshot settings
    • Persistence plugins Hosting extensions should not use Setup classes, they should inject their HOCON configuration directly.
  • Change the current Akka.Persistence.*.Hosting implementation to not automatically inject themselves as the default persistence plugins #146
  • Change the current Akka.Persistence.*.Hosting API to let their HOCON settings be copied over or renamed to another HOCON block for use with either normal persistence or cluster sharding #146
  • Add a generalized Akka.Persistence.Hosting API to specifically set the akka.persistence.journal.plugin and akka.persistence.snapshot-store.plugin settings. #146

Remoting

  • We will not make remote deployment hosting API

Clustering

  • Expand current API with more options to configure the cluster. Current settings candidates: (#149)
    • min-nr-of-members
    • Per role min-nr-of-members
    • app-version
    • Logging related settings
    • Move SBR into this options class (only the new SBR will be supported)
  • Expand singleton API to enable lease (#150)
  • Expand sharding API to enable lease (#150)

General

  • Make all options class bindable to Microsoft.Extensions.Configuration

Akka.Hosting NRE upon shutdown

Version Information
Version of Akka.NET? 0.2.2
Which Akka.NET Modules? Akka.Cluster.Hosting

Describe the bug

Upon shutdown, Akka.Hosting will sometimes throw the following NullReferenceException:

   at Microsoft.Extensions.Hosting.Internal.Host.StopAsync(CancellationToken cancellationToken)
   at Phobos.Hosting.Tests.EndToEndHostingSpecs.ShouldLaunchPhobosCluster() in D:\a\1\s\src\core\Phobos.Hosting.Tests\EndToEndHostingSpecs.cs:line 265
--- End of stack trace from previous location where exception was thrown ---
----- Inner Stack Trace -----
   at Akka.Actor.ActorSystemWithExtensions.WithExtension[T,TI](ActorSystem system)
   at Akka.Actor.CoordinatedShutdown.Get(ActorSystem sys)
   at Akka.Hosting.AkkaHostedService.StopAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.Internal.Host.StopAsync(CancellationToken cancellationToken)

I believe this is because the ActorSystem was already terminate prior to CoordinatedShutdown being called.

Expected behavior

Should not throw.

Actual behavior

Throws.

Please add guardian-supervisor-strategy tot he Fluent API

We are trying to update our application to use the Fluent API for configuration and after asking about this Aaron suggested creating an issue for it.

We would to be able to configure the following via the Fluent API:

akka
{
actor
{
guardian-supervisor-strategy = "BatchProcessorLibrary.Akka.GuardianSupervisorStrategy, BatchProcessorLibrary"
}
}

builder.AddSetup(DependencyResolverSetup.Create(provider)) not apply

Version Information
Version of Akka.NET? 1.5.13
Which Akka.NET Modules? Akka.Hosting

Describe the bug
The code did not work, so I reviewed the akka.hosting code.
https://github.com/akkadotnet/Akka.Hosting/blob/dev/src/Akka.Hosting/AkkaConfigurationBuilder.cs#L142-L146

             // don't apply the diSetup
             if (setup is DependencyResolverSetup)
             {
                 return this;
             }

I was hoping that the DependencyResolverSetup setting would work correctly.
Is there a reason why this code doesn't do anything?

To Reproduce
Steps to reproduce the behavior:
My Code.

        services.AddAkka(appConfig.Name, (builder, provider) =>
        {
            var config = ConfigurationFactory.ParseString(@"...");
            builder.AddHocon(config, HoconAddMode.Prepend)
                .AddSetup(DependencyResolverSetup.Create(provider));
            // skip

Links to working reproductions on Github / Gitlab are very much appreciated

Expected behavior
I expected

Caller

[ApiController]
public class TestController
{
    private readonly IServiceProvider _sp;
    private readonly ActorSystem _system;

    public TestController(ActorSystem system,
        IServiceProvider sp)
    {
        _system = system;
        _sp = sp;
    }

    [HttpGet("/test")]
    public async Task<string> Test()
    {
        var address = Address.Parse("akka.tcp://...@localhost:5011");
        var props = EchoActor.Props(_system)
            .WithDeploy(new Deploy(new RemoteScope(address)));
        var actorRef = _system.ActorOf(props);
        var str = await actorRef.Ask<Response>(new Request("HELLO"));

        return str.Message;
    }
}

Callee

public class EchoActor : ReceiveActor
{
    private readonly IServiceProvider _sp;
    private readonly ILoggingAdapter _logger = Context.GetLogger();
    
    public EchoActor(IServiceProvider sp)
    {
        _sp = sp;
        Receive<Request>(OnReceiveEcho);
    }

    public static Props Props(ActorSystem actorSystem)
    {
        var props = DependencyResolver.For(actorSystem).Props<EchoActor>();
        return props;
    }

Actual behavior
What actually happened and how did it differ from your expectations?

[ERROR][10/29/2023 17:15:20.566Z][Thread 0021][akka.tcp://...@localhost:5011/remote/akka.tcp/r...@localhost:5001/user/$b] Error while creating actor instance of type ....AspNetCore.Actors.EchoActor with 0 args: ()
Cause: [akka.tcp://...@localhost:5011/remote/akka.tcp/...@localhost:5001/user/$b#270112165]: Akka.Actor.ActorInitializationException: Exception during creation
 ---> System.TypeLoadException: Error while creating actor instance of type ....AspNetCore.Actors.EchoActor with 0 args: ()
 ---> System.MissingMethodException: Constructor on type '....AspNetCore.Actors.EchoActor' not found.
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
   at Akka.Actor.Props.ActivatorProducer.Produce()
   at Akka.Actor.Props.NewActor()
   --- End of inner exception stack trace ---
   at Akka.Actor.Props.NewActor()
   at Akka.Actor.ActorCell.CreateNewActorInstance()
   at Akka.Actor.ActorCell.<>c__DisplayClass116_0.<NewActor>b__0()
   at Akka.Actor.ActorCell.UseThreadContext(Action action)
   at Akka.Actor.ActorCell.NewActor()
   at Akka.Actor.ActorCell.Create(Exception failure)
   --- End of inner exception stack trace ---
   at Akka.Actor.ActorCell.Create(Exception failure)
   at Akka.Actor.ActorCell.SysMsgInvokeAll(EarliestFirstSystemMessageList messages, Int32 currentState)

Screenshots
If applicable, add screenshots to help explain your problem.

Environment
Are you running on Linux? Windows? Docker? Which version of .NET?
.NET 7

Additional context
Add any other context about the problem here.

Akka.Hosting racy spec failure: `Should_configure_LoggerFactoryLogger`

Version Information
Version of Akka.NET? dev latest
Which Akka.NET Modules? Akka.Hosting core

Describe the bug

Encountered a racy spec failure upon merging #108 into dev

  14:18:20 [ERR] [xUnit.net 00:00:03.96]     Akka.Hosting.Tests.Logging.LoggerConfigEnd2EndSpecs.Should_configure_LoggerFactoryLogger [FAIL]
  14:18:20 [DBG]   Failed Akka.Hosting.Tests.Logging.LoggerConfigEnd2EndSpecs.Should_configure_LoggerFactoryLogger [3 s]
  14:18:20 [DBG]   Error Message:
  14:18:20 [DBG]    Expected _logger.Infos.Where(c => c.Contains("foo")) to contain 1 item(s), but found 0: {empty}.
  14:18:20 [DBG]   Stack Trace:
  14:18:20 [DBG]      at FluentAssertions.Execution.XUnit2TestFramework.Throw(String message)
  14:18:20 [DBG]    at FluentAssertions.Execution.TestFrameworkProvider.Throw(String message)
  14:18:20 [DBG]    at FluentAssertions.Execution.DefaultAssertionStrategy.HandleFailure(String message)
  14:18:20 [DBG]    at FluentAssertions.Execution.AssertionScope.FailWith(Func`1 failReasonFunc)
  14:18:20 [DBG]    at FluentAssertions.Collections.GenericCollectionAssertions`3.HaveCount(Int32 expected, String because, Object[] becauseArgs)
  14:18:20 [DBG]    at Akka.Hosting.Tests.Logging.LoggerConfigEnd2EndSpecs.<Should_configure_LoggerFactoryLogger>b__4_0() in /home/runner/work/Akka.Hosting/Akka.Hosting/src/Akka.Hosting.Tests/Logging/LoggerConfigEnd2EndSpecs.cs:line 67
  14:18:20 [DBG]    at Akka.TestKit.TestKitBase.AwaitAssertAsync(Action assertion, Nullable`1 duration, Nullable`1 interval)
  14:18:20 [DBG]    at Akka.Hosting.Tests.Logging.LoggerConfigEnd2EndSpecs.Should_configure_LoggerFactoryLogger() in /home/runner/work/Akka.Hosting/Akka.Hosting/src/Akka.Hosting.Tests/Logging/LoggerConfigEnd2EndSpecs.cs:line 66
  14:18:20 [DBG] --- End of stack trace from previous location ---
  14:18:20 [DBG]   Standard Output Messages:

To Reproduce

N/A - appears to be racy.

Custom Serializer setup not propagating to Persistence

Version Information
Akka - v1.4.43
Akka.Hosting - v0.4.3
Akka.Persistence.SqlServer.Hosting - v0.4.3
Akka.Serialization.Hyperion - v1.4.43

Describe the bug
When setting up Hyperion as the default serializer via Akka.Hosting configuration, persistence still uses Json as the default serializer and has to be configured separately to use Hyperion.

To Reproduce
I used a configuration similar to the following:

        services.AddAkka("actor-system", (configurationBuilder, _) =>
        {
            configurationBuilder
                .WithRemoting("localhost", 7910)
                .WithClustering(new ClusterOptions() { Roles = new[] { "Coordinator-Role" }, SeedNodes = Address[] { Address.Parse("akka.tcp://actor-system@localhost:7910") } })
                .WithSqlServerPersistence("Server=localhost,1433; Database=Akka; User Id=sa; Password=[password];")
                .WithCustomSerializer("hyperion", new[] { typeof(object) }, system => new HyperionSerializer(system));
        {

Expected behavior
When setting up Hyperion as the default serializer, this setting should be propagated through-out the Akka.net modules.

Actual behavior
Json was still the default serializer for persistence.

Screenshots
image

Environment
Windows 11
.Net 6.0

Additional context

Akka.Remote.Hosting doesn't support `public-hostname` correctly

Version Information
Version of Akka.NET? 0.2.0
Which Akka.NET Modules? Akka.Remote.Hosting

Describe the bug

When using the following configuration:

services.AddAkka("RemoteSys", (builder, provider) =>
{
    builder.WithRemoting("0.0.0.0", 0, "localhost");
});

Akka.Remote will bind to 0.0.0.0 as its public address instead of localhost.

To Reproduce

Sending a PR for this

Expected behavior

Akka.Remote should bind to 0.0.0.0 under the hood, but advertise its public address as localhost.

Actual behavior

Akka.Remote binds to 0.0.0.0 and advertises it as its public address.

Add Support for `Akka.Persistence.Postgresql.Hosting`?

Is your feature request related to a problem? Please describe.

Akka.Hosting is a big step forward! Right now the out of the box persistence is SQL Server based. It would be nice for the people who run Postgresql to not have to run a separate SQL Server next to it.

Describe the solution you'd like

Create a WithPostgresqlPersistence extension method.

Additional context

Code for the extension methods for Sql Server: https://github.com/akkadotnet/Akka.Hosting/blob/dev/src/Akka.Persistence.SqlServer.Hosting/AkkaPersistenceSqlServerHostingExtensions.cs

The config is probably something like:

Config journalConfiguration = $@"
  akka.persistence {{
    journal {{
      plugin = ""akka.persistence.journal.postgresql""

      postgresql {{
        class = ""Akka.Persistence.PostgreSql.Journal.PostgreSqlJournal, Akka.Persistence.PostgreSql""
        plugin-dispatcher = ""akka.actor.default-dispatcher""
        connection-timeout = 30s
        schema-name = public
        table-name = event_journal
        auto-initialize = off
        timestamp-provider = ""Akka.Persistence.Sql.Common.Journal.DefaultTimestampProvider, Akka.Persistence.Sql.Common""
        metadata-table-name = metadata
        stored-as = json
        sequential-access = off
        use-bigint-identity-for-ordering-column = on
        connection-string = ""{connectionString}""
      }}
    }}
  }}";

Config snapshotStoreConfig = $@"
  akka.persistence {{
    snapshot-store {{
      plugin = ""akka.persistence.snapshot-store.postgresql""

      postgresql {{
        class = ""Akka.Persistence.PostgreSql.Snapshot.PostgreSqlSnapshotStore, Akka.Persistence.PostgreSql""
        plugin-dispatcher = ""akka.actor.default-dispatcher""
        connection-timeout = 30s
        schema-name = public
        table-name = snapshot_store
        auto-initialize = off
        stored-as = json
        sequential-access = off
        connection-string = ""{connectionString}""
      }}
    }}
  }}";

Finer grain control over ActorSystem startup

Is your feature request related to a problem? Please describe.

Right now, there is no proper programmatic way to defer/modify ActorSystem hosted service startup, everything is started immediately on host application start up.

Describe the solution you'd like

A way for users to modify/override the IHostedService.StartAsync() and IHostedService.StopAsync() methods.

SimpleDemo Execution Exception

Version of Akka.NET? Which Akka.NET Modules? clone Akka.Hosting and executed Build.cmd

A clear and concise description of what the bug is.


[INFO][1/3/2023 5:31:47 AM][Thread 0001][remoting (akka://MyActorSystem)] Starting remoting
[INFO][1/3/2023 5:31:47 AM][Thread 0001][remoting (akka://MyActorSystem)] Remoting started; listening on addresses : [akka.tcp://MyActorSystem@localhost:8110]
[INFO][1/3/2023 5:31:47 AM][Thread 0001][remoting (akka://MyActorSystem)] Remoting now listens on addresses: [akka.tcp://MyActorSystem@localhost:8110]
[INFO][1/3/2023 5:31:47 AM][Thread 0001][Cluster (akka://MyActorSystem)] Cluster Node [akka.tcp://MyActorSystem@localhost:8110] - Starting up...
[INFO][1/3/2023 5:31:47 AM][Thread 0001][Cluster (akka://MyActorSystem)] Cluster Node [akka.tcp://MyActorSystem@localhost:8110] - Started up successfully
[INFO][1/3/2023 5:31:47 AM][Thread 0005][Cluster (akka://MyActorSystem)] Cluster Node [0.4.0] - Node [akka.tcp://MyActorSystem@localhost:8110] is JOINING itself (with roles [], version [0.4.0]) and forming a new cluster
[INFO][1/3/2023 5:31:47 AM][Thread 0005][Cluster (akka://MyActorSystem)] Cluster Node [akka.tcp://MyActorSystem@localhost:8110] - is the new leader among reachable nodes (more leaders may exist)
[INFO][1/3/2023 5:31:47 AM][Thread 0005][Cluster (akka://MyActorSystem)] Cluster Node [akka.tcp://MyActorSystem@localhost:8110] - Leader is moving node [akka.tcp://MyActorSystem@localhost:8110] to [Up]
[INFO][1/3/2023 5:31:47 AM][Thread 0015][akka.tcp://MyActorSystem@localhost:8110/system/sharding/myRegion] myRegion: Idle entities will be passivated after [00:02:00]
[INFO][1/3/2023 5:31:48 AM][Thread 0013][akka.tcp://MyActorSystem@localhost:8110/system/sharding/myRegionCoordinator] Singleton manager started singleton actor [akka://MyActorSystem/system/sharding/myRegionCoordinator/singleton]
[INFO][1/3/2023 5:31:48 AM][Thread 0013][akka.tcp://MyActorSystem@localhost:8110/system/sharding/myRegionCoordinator] ClusterSingletonManager state change [Start -> Oldest] Akka.Cluster.Tools.Singleton.Uninitialized
[INFO][1/3/2023 5:31:48 AM][Thread 0003][akka.tcp://MyActorSystem@localhost:8110/system/sharding/myRegionCoordinator/singleton/coordinator] Sharding Coordinator was moved to the active state Akka.Cluster.Sharding.PersistentShardCoordinator+State
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:61877
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:61878
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: G:\git\Akka.Hosting\src\Examples\Akka.Hosting.SimpleDemo\
[ERROR][1/3/2023 5:31:49 AM][Thread 0013][akka://MyActorSystem/system/sharding/myRegion/3/0HMND9F7KSI93%3A00000001] Error while creating actor instance of type Akka.Hosting.SimpleDemo.EchoActor with 0 args: ()
Cause: [akka://MyActorSystem/system/sharding/myRegion/3/0HMND9F7KSI93%3A00000001#345915985]: Akka.Actor.ActorInitializationException: Exception during creation
 ---> System.TypeLoadException: Error while creating actor instance of type Akka.Hosting.SimpleDemo.EchoActor with 0 args: ()
 ---> System.InvalidOperationException: Cannot resolve scoped service 'Akka.Hosting.SimpleDemo.IReplyGenerator' from root provider.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
   at Akka.DependencyInjection.ServiceProviderActorProducer.Produce()
   at Akka.Actor.Props.NewActor()
   --- End of inner exception stack trace ---
   at Akka.Actor.Props.NewActor()
   at Akka.Actor.ActorCell.CreateNewActorInstance()
   at Akka.Actor.ActorCell.<>c__DisplayClass116_0.<NewActor>b__0()
   at Akka.Actor.ActorCell.UseThreadContext(Action action)
   at Akka.Actor.ActorCell.NewActor()
   at Akka.Actor.ActorCell.Create(Exception failure)
   --- End of inner exception stack trace ---
   at Akka.Actor.ActorCell.Create(Exception failure)
   at Akka.Actor.ActorCell.SysMsgInvokeAll(EarliestFirstSystemMessageList messages, Int32 currentState)
[INFO][1/3/2023 5:31:49 AM][Thread 0003][akka://MyActorSystem/system/sharding/myRegion/3/0HMND9F7KSI93%3A00000001] Message [String] from [akka://MyActorSystem/temp/g] to [akka://MyActorSystem/system/sharding/myRegion/3/0HMND9F7KSI93%3A00000001#345915985] was not delivered. [1] dead letters encountered. If this is not an expected behavior then [akka://MyActorSystem/system/sharding/myRegion/3/0HMND9F7KSI93%3A00000001#345915985] may have terminated unexpectedly. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.

Steps to reproduce the behavior:
Clone -> Build -> Set Akka.Hosting.SimpleDemo as startup project. -> F5

A clear and concise description of what you expected to happen. -> There should be no exceptions?!

Are you running on Linux? Windows? Docker? Which version of .NET? => Windows 2019, .net 7.101

NUKE no longer publishes `CHANGELOG.md` notes to GitHub Release

Since merging #74 GitHub Release flows no longer propagate the release notes typed into CHANGELOG.md into GitHub Releases - we noticed this yesterday with https://github.com/akkadotnet/Akka.Hosting/releases/tag/0.4.0. The release notes for 0.4.0 were auto-generated by GitHub itself, rather than using the notes that @Arkatufus typed up. This is a regression that needs to be fixed in this project and in all of https://github.com/petabridge/petabridge-dotnet-new that have been upgraded to the latest NUKE.

Akka.Hosting: return non-zero exit code when `CoordinatedShutdown` is invoked due to error'd reasons

Is your feature request related to a problem? Please describe.

In order to enable process supervision on platforms like Windows Services to properly restart an Akka.Hosting / IHost`-powered application, we need to return a non-zero exit code when the application exits improperly.

In many cases, things are shutdown for normal reasons - however, due to the CoordinatedShutdown.Reason property we can generally determine cases where the process may not have shut down cleanly:

Describe the solution you'd like

We should return a non-zero exit code when one of those two types is returned during the method below:

async Task TerminationHook()
{
await ActorSystem.WhenTerminated.ConfigureAwait(false);
HostApplicationLifetime?.StopApplication();
}

Describe alternatives you've considered

It might be worth considering extending the CoordinatedShutdown.Reason class to include an IsError property that would indicate whether or not we should log a non-zero exit code and incorporate this functionality directly into CoordinatedShutdown instead, but I think that might be a bit too prescriptive.

Akka.Cluster.Hosting & Serialization

Version Information
Version of Akka.NET? 1.4.48
Which Akka.NET Modules? Akka.Cluster, Akka.Cluster.Hosting, Akka.Logger.NLog

Describe the bug
This is the message during deserialization of custom message in a cluster between two nodes.

Deserialization failed for message with serializer id [1] and manifest []. Transient association error (association remains live). Object reference not set to an instance of an object.. Serializer Id [1] is used to instantiate [Akka.Serialization.NewtonSoftJsonSerializer, Akka]. You can add it by adding a fallback to your ActorSystem configuration by using [ConfigurationFactory.Default()]. It is also automatically injected into your configuration when you call [ActorSystem.Create()].

To Reproduce
Simply create two node with the same application and ask/tell messages between them. Configure Actors using Akka.Cluster.Hosting

Links to working reproductions on Github / Gitlab are very much appreciated

Expected behavior
Custom message deserialized

Actual behavior
The error above

Screenshots
null

Environment
Linux

Additional context
null

WithLoggerFactory unable to load logger

Version Information
Akka 1.4.39
Akka.hosting 0.4.0

Describe the bug
Using WithLoggerFactory throws Akka.Configuration.ConfigurationException:

To Reproduce
Create a new console application using the dotnet cli

> dotnet new console

Install dependencies

    <ItemGroup>
      <PackageReference Include="Akka" Version="1.4.39" />
      <PackageReference Include="Akka.Hosting" Version="0.4.0" />
      <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.0.0" />
    </ItemGroup>

Update program.cs

using var host = CreateHostBuilder(args)
    .UseConsoleLifetime().Build();

await host.StartAsync();

var actorSystem = host.Services.GetRequiredService<ActorSystem>();

Console.ReadLine();

await actorSystem.Terminate();
await actorSystem.WhenTerminated;
await host.StopAsync();

static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices(services =>
        {
            services.AddAkka("AkkaSandbox", (configBuilder, provider) =>
            {
                configBuilder.AddHocon("akka.loglevel = DEBUG")
                    .WithLoggerFactory();
            });
        });

Error message

crit: Akka.Hosting.AkkaHostedService[0]
      Unable to start AkkaHostedService - shutting down application
Akka.Configuration.ConfigurationException: Logger [Akka.Hosting.Logging.LoggerFactoryLogger, Akka.Hosting] specified in config cannot be loaded: System.AggregateException: One or more errors occurred. (Timeout after 00:00:05 seconds)
 ---> Akka.Actor.AskTimeoutException: Timeout after 00:00:05 seconds
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   at System.Threading.Tasks.Task`1.get_Result()
   at Akka.Event.LoggingBus.AddLogger(ActorSystemImpl system, Type loggerType, LogLevel logLevel, String loggingBusName, TimeSpan timeout)
   at Akka.Event.LoggingBus.StartDefaultLoggers(ActorSystemImpl system)
 ---> System.AggregateException: One or more errors occurred. (Timeout after 00:00:05 seconds)
 ---> Akka.Actor.AskTimeoutException: Timeout after 00:00:05 seconds
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   at System.Threading.Tasks.Task`1.get_Result()
   at Akka.Event.LoggingBus.AddLogger(ActorSystemImpl system, Type loggerType, LogLevel logLevel, String loggingBusName, TimeSpan timeout)
   at Akka.Event.LoggingBus.StartDefaultLoggers(ActorSystemImpl system)
   --- End of inner exception stack trace ---
   at Akka.Event.LoggingBus.StartDefaultLoggers(ActorSystemImpl system)
   at Akka.Actor.LocalActorRefProvider.Init(ActorSystemImpl system)
   at Akka.Actor.Internal.ActorSystemImpl.Start()
   at Akka.Actor.ActorSystem.CreateAndStartSystem(String name, Config withFallback, ActorSystemSetup setup)
   at Akka.Actor.ActorSystem.Create(String name, ActorSystemSetup setup)
   at Akka.Hosting.AkkaConfigurationBuilder.<>c.<ActorSystemFactory>b__28_0(IServiceProvider sp)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Akka.Hosting.AkkaHostedService.StartAsync(CancellationToken cancellationToken)

Environment
MacOS Monetery, .NET 6

Add support for Akka.Logger.Serilog.Hosting

Is your feature request related to a problem? Please describe.
I'm successfully using the hosting package, but I still have to configure Serilog in hocon, and ensure that the Akka.Logger.Serilog package is added to the project.

Describe the solution you'd like
Add a project for Akka.Logger.Serilog.Hosting that includes the dependency on Akka.Logger.Serilog, and has a builder extension method for WithSerilogLogger(string loglevel)

Describe alternatives you've considered
N/A

Additional context
A simple extension method could be added that sets the logger in the hocon

public static class AkkaLoggerSerilogHostingExtensions
    {
        /// <summary>
        /// Configures Serilog logger.
        /// </summary>
        /// <param name="builder">The builder instance being configured.</param>
        /// <param name="loglevel">Specifies loglevel for <see cref="ActorSystem"/></param>
        /// <returns>The same <see cref="AkkaConfigurationBuilder"/> instance originally passed in.</returns>
        public static AkkaConfigurationBuilder WithSerilogLogger(this AkkaConfigurationBuilder builder, string loglevel)
        {
            if (!string.IsNullOrEmpty(loglevel))
            {
                builder.AddHocon($"akka {{ loglevel={loglevel},  loggers=[\"Akka.Logger.Serilog.SerilogLogger, Akka.Logger.Serilog\"]}}", HoconAddMode.Prepend);
            }

            return builder;
        }
    }

I have this code running, so let me know if you'd like me to send a PR.

Akka.Persistence.Hosting: add support for `IEventAdapters`

Is your feature request related to a problem? Please describe.

Based on a YouTube comment here: https://www.youtube.com/watch?v=Mnb9W9ClnB0&lc=UgwAV82O9_6CgesxVCl4AaABAg

This is really cool! Is there a plan to include event-bindings as being configurable? We have some persisted events that are now need to be upcasted, but I don't see a way to do that.

cc @briansain is that right? Doing this for the IEventAdapter in Akka.Persistence is what you meant?

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Akka.Persistence: Add support for adding `IEventAdapter`

Is your feature request related to a problem? Please describe.

It'd be great if we had a strongly typed way of adding read / write IEventAdapter instances to Akka.Persistence through Akka.Hosting.

Describe the solution you'd like

We'd need a stand-alone Akka.Persistence.Hosting binary since the event adapters aren't specific to any journal implementation, and it'd need to have a method like this:

builder.WithEventAdapter<TAdapter>(...) where TAdapter:IEventAdapter

Behind the scenes this could build up the appropriate HOCON, extracted from the strongly typed arguments, without needing to add any new Setup types to Akka.NET.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

`.WithShardRegion` does not add default configuration for cluster sharding

Version Information
Version of Akka.Hosting? 1.0.3
Which Akka.Hosting Modules? Akka.Cluster.Hosting

Describe the bug
IHost fails to load with .WithShardRegion because singleton default configuration was not added

To Reproduce

builder
    .ConfigureLoggers(logger =>
    {
        logger.LogLevel = LogLevel.InfoLevel;
        logger.AddLoggerFactory();
    })
    .AddHocon(@"
akka.cluster.min-nr-of-members = 3
akka.cluster.sharding.snapshot-after = 20
akka.actor.ask-timeout = 3s", HoconAddMode.Prepend)
    .WithCustomSerializer(
        serializerIdentifier: "customSerializer",
        boundTypes: new [] {typeof(CustomShardedMessage)}, 
        serializerFactory: system => new CustomSerializer(system))
    .WithRemoting("localhost", port)
    .WithClustering()
    .WithShardRegion<ShardRegion>(
        "test",
        EntityActor.Props,
        new MessageExtractor(), 
        new ShardOptions
        {
            RememberEntities = false,
            StateStoreMode = StateStoreMode.Persistence
        })
    .WithJournal(journalId, journalBuilder =>
    {
        journalBuilder.AddWriteEventAdapter<EventAdapter>(
            eventAdapterName: "customMessage",
            boundTypes: new[] { typeof(int), typeof(string) });
    });

Throws an NRE from inside ClusterShardingSettings.Create() because akka.cluster.sharding.coordinator-singleton does not exist

Adding .WithFallback(ClusterSharding.DefaultConfig()).WithFallback(ClusterSingletonManager.DefaultConfig()); into my config fixes the issue.

Akka.Cluster.Hosting: `ShardOptions.PassivateIdleEntityAfter` overwrites custom `akka.cluster.sharding.passivate-idle-entity-after` setting in HOCON

Version Information
Version of Akka.NET? v1.5.6
Which Akka.NET Modules? Akka.Cluster.Hosting

Describe the bug

Not 100% if this is a bug or not yet (going off an end-user's report), but if I set

akka.cluster.sharding.passivate-idle-entity-after = 24h #24 hours

In a separate .hocon file added via the AddHoconFile method (with prepending et al done correctly), but if I start any ShardRegion using the default ShardOptions (without specifying ShardOptions.PassivateIdleEntityAfter) I get the following logs:

image

This indicates that we're still using the default 2 minutes setting, and I believe that's because of our default value for ShardOptions here:

public TimeSpan? PassivateIdleEntityAfter { get; set; } = TimeSpan.FromMinutes(2);

To Reproduce

  1. Create ActorSystem with akka.cluster.sharding.passivate-idle-entity-after = 24h
  2. Start ShardRegion using Akka.Hosting;
  3. Check to see if the passivation timeout is 2 minutes instead of 24 hours.

Additional context

We should probably make null the default value here so that way the HOCON values will be used if not user-supplied value is defined.

WithDistributedPubSub throws NullReferenceException

Version Information
Version of Akka.NET? 1.4.28
Which Akka.NET Modules? Akka.Cluster.Hosting 0.3.0

Describe the bug
I'm trying to configure DistributedPubSub using WithDistributedPubSub, but getting System.NullReferenceException

To Reproduce
Steps to reproduce the behavior:

  1. Use WithDistributedPubSub("pub-sub-host") extension method on builder

Expected behavior
A clear and concise description of what you expected to happen.

Configure DistributedPubSub with the role

Actual behavior
What actually happened and how did it differ from your expectations?

Getting the following exception

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at Akka.Configuration.Config.Contains(Config other)
   at Akka.Configuration.Config.WithFallback(Config fallback)
   at Akka.Configuration.Config.GetConfig(String path)
   at Akka.Configuration.Config.GetConfig(String path)
   at Akka.Configuration.Config.GetConfig(String path)
   at Akka.Configuration.Config.GetConfig(String path)
   at Akka.Configuration.Config.GetConfig(String path)
   at Akka.Configuration.Config.GetConfig(String path)
   at Akka.Configuration.Config.GetConfig(String path)
   at Akka.Configuration.Config.GetConfig(String path)
   at Akka.Configuration.Config.GetConfig(String path)
   at Akka.Cluster.Tools.PublishSubscribe.DistributedPubSubSettings.Create(ActorSystem system)
   at Akka.Cluster.Tools.PublishSubscribe.DistributedPubSub..ctor(ExtendedActorSystem system)
   at Akka.Cluster.Tools.PublishSubscribe.DistributedPubSubExtensionProvider.CreateExtension(ExtendedActorSystem system)
   at Akka.Actor.ExtensionIdProvider`1.Akka.Actor.IExtensionId.CreateExtension(ExtendedActorSystem system)
   at Akka.Actor.Internal.ActorSystemImpl.<>c__DisplayClass61_0.<RegisterExtension>b__1()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1.CreateValue()
   at System.Lazy`1.get_Value()
   at Akka.Actor.Internal.ActorSystemImpl.TryGetExtension(Type extensionType, Object& extension)
   at Akka.Actor.Internal.ActorSystemImpl.GetExtension(IExtensionId extensionId)
   at Akka.Actor.ExtensionIdProvider`1.Get(ActorSystem system)
   at Akka.Actor.ExtensionIdProvider`1.Akka.Actor.IExtensionId.Get(ActorSystem system)
   at Akka.Actor.Internal.ActorSystemImpl.RegisterExtension(IExtensionId extension)
   at Akka.Actor.ActorSystemWithExtensions.WithExtension[T,TI](ActorSystem system)
   at Akka.Cluster.Tools.PublishSubscribe.DistributedPubSub.Get(ActorSystem system)
   at Akka.Cluster.Tools.Client.ClusterClientReceptionist.get_PubSubMediator()
   at Akka.Cluster.Tools.Client.ClusterClientReceptionist.CreateReceptionist()
   at Akka.Cluster.Tools.Client.ClusterClientReceptionist..ctor(ExtendedActorSystem system)
   at Akka.Cluster.Tools.Client.ClusterClientReceptionistExtensionProvider.CreateExtension(ExtendedActorSystem system)
   at Akka.Actor.ExtensionIdProvider`1.Akka.Actor.IExtensionId.CreateExtension(ExtendedActorSystem system)
   at Akka.Actor.Internal.ActorSystemImpl.<>c__DisplayClass61_0.<RegisterExtension>b__1()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1.CreateValue()
   at System.Lazy`1.get_Value()
   at Akka.Actor.Internal.ActorSystemImpl.TryGetExtension(Type extensionType, Object& extension)
   at Akka.Actor.Internal.ActorSystemImpl.GetExtension(IExtensionId extensionId)
   at Akka.Actor.ExtensionIdProvider`1.Get(ActorSystem system)
   at Akka.Actor.ExtensionIdProvider`1.Akka.Actor.IExtensionId.Get(ActorSystem system)
   at Akka.Actor.Internal.ActorSystemImpl.RegisterExtension(IExtensionId extension)
   at Akka.Actor.Internal.ActorSystemImpl.ConfigureExtensions(IEnumerable`1 extensionIdProviders)
   at Akka.Actor.Internal.ActorSystemImpl.Start()
   at Akka.Actor.ActorSystem.CreateAndStartSystem(String name, Config withFallback, ActorSystemSetup setup)
   at Akka.Actor.ActorSystem.Create(String name, ActorSystemSetup setup)
   at Akka.Hosting.AkkaConfigurationBuilder.<>c.<ActorSystemFactory>b__28_0(IServiceProvider sp)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
   at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Program.<Main>$(String[] args) in /Users/jblackburn/repos/signify/architecture/architecture.templates/templates/akkaclusterservice/src/akkacluster.Service/Program.cs:line 25
   at Program.<Main>(String[] args)

Environment
Docker, dotnet 6

** Additional Info**
It looks like the hocon config is incorrect akka.cluster.pub-sub, should it be?

middle.AddHocon($"akka.cluster.pub-sub.role = \"{role}\"", HoconAddMode.Prepend);

ActorRegistry returning ActorRefs.Nobody for not found actors introduces hard-to-find bugs

Is your feature request related to a problem? Please describe.

We currently refactored our project to use Akka.Hosting.
We previously had our own-handrolled solution to register root actors into Microsofts DI so it was nice to see we can just use the new library features now.

We have several root actors, some their child actors need other root actors injected into their constructors (via the DependencyResolver.For(Context.System) pattern).

Those root actors are sometimes used in the PreStart hook, so some root actors have to be available already at construction time of other root actors. This forms a kind of dependency order between our root actors.

If root actors get constructed in the wrong order, those injected root actors have their Actor refs set to Nobody when accessed by IRequiredActor.ActorRef, which means a Tell call will still succeed, but will land in the dead letter queue of course because the sender is Nobody. This was hard to diagnose until we got our construction order of the root actors right.

We made our code now more fail-safe by introducing some extensions which we use i all places where we Tell/Forward messages to injected RequiredActors:

using Akka.Actor;
using Akka.Hosting;
using AtlasCopco.Enso.Actors.Administrator;
using AtlasCopco.Enso.Contracts.Actors;
using Serilog;
using System.Linq;
using System.Reflection;

namespace AtlasCopco.Enso.Actors;

public static class AkkaExtensions
{
    private static void CheckInitialized<TActor>(IRequiredActor<TActor> requiredActor)
    {
        if (requiredActor.ActorRef.Equals(Nobody.Instance))
            throw new ActorRegistryException($"requiredActor {typeof(TActor).Name} not yet constructed!");
    }

    public static void Tell<TActor>(this IRequiredActor<TActor> requiredActor, object message)
    {
        CheckInitialized(requiredActor);
        requiredActor.ActorRef.Tell(message);
    }

    public static void Forward<TActor>(this IRequiredActor<TActor> requiredActor, object message)
    {
        CheckInitialized(requiredActor);
        requiredActor.ActorRef.Forward(message);
    }

    public static void Tell<TActor>(this IRequiredActor<TActor> requiredActor, object message, IActorRef sender)
    {
        CheckInitialized(requiredActor);
        requiredActor.ActorRef.Tell(message, sender);
    }
}

Describe the solution you'd like
A clear and concise description of what you want to happen.

I am not sure why RootActors got registered as Nobody actors in the Registry so the MissingActorRegistryEntryException didn't trigger.

Describe alternatives you've considered

Of course we could use GetAsync() everywhere in our actors for injected root actors (with PipeTo/Self and forwarding message types and handlers), but this would make the code more complicated and messy.

Additional context
Add any other context or screenshots about the feature request here.

Akka.Hosting.TestKit: just have `ConfigureLogging` "do the right thing" by default and automatically wire up `ITestOutput`

Is your feature request related to a problem? Please describe.

Each time I write a test I have to remember how to pipe the MSFT Logging, ILoggingAdapter, and the xUnit ITestOutputHelper together and it's very cumbersome.

Describe the solution you'd like

base implementation of protected virtual void ConfigureLogging(ILoggingBuilder builder){} should just do this for me unless I specify otherwise. Maybe even make the ITestOutputHelper a required constructor parameter (breaking change but no one else is using this yet)

pbm hosting in .NET 4.7.2 fsi (F# 8) of SSIS script task

Version of Akka.NET? .net 4.7.2 in SSIS script task
Which Akka.NET Modules? Akka.Hosting &

To Reproduce
Create a HostBuilder and start it via F# fsiEvaluationSession hosted in SSIS 2022 script task (.NET 4.7.2) with .AddPetabridgeCmd

Expected behavior
The pbm client should easily connect pbm host, but not.

Actual behavior
The same F# script would successfully create the (web) service and ActorSystem would bring pbm host started in normal Fsi session.
But if the script is execut ed in a fsiEvaluationSession hosted in SSIS, the ActorSystem would not bring pbm host started with HOCON correctly configured.

Environment
Windows Server 2022/SQL Server 2022 (SSIS)

Additional context
It must to manually start pbm with

let cmd = PetabridgeCmd.Get asys
cmd.Start()

MAUI support for Akka.Hosting (disable `IHostedService`)

Is your feature request related to a problem? Please describe.

I spent some time chasing down a .NET MAUI user's issue getting IRequiredActor<TKey> to work correctly inside their application. Their ActorRegistry settings were correct and their code correctly binded Akka.NET to the MauiAppBuilder - the IHost equivalent for MAUI:

public static MauiAppBuilder Akka(this MauiAppBuilder builder)
{
    builder.Services.AddAkka("MyActorSystem", akkaConfigurationBuilder =>
    {
        akkaConfigurationBuilder.PipeActor();
    });

    return builder;
}

Yet the IRequiredActor<TKey> injection doesn't work and throws an actor resolution error upon trying, even with Akka.Hosting v1.5.4+ - why is that?

This is because MAUI doesn't support IHostedServices: dotnet/maui#2244

Describe the solution you'd like

Assuming that the rest of the core Akka DLL can even run on MAUI (i.e. there are no major AOT or other issues that would cause it to run not on the client), one solution we could try is configuring Akka.Hosting to run and launch all actors without the IHostedService. I think it might be possible to remove the IHostedService altogether without very little downside for regular users, possibly:

  • We can still shut the ActorSystem down using the IApplicationLifetime and continuation Tasks without needing the IHostedService.StopAsync method;
  • We already start the ActorSystem prior to the IHostedService.StartAsync method;
  • We already have a separate data structure for keeping track of what needs to be launched at startup - we could, in theory, just execute that as-is in the foreground with no code changes;
  • We already blow up the foreground process and kill it if there are problems starting any of the actors or Startup tasks - we'd just be doing that with one less layer of indirection under this arrangement.

To implement this, we'd need an integration test that demonstrates Akka.NET failing to boot up under MAUI now - and we'd need to get that test to turn green by removing the AkkaHostedService.

Describe alternatives you've considered

Alternatively, rather than removing the AkkaHostedService we could add an extension method that starts up Akka.NET without the IHostedService - or we could add an explicit MAUI build target and using compiler directives that avoid the use of the IHostedService in MAUI only. Those would lower the risk of Akka.Hosting having adverse side-effects for existing users.

Additional context
Add any other context or screenshots about the feature request here.

"There is no active ActorContext" exception in Context.System while running tests

Version Information
Version of Akka.NET? 1.5.10
Which Akka.NET Modules? Akka.Hosting

Describe the bug
There is no active ActorContext exception in Context.System while running tests
(when trying to create actor props with the DependencyResolver)

To Reproduce
Steps to reproduce the behavior:

  1. Clone https://github.com/gunters63/DIPropsFail
  2. Run tests

Expected behavior
Test is green

Actual behavior

There is no active ActorContext, this is most likely due to use of async operations from within this actor.
   at Akka.Actor.ActorBase.get_Context()
   at Akka.Actor.UntypedActor.get_Context()
   at DiPropsFail.DiPropsFailTest.NonRootActorWithDi.Props() in D:\temp\DIPropsFail\DIPropsFail\DiPropsFailTest.cs:line 36
   at DiPropsFail.DiPropsFailTest.NonRootActorWithDiTest() in D:\temp\DIPropsFail\DIPropsFail\DiPropsFailTest.cs:line 20

Screenshots
image

Environment
Rider unter Windows 11

Additional context
The strange things is:

  • sometimes the test is green when I run it in a bigger test suite together with other tests
  • when I copy this exact unit test file into the Akka.Hosting.TestKit.Tests folder of a cloned repo it works reliably.

I suspect this is maybe a racing condition.

Tighter integration with Extensions.DependencyInjection

Is your feature request related to a problem? Please describe.
I'm trying out Akka.Hosting in my ASP.NET application and for most parts I'm very happy and didnt need to use any HOCON fine tuning. As a ASP.NET user I'm a bit confused at the state of DependencyInjection when using AddAkka and ActorRegistry. From my past experience with Akka.net I know I can use the DependencyResolver and build props using it to spawn an actor using constructor injection but I have no reference to the DependencyResolver (or the Actor System) in my AddAkka call, right?

Let's say I want to add a cluster singleton via WithSingleton:

services.AddAkka(name, (akka, provider) => {
   ...
   akka.WithSingleton<ClusterCronJobSchedulerActor>("cron-scheduler", Props.Create<ClusterCronJobSchedulerActor>());
   ...
});

Here I would like to use the "DI-enabled" props via DependencyResolver but I have no reference to the ActorSystem to create my DependencyResolver. I could use WithActors but this defeats the purpose of WithSingleton. Am I doing something wrong here?

Describe the solution you'd like
My ideal solution would be one where I didn't have to think about when to use DependencyResolver. My thinking is, when using Akka.Hosting the possibility is high that I also want to be integrated in my ServiceProvider but I expect this isn't possible because we can't just assume constructor injection everywhere as this would make actor creation slower for simple props.

Describe alternatives you've considered
My exact problem could be solved by either including ActorSystem/DependencyResolver somewhere in the AddAkka-lambda (akka.System? akka.DependencyResolver? Is the system already available when in building phase?) or providing a factory function everywhere it might be needed (Func<ActorSystem, Props>) to use DependencyResolver.Get(system).

This problem also exists with WithShardRegion calls.

Add Support for `Microsoft.Extensions.Diagnostics.IHealthCheck`?

Is your feature request related to a problem? Please describe.

We have a couple of different implementations for health-checks floating around in Akka.NET - all of them expressing the same idea: let the platform supervisor (i.e. Kubernetes, Cloud, process monitor etc) know when the Akka.NET service is degraded as a result of common problems:

  • Inability to join cluster;
  • Akka.Persistence being unavailable;
  • Failure to start ActorSystem (i.e. an Akka.Remote port binding failure.)

The first implementation is one I rolled myself a few years ago - and it works well enough: https://github.com/petabridge/akkadotnet-healthcheck - however, it definitely suffers from byte rot to some degree and there isn't a "one liner" way of integrating it with something like ASP.NET.

The second implementation, which is not fully baked as far as I know, is inside Akka.Management's HTTP APIs: https://github.com/akkadotnet/Akka.Management/tree/dev/src/management/Akka.Management#exposed-rest-api-endpoints - this takes a hard dependency on ASP.NET and you basically have to ship your Akka.NET application with a lightweight HTTP API on top of it. Plus, I don't think those checks are configurable beyond whether or not a cluster has been formed.

Describe the solution you'd like

Micrsoft.Extensions.Diagnostics.IHealthCheck provides a somewhat standard way of plugging healthcheck infrastructure together through the HealthCheckService class. Since Akka.Hosting is already built around the Microsoft.Extensions.[DI|Hosting|Configuration] libraries anyway, why not support having pluggable healthchecks from services like Akka.Management or Akka.Persistence available as options on the builder interface here?

Describe alternatives you've considered

Making Akka.Management the end-all-be-all for this.

Add support for modifying the sql settings in the `WithSqlServerPersistence` method

Is your feature request related to a problem? Please describe.
We are unable to modify the schema-name, table-name and auto-initialize values when I use the .WithSqlServerPersistence() extension method.

Describe the solution you'd like
We would like to be able to modify the schema-name, table-name and auto-initialize values for both the Journal and Snapshot tables when I use the .WithSqlServerPersistence() extension method.

Describe alternatives you've considered
We've tried adding a hocon config for this, like so:

.AddHocon(@"akka.persistence {
	                journal {
                        plugin = ""akka.persistence.journal.sql-server""
	                    sql-server {
                            schema-name = akka
                            auto-initialize = on
                        }
                    }
	                snapshot-store {
                        plugin = ""akka.persistence.snapshot-store.sql-server""
		                sql-server {
                            schema-name = akka
                            auto-initialize = on
                        }
                    }
                }");

But that does not work. The tables are still initialized with dbo as the schema name.

IConfiguration To HOCON Adapter always converts all keys to lower-case

Version Information
Version of Akka.NET? 1.5,12
Which Akka.NET Modules?

Describe the bug
In HOCON I can configure Akka.Net to use the Hyperion serializer like this:

  actor {
    serializers {
      hyperion = "Akka.Serialization.HyperionSerializer, Akka.Serialization.Hyperion"
    }
    serialization-bindings {
      "System.Object" = hyperion
    }
  }

I tried to move my HOCON configuration to ASP.NET IConfiguration (in appsettings.json).
Fist I had a problem with the dot character in "System.Object" because the HOCON adapter treats all keys as potentially dotted paths. I found out I can quote the key like this in my appsettings.json (this should probably be documented):

{
  "akka": {
    "actor": {
      "serializers": {
        "hyperion": "Akka.Serialization.HyperionSerializer, Akka.Serialization.Hyperion"
      },
      "serialization-bindings": {
        "\"System.Object\"": "hyperion"
      }
    }
  }
}

I have still the problem that the HOCON adapter converts all keys to lowercase (see ExpandKey in ConfigurationHoconAdapter.cs). I get the following warning at startup:

[WARNING][08.11.2023 08:29:24.190Z][Thread 0001][ActorSystem(EnsoBackend)] The type name for message/serializer binding 'hyperion' did not resolve to an actual Type: 'system.object'

and if I log the HCON config with "log-config-on-start": true I can see the problem:

image

BackgroundService DI of IRequiredActor for reference to an Actor fails with 'No actor registered'

Version Information
Akka.Hosting 1.0.2

Describe the bug
Creating a Microsoft.Extensions.Hosting.BackgroundService with a DI fails to inject a IRequiredActor interface with the error of Akka.Hosting.MissingActorRegistryEntryException: 'No actor registered for key <ActorName>'

To Reproduce
The simplest code reproduction of this error is

using Akka.Actor;
using Akka.Hosting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHostedService<ThingRunningInTheBackground>();

builder.Services.AddAkka("mysystem", configurationBuilder =>
{
    configurationBuilder.WithActors((system, registry, resolver) =>
    {
        var leadActor = system.ActorOf(Props.Create(() => new LeadActor()));

        registry.TryRegister<LeadActor>(leadActor);
    });
});

var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();

public class LeadActor : ReceiveActor
{
    public LeadActor()
    {
        Receive<string>(Console.Write);
    }
}

public class ThingRunningInTheBackground : BackgroundService
{
    private readonly IActorRef _actor;

    public ThingRunningInTheBackground(IRequiredActor<LeadActor> actor)
    {
        _actor = actor.ActorRef;
    }
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        throw new NotImplementedException();
    }
}

Environment
Windows 10
Net 7.0

Discord Discussions
Chatting on discord about this problem @Aaronontheweb

What the problem is

I'm not sure how to work around this problem inside IRequiredActor just yet
but the issue is that all of your actors are started in another IHostedService managed by Akka.NET
and that hasn't had a chance to run and populate the ActorRegistry by the time your BackgroundService starts
so until I come up with a way to force IRequiredActor to do this behind the scenes (probably by blocking, unless there's an await-able way of doing this)

The current solution to the approach

so the solution to this is to have the ActorRegistry injected into your BackgroundService and then call ActorRegistry.GetAsync on it
that call will return a Task you can await on
and the task will be completed as soon as the actor gets added to the registry
which should only be a on the order of 100s of milliseconds

Tweet by Aaron asking about some approachs
https://twitter.com/Aaronontheweb/status/1623064967724298251

Taking the above simple reproduction this below code works noting the injection of ActorRegistry into the constructor and using the _actorRegistry.GetAsync to get the actor reference.

using Akka.Actor;
using Akka.Hosting;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHostedService<ThingRunningInTheBackgroundUsingRegistry>();

builder.Services.AddAkka("mysystem", configurationBuilder =>
{
    configurationBuilder.WithActors((system, registry, resolver) =>
    {
        var leadActor = system.ActorOf(Props.Create(() => new LeadActor()));

        registry.TryRegister<LeadActor>(leadActor);
    });
});

var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();

public class LeadActor : ReceiveActor
{
    public LeadActor()
    {
        Receive<string>(Console.Write);
    }
}

public class ThingRunningInTheBackgroundUsingRegistry : BackgroundService
{
    private IActorRef? _actorReference;
    private readonly ActorRegistry _actorRegistry;

    public ThingRunningInTheBackgroundUsingRegistry(ActorRegistry actorRegistry)
    {
        _actorRegistry = actorRegistry;
    }
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _actorReference = await _actorRegistry.GetAsync<LeadActor>(stoppingToken);

        _actorReference.Tell("Message");
    }
}

Expose AskTimeout configuration in Akka.Hosting

Is your feature request related to a problem? Please describe.
Akka.Hosting is created to 'get rid of' hocon configuration files to have a more seamless and easy model of setting up Akka.NET. Right now, in our solution, we only need hocon to configure the ask-timeout setting.

Describe the solution you'd like
Expose ask-timeout also via Akka.Hosting so that we can completely remove our .hocon files.

Akka.Hosting works really great so it helps if more options are getting exposed.

Make most major Akka.NET configuration sections parseable via Microsoft.Extensions.Configuration

    Here's what we have to do in the current stable version of Akka.Hosting:
var akkaSection = context.Configuration.GetSection("Akka");

// maps to environment variable Akka__ClusterIp
var hostName = akkaSection.GetValue<string>("ClusterIp", "localhost");

// maps to environment variable Akka__ClusterPort
var port = akkaSection.GetValue<int>("ClusterPort", 0);

var seeds = akkaSection.GetValue<string[]>("ClusterSeeds", new[] { "akka.tcp://SqlSharding@localhost:7918" })
    .Select(Address.Parse)
    .ToArray();

It would be amazing if we could just do this instead (and if we decide to go down this route, we should write a unit test that looks like this):

appsettings.json

{
    "Logging": {
        "LogLevel": {
            "Default": "Debug",
            "System": "Information",
            "Microsoft": "Information"
        }
    },
    "Akka": {
        "RemoteOptions":{
            "Hostname": "localhost",
            "Port": 8081,
            "PublicHostname": "localhost",
            "PublicPort": 8081
        },
        "ClusterOptions": {
            "SeedNodes": "akka.tcp://ClusterSystem@localhost:8081",
            "Roles": ["backend"]
        }
    }
}

program.cs

var akkaSection = context.Configuration.GetSection("Akka");
var clusterConfig = akkaSection.Get<ClusterOptions>(); // ideally, this should work

We've not really stated this as an explicit goal for the project before, but in the context of our "pit of success" mission for this library I think it's probably worth doing before we ship 1.0. What do you think?

Originally posted by @Aaronontheweb in #149 (comment)

Akka.Hosting.TestKit: `AwaitAssertAsync` only runs once

Version Information
Version of Akka.NET? 1.5.0
Which Akka.NET Modules? Akka.Hosting.TestKit

Describe the bug

await AwaitAssertAsync(async () =>
{
    var r = await testActorRef.Ask<string>(new MyTestActor.GetData(), TimeSpan.FromMilliseconds(100));
    r.Should().Be("BackgroundService started");
}, RemainingOrDefault, TimeSpan.FromMilliseconds(150));

The above assertion runs once and then quits.

Add built-in cluster options to `ClusterOptions` class

    It would also be nice to be able to set cluster related config, like minimum members.

Originally posted by @PeterHageus in #106 (comment)

Doing this is contingent upon whether or not the API is workable / easily express-able in C#.

None of these values should be required to start Akka.Cluster via Akka.Hosting.

  • min-nr-members
  • {role}.min-nr-members <-- might be too difficult to express.
  • AppVersion
  • log-info and log-info-verbose

We don't really want to get into stuff like failure detector customization - that should be done in HOCON.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.