Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BREAKING_CHANGES_V1.6.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ of `Behavior`, `Wire`, `API` (combine with `+`).

| Status | PR / Branch | Component | Type | Change | Migration |
|--------|-------------|-----------|------|--------|-----------|
| Planned | `aot/m1-c-props-and-loggers` | `Akka` (core / `Akka.Actor.Props`, `Akka.Event.LoggingBus`), `Akka.DependencyInjection` | Behavior + API | Two changes. (1) The whole `Props` actor-type flow now carries one trimming contract, `[DynamicallyAccessedMembers(PublicConstructors \| Interfaces)]`, so the trimmer keeps the constructor `Activator` calls and the `IRequiresMessageQueue<>` interfaces the mailbox requirement check reads off the actor type: the `Type`-taking `Props` constructors, `Props.Type`, `Props.Create(Type, params object[])`, all three `Props.Create<TActor>` overloads, `Props.CreateBy<TProducer>`, `IIndirectActorProducer.ActorType`, `ActorRefFactoryExtensions.ActorOf<TActor>`, `ExtendedActorSystem.SystemActorOf<TActor>` (and the `ActorSystemImpl` override), plus -- in `Akka.DependencyInjection` -- the three `IDependencyResolver.Props` members and their implementations on `DependencyResolver`, `ServiceProvider` and `ServiceProviderDependencyResolver`, and the internal `ServiceProviderActorProducer`. Separately, `ActorSystemWithExtensions.WithExtension<T>(ActorSystem, Type)` and `WithExtension<T, TI>` gain `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]` -- the narrower contract, because those take an `IExtensionId`, not an actor type, and call the parameterless `Activator.CreateInstance`. No runtime behavior changes; the attributes are visible only to trim analysis and change no signature a compiler binds to. (2) `LoggingBus.StartDefaultLoggers` resolves `akka.loggers` from new built-in name tables (all three loggers Akka.dll ships -- `Akka.Event.DefaultLogger`, `Akka.Event.StandardOutLogger` and `Akka.Event.TraceLogger` -- each under three spellings: the bare name core's own `akka.conf` ships, the short assembly-qualified name that `TraceLogger`'s own documentation tells users to write, and the full versioned assembly-qualified name Akka.Hosting writes) before falling back to `Type.GetType`, and that fallback only runs while dynamic type loading is on. With the switch on, which is what every existing application gets, the same config resolves to the same logger as before, for every input. With the switch off, only the names in the table resolve: any other logger throws a `ConfigurationException` naming `akka.loggers`, the value and the switch, where it previously threw `ConfigurationException` "Logger specified in config cannot be found". | None at the default -- the switch is on unless a project file explicitly turns it off. A trimmed or Native AOT application that turns the switch off must leave `akka.loggers` at the built-in loggers. The new attributes need no source change, and nothing warns unless a project sets `EnableTrimAnalyzer` / `IsTrimmable` / `IsAotCompatible` -- no project in this repository does yet. Once one does: passing an unannotated `Type` into any of these members reports `IL2072` when it came from a method return or a field, and `IL2067` when it came from one of the caller's own parameters; an unannotated generic argument reports `IL2091`. Fix either by annotating the `Type` or type parameter it flows from with `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors \| DynamicallyAccessedMemberTypes.Interfaces)]`, or by passing a `typeof(...)`. Because the attribute is part of the contract, an *implementor* has to repeat it verbatim rather than flow it: a class implementing `IIndirectActorProducer.ActorType` or any of the three `IDependencyResolver.Props` members reports `IL2092` / `IL2093` (parameter and return annotation mismatch) or `IL2095` (type-parameter mismatch) until it carries the identical attribute, and so does an override of `ExtendedActorSystem.SystemActorOf<TActor>`. |
| Planned | `aot/m1-b-mailboxes` | `Akka` (core / `Akka.Dispatch.Mailboxes`) | Behavior + API | `Mailboxes` resolves the mailbox names core's own `akka.conf` ships from two built-in tables (`BuiltInMailboxTypes`, `BuiltInMessageQueueSemantics`) instead of `Type.GetType`, so the trimmer and the Native AOT compiler can see which type is loaded. Two observable changes. (1) A built-in `mailbox-type` is constructed directly, so when its constructor rejects its config the `ArgumentException` from `Mailboxes.Lookup` carries that constructor's own exception as `InnerException` instead of the `TargetInvocationException` `Activator.CreateInstance` wrapped it in, and names the configured spelling (e.g. `Akka.Dispatch.BoundedMailbox, Akka`) where it previously named `Type.ToString()`; relatedly, the non-zero-push-timeout warning now runs outside that `try`, so an exception from a custom `IProducesPushTimeoutSemanticsMailbox.PushTimeout` getter propagates as itself rather than wrapped. (2) With the `Akka.DynamicTypeLoading` switch **off**, a key under `akka.actor.mailbox.requirements` that is neither built in nor resolvable now throws a `ConfigurationException` out of `ActorSystem.Create`, where the switch-on path still logs a warning and skips that one binding; an unknown `mailbox-type` or `mailbox-requirement` likewise throws a `ConfigurationException` naming the setting, the value and the switch, in place of the previous `ConfigurationException` / `TypeLoadException`. With the switch on - the default, and what every existing application gets - every other input resolves to the same mailbox as before. Separately, `MailboxType` and the `Type` parameters of `Mailboxes.HasRequiredType`, `Mailboxes.ProducesMessageQueue` and `Mailboxes.GetRequiredType` gain `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]` so that trimming keeps `IProducesMessageQueue<>` / `IRequiresMessageQueue<>` on subclasses. | None at the default -- the switch is on unless a project file explicitly turns it off. Code that caught `TargetInvocationException` around a rejected `mailbox-capacity` / `mailbox-push-timeout-time` should catch the inner exception type instead, and code asserting on the exact `Cannot instantiate MailboxType ...` text should expect the configured spelling for the five built-in types. A trimmed or Native AOT application that turns the switch off must keep `mailbox-type`, `mailbox-requirement` and every key under `akka.actor.mailbox.requirements` at built-in values. The new attributes are visible only to trim analysis -- they change no signature a compiler binds to -- but a caller that has trim analysis enabled and passes an unannotated `Type` into those three methods now gets `IL2072` at its own call site: annotate the `Type` it flows from with `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]`, or pass a `typeof(...)`. |
| Planned | `aot/m1-a-feature-switch-and-first-tables` | `Akka` (core / actor system bootstrap) | Behavior | Adds the internal trimming / Native AOT feature switch `Akka.DynamicTypeLoading` (default **on**) and puts built-in lookup tables in front of the first three HOCON type-name sites: `akka.stdout-logger-class` and `akka.logger-formatter` in `Settings`, and `akka.scheduler.implementation` in `ActorSystemImpl.ConfigureScheduler`. Each site now checks a `BuiltIn*` table before falling back to `Type.GetType`. Each table carries two spellings of every name it knows -- the bare `Ns.T` and the `Ns.T, Akka` form, which is what core's own `akka.conf` uses -- and the lookup first runs the configured value through the new internal `Akka.Util.TypeExtensions.StripAssemblyIdentity`, which removes the `Version=`, `Culture=`, `PublicKeyToken=`, `ProcessorArchitecture=`, `Retargetable=` and `ContentType=` components. With the switch on, which is what every existing application gets, the same config resolves to the same type as before, with two narrow differences: (1) a built-in name is constructed directly, so a constructor that rejects its config (e.g. `akka.scheduler.tick-duration` below 10 ms, which `HashedWheelTimerScheduler` refuses) now surfaces its own `ArgumentOutOfRangeException` / `ConfigurationException` instead of the `TargetInvocationException` that `Activator.CreateInstance` wrapped it in; (2) a built-in type name written with a full assembly-qualified name of a different Akka version (what `Akka.Hosting` writes into HOCON, carried across an upgrade) now resolves where it previously threw. `Type.GetType` rejects the version mismatch, so on `dev` such a value fails in both switch states -- measured: `ArgumentException("Could not load type of ...")` for `akka.logger-formatter`/`akka.stdout-logger-class`, and `FileNotFoundException` for `akka.scheduler.implementation`, whose `Type.GetType` call passes `throwOnError: true`. Custom (non-built-in) names are unchanged, since the reflection fallback still receives the raw value. An application that turns the switch off with `<RuntimeHostConfigurationOption Include="Akka.DynamicTypeLoading" Value="false" Trim="true" />` gets only the names in the tables: anything else throws a `ConfigurationException` naming the setting, the value and the switch, where it previously threw `TypeLoadException` (scheduler) or `ArgumentException` (stdout logger, log formatter). | None at the default -- the switch is on unless a project file explicitly turns it off. Code that caught `TargetInvocationException` around `ActorSystem.Create` to read a rejected `akka.scheduler.*` value should catch the inner exception type instead. A trimmed or Native AOT application that turns the switch off must leave these three settings at their built-in values. |
| Planned | `fix/aot-provider-constant-type-names` | `Akka` (core / actor system bootstrap) | Behavior | `ActorSystemImpl` now resolves the three built-in actor ref providers from the compile-time constant type names on `ProviderSelection`, with the `Type.GetType` call inlined at each site, so the trimmer and the Native AOT compiler can see which type is loaded and keep it. Two observable consequences: (1) `ProviderSelection.GetProvider` now maps the local provider's type name -- both the bare `"Akka.Actor.LocalActorRefProvider"` that ships in `akka.conf` and the assembly-qualified `"Akka.Actor.LocalActorRefProvider, Akka"` -- onto `ProviderSelection.Local` instead of letting it fall through to `Custom`, so `Settings.ProviderClass` on a default boot now reports `"Akka.Actor.LocalActorRefProvider, Akka"` where it previously reported the bare `"Akka.Actor.LocalActorRefProvider"`. (2) `Settings` only validates `ProviderClass` through `Type.GetType` when the selection is `ProviderSelection.Custom`; the built-in three are validated where they are constructed, and a missing `Akka.Remote` / `Akka.Cluster` reference now throws `ConfigurationException("akka.actor.provider = cluster, but Akka.Cluster is not referenced by this application.")` from `ActorSystemImpl` rather than the generic "not a valid type name" message from `Settings`. Which provider gets constructed for any given config is unchanged. | None for normal use. Code that compares `Settings.ProviderClass` against the bare string `"Akka.Actor.LocalActorRefProvider"` should compare against `ProviderSelection.Local.Instance.Fqn`, or test `Settings.ProviderSelectionType is ProviderSelection.Local`. Code that asserted on the exact text of the misconfiguration exception for `provider = remote` / `provider = cluster` should expect the new message. |
Expand Down

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//-----------------------------------------------------------------------

using System;
using System.Diagnostics.CodeAnalysis;
using Akka.Actor;
using Akka.Configuration;
using Akka.Event;
Expand Down Expand Up @@ -57,7 +58,7 @@ public static DependencyResolver For(ActorSystem actorSystem)
/// <typeparam name="T">The type of actor to instantiate.</typeparam>
/// <param name="args">Optional. Any constructor arguments that will be passed into the actor's constructor directly without being resolved by DI first.</param>
/// <returns>A new <see cref="Akka.Actor.Props"/> instance which uses DI internally.</returns>
public Props Props<T>(params object[] args) where T : ActorBase
public Props Props<[DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] T>(params object[] args) where T : ActorBase
{
return Resolver.Props<T>(args);
}
Expand All @@ -71,7 +72,7 @@ public Props Props<T>(params object[] args) where T : ActorBase
/// </remarks>
/// <typeparam name="T">The type of actor to instantiate.</typeparam>
/// <returns>A new <see cref="Akka.Actor.Props"/> instance which uses DI internally.</returns>
public Props Props<T>() where T : ActorBase
public Props Props<[DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] T>() where T : ActorBase
{
return Resolver.Props<T>();
}
Expand All @@ -85,7 +86,7 @@ public Props Props<T>() where T : ActorBase
/// </remarks>
/// <param name="type">The type of actor to instantiate.</param>
/// <returns>A new <see cref="Akka.Actor.Props"/> instance which uses DI internally.</returns>
public Props Props(Type type)
public Props Props([DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] Type type)
{
return Resolver.Props(type);
}
Expand All @@ -100,7 +101,7 @@ public Props Props(Type type)
/// <param name="type">The type of actor to instantiate.</param>
/// <param name="args">Optional. Any constructor arguments that will be passed into the actor's constructor directly without being resolved by DI first.</param>
/// <returns>A new <see cref="Akka.Actor.Props"/> instance which uses DI internally.</returns>
public Props Props(Type type, params object[] args)
public Props Props([DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] Type type, params object[] args)
{
return Resolver.Props(type, args);
}
Expand Down

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//-----------------------------------------------------------------------

using System;
using System.Diagnostics.CodeAnalysis;
using Akka.Actor;

namespace Akka.DependencyInjection
Expand Down Expand Up @@ -33,7 +34,7 @@ public interface IDependencyResolver
/// <param name="type">The type of actor to instantiate.</param>
/// <param name="args">Optional. Any constructor arguments that will be passed into the actor's constructor directly without being resolved by DI first.</param>
/// <returns>A new <see cref="Akka.Actor.Props"/> instance which uses DI internally.</returns>
Props Props(Type type, params object[] args);
Props Props([DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] Type type, params object[] args);

/// <summary>
/// Used to dynamically instantiate an actor where some of the constructor arguments are populated via dependency injection
Expand All @@ -44,7 +45,7 @@ public interface IDependencyResolver
/// </remarks>
/// <param name="type">The type of actor to instantiate.</param>
/// <returns>A new <see cref="Akka.Actor.Props"/> instance which uses DI internally.</returns>
Props Props(Type type);
Props Props([DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] Type type);

/// <summary>
/// Used to dynamically instantiate an actor where some of the constructor arguments are populated via dependency injection
Expand All @@ -56,6 +57,6 @@ public interface IDependencyResolver
/// <typeparam name="T">The type of actor to instantiate.</typeparam>
/// <param name="args">Optional. Any constructor arguments that will be passed into the actor's constructor directly without being resolved by DI first.</param>
/// <returns>A new <see cref="Akka.Actor.Props"/> instance which uses DI internally.</returns>
Props Props<T>(params object[] args) where T : ActorBase;
Props Props<[DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] T>(params object[] args) where T : ActorBase;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//-----------------------------------------------------------------------

using System;
using System.Diagnostics.CodeAnalysis;
using Akka.Actor;
using Akka.Configuration;
using Akka.Event;
Expand Down Expand Up @@ -61,7 +62,7 @@ public static ServiceProvider For(ActorSystem actorSystem)
/// <typeparam name="T">The type of actor to instantiate.</typeparam>
/// <param name="args">Optional. Any constructor arguments that will be passed into the actor's constructor directly without being resolved by DI first.</param>
/// <returns>A new <see cref="Akka.Actor.Props"/> instance which uses DI internally.</returns>
public Props Props<T>(params object[] args) where T : ActorBase
public Props Props<[DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] T>(params object[] args) where T : ActorBase
{
return Akka.Actor.Props.CreateBy(new ServiceProviderActorProducer<T>(Provider, args));
}
Expand Down Expand Up @@ -98,7 +99,7 @@ internal class ServiceProviderActorProducer : IIndirectActorProducer
private readonly IServiceProvider _provider;
private readonly object[] _args;

public ServiceProviderActorProducer(IServiceProvider provider, Type actorType, object[] args)
public ServiceProviderActorProducer(IServiceProvider provider, [DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] Type actorType, object[] args)
{
_provider = provider;
_args = args;
Expand All @@ -107,9 +108,12 @@ public ServiceProviderActorProducer(IServiceProvider provider, Type actorType, o

public ActorBase Produce()
{
// ActivatorUtilities.CreateInstance picks a public constructor, which the PublicConstructors half of
// Akka.Actor.Props.ActorTypeMembers keeps; the Interfaces half is what the mailbox requirement check needs.
return (ActorBase)ActivatorUtilities.CreateInstance(_provider, ActorType, _args);
}

[DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)]
public Type ActorType { get; }

public void Release(ActorBase actor)
Expand All @@ -124,7 +128,7 @@ public void Release(ActorBase actor)
/// Used to create actors via the <see cref="ActivatorUtilities"/>.
/// </summary>
/// <typeparam name="TActor">the actor type</typeparam>
internal class ServiceProviderActorProducer<TActor> : ServiceProviderActorProducer where TActor:ActorBase
internal class ServiceProviderActorProducer<[DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] TActor> : ServiceProviderActorProducer where TActor : ActorBase
{

public ServiceProviderActorProducer(IServiceProvider provider, object[] args)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//-----------------------------------------------------------------------

using System;
using System.Diagnostics.CodeAnalysis;
using Akka.Actor;
using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -40,19 +41,19 @@ public object GetService(Type type)
return ServiceProvider.GetService(type);
}

public Props Props(Type type, params object[] args)
public Props Props([DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] Type type, params object[] args)
{
if(typeof(ActorBase).IsAssignableFrom(type))
return Akka.Actor.Props.CreateBy(new ServiceProviderActorProducer(ServiceProvider, type, args));
throw new ArgumentException(nameof(type), $"[{type}] does not implement Akka.Actor.ActorBase.");
}

public Props Props(Type type)
public Props Props([DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] Type type)
{
return Props(type, Array.Empty<object>());
}

public Props Props<T>(params object[] args) where T : ActorBase
public Props Props<[DynamicallyAccessedMembers(Akka.Actor.Props.ActorTypeMembers)] T>(params object[] args) where T : ActorBase
{
return Akka.Actor.Props.CreateBy(new ServiceProviderActorProducer<T>(ServiceProvider, args));
}
Expand Down
Loading
Loading