Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8a370d7
First pass at getting rid of expressions
mhbuck Mar 24, 2025
24d14a5
Adding method where actor created without args.
mhbuck Mar 25, 2025
d241d73
Minor rename and removal of non referenced object.
mhbuck Mar 26, 2025
d038796
added AOT application
Aaronontheweb Jan 10, 2025
3caeb97
adding AOT compat testing pipeline
Aaronontheweb Jan 10, 2025
d02485e
added AOT compilation steps to PR pipeline
Aaronontheweb Jan 10, 2025
cd34d98
fixed missing template parameter
Aaronontheweb Jan 10, 2025
9226aa4
fix path
Aaronontheweb Jan 10, 2025
2bbcfb3
fix script arguments
Aaronontheweb Jan 10, 2025
c37bb76
more file paths
Aaronontheweb Jan 10, 2025
b6031d0
fix x-plat AOT invocation
Aaronontheweb Jan 10, 2025
a100932
fix
Aaronontheweb Jan 10, 2025
7740d17
fixed sln issues
Aaronontheweb Oct 10, 2025
2da8e09
use global.json for AOT canary
Aaronontheweb Oct 10, 2025
fe43b81
initial experiments with typehints compiler directives
Aaronontheweb Oct 10, 2025
10e3cba
removed deprecated Akka.DI.Core backwards compat method to resolve tr…
Aaronontheweb Oct 10, 2025
a9f343f
added dynamic member access attributes to 'Extensions'
Aaronontheweb Oct 10, 2025
52cbe98
store mailbox requirements into TypeHints
Aaronontheweb Oct 10, 2025
b1cb87b
added type hints for serializers; disabled Newtonsoft.Json
Aaronontheweb Oct 10, 2025
538533b
fix ActorSystemSetup FQN loading for IActorRefProvider
Aaronontheweb Oct 10, 2025
ce33bf1
AOT: solve StandardOutLogger loading
Aaronontheweb Oct 10, 2025
011affa
fix DefaultLogFormatter AOT loading
Aaronontheweb Oct 10, 2025
2425228
AOT: fix issue with SupervisorStrategyConfigurator
Aaronontheweb Oct 10, 2025
d7c5922
fix issues with LoggingBus
Aaronontheweb Oct 10, 2025
185b473
fixed AOT warning with loading mailbox configurator
Aaronontheweb Oct 10, 2025
f8f627a
AOT: fixed issues with custom dispatcher loading
Aaronontheweb Oct 10, 2025
fbe54e9
Merge remote-tracking branch 'mhbuck/propsusingfunc' into experiment/…
Aaronontheweb Oct 10, 2025
20260d9
AOT: solve router config loading with factory functions
Aaronontheweb Oct 10, 2025
25ebf58
AOT: solve serializer identifier loading with string matching
Aaronontheweb Oct 10, 2025
8be269e
added TODO regarding serializerIds
Aaronontheweb Oct 10, 2025
bea15f9
AOT: fix dispatcher executor configuration
Aaronontheweb Oct 10, 2025
cfee5b6
AOT: add DynamicallyAccessedMembers annotations to Mailboxes
Aaronontheweb Oct 10, 2025
20e36de
fix NREs with `Settings.cs`
Aaronontheweb Oct 10, 2025
fa3c84b
fixed AOT warnings on Props.Interfaces access
Aaronontheweb Oct 10, 2025
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
17 changes: 17 additions & 0 deletions Akka.sln
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.Xunit2.Tests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.Xunit.Tests", "src\contrib\testkits\Akka.TestKit.Xunit.Tests\Akka.TestKit.Xunit.Tests.csproj", "{F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AOT", "AOT", "{2E11BBFC-EA1F-4C20-8076-0D8542397264}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.AOT.App", "src\aot\Akka.AOT.App\Akka.AOT.App.csproj", "{6732F90D-59C9-45BA-801A-24BE2A12EB62}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1318,6 +1322,18 @@ Global
{F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Release|x64.Build.0 = Release|Any CPU
{F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Release|x86.ActiveCfg = Release|Any CPU
{F80F41E6-E5C7-4C92-B1CF-42539ECFBE68}.Release|x86.Build.0 = Release|Any CPU
{6732F90D-59C9-45BA-801A-24BE2A12EB62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6732F90D-59C9-45BA-801A-24BE2A12EB62}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6732F90D-59C9-45BA-801A-24BE2A12EB62}.Debug|x64.ActiveCfg = Debug|Any CPU
{6732F90D-59C9-45BA-801A-24BE2A12EB62}.Debug|x64.Build.0 = Debug|Any CPU
{6732F90D-59C9-45BA-801A-24BE2A12EB62}.Debug|x86.ActiveCfg = Debug|Any CPU
{6732F90D-59C9-45BA-801A-24BE2A12EB62}.Debug|x86.Build.0 = Debug|Any CPU
{6732F90D-59C9-45BA-801A-24BE2A12EB62}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6732F90D-59C9-45BA-801A-24BE2A12EB62}.Release|Any CPU.Build.0 = Release|Any CPU
{6732F90D-59C9-45BA-801A-24BE2A12EB62}.Release|x64.ActiveCfg = Release|Any CPU
{6732F90D-59C9-45BA-801A-24BE2A12EB62}.Release|x64.Build.0 = Release|Any CPU
{6732F90D-59C9-45BA-801A-24BE2A12EB62}.Release|x86.ActiveCfg = Release|Any CPU
{6732F90D-59C9-45BA-801A-24BE2A12EB62}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1441,6 +1457,7 @@ Global
{337A85B5-4A7C-4883-8634-46E7E52A765F} = {7735F35A-E7B7-44DE-B6FB-C770B53EB69C}
{95017C99-E960-44E5-83AD-BF21461DF06F} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E}
{F80F41E6-E5C7-4C92-B1CF-42539ECFBE68} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E}
{6732F90D-59C9-45BA-801A-24BE2A12EB62} = {2E11BBFC-EA1F-4C20-8076-0D8542397264}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {03AD8E21-7507-4E68-A4E9-F4A7E7273164}
Expand Down
33 changes: 33 additions & 0 deletions build-system/azure.aot.template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
parameters:
name: ''
displayName: ''
vmImage: ''
run_if: true
timeoutInMinutes: 10

jobs:
- job: ${{ parameters.name }}
condition: eq( ${{ parameters.run_if }}, true )
displayName: ${{ parameters.displayName }}
timeoutInMinutes: ${{ parameters.timeoutInMinutes }}
pool:
vmImage: ${{ parameters.vmImage }}
steps:
- task: UseDotNet@2
displayName: 'Use .NET'
inputs:
packageType: 'sdk'
useGlobalJson: true

# Option 1: Use inline script to handle paths properly
- task: PowerShell@2
inputs:
targetType: 'inline'
script: |
./scripts/test-aot-compatibility.ps1 net8.0
pwsh: true

- script: 'echo 1>&2'
failOnStderr: true
displayName: 'If above is partially succeeded, then fail'
condition: eq(variables['Agent.JobStatus'], 'SucceededWithIssues')
12 changes: 12 additions & 0 deletions build-system/pr-validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,15 @@ jobs:
command: "dotnet pack -c Release -o bin/nuget"
outputDirectory: "bin/nuget"
artifactName: "nuget_pack-$(Build.BuildId)"

- template: azure.aot.template.yaml
parameters:
name: "aot_linux"
displayName: "AOT Compilation (Linux)"
vmImage: "ubuntu-latest"

- template: azure.aot.template.yaml
parameters:
name: "aot_windows"
displayName: "AOT Compilation (Windows)"
vmImage: "windows-latest"
64 changes: 64 additions & 0 deletions scripts/test-aot-compatibility.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
param([string]$targetNetFramework = "net8.0")

$rootDirectory = Split-Path $PSScriptRoot -Parent
$publishOutput = dotnet publish $rootDirectory/src/aot/Akka.AOT.App/Akka.AOT.App.csproj -nodeReuse:false /p:UseSharedCompilation=false /p:ExposeExperimentalFeatures=true

$actualWarningCount = 0

foreach ($line in $($publishOutput -split "`r`n"))
{
if ($line -like "*analysis warning IL*")
{
Write-Host $line

$actualWarningCount += 1
}
}

# Determine the OS-specific folder
$osPlatform = [System.Runtime.InteropServices.RuntimeInformation]::OSDescription
if ($osPlatform -match "Windows") {
$osFolder = "win-x64"
} else {
$osFolder = "linux-x64"
# Default to linux
}

$testAppPath = Join-Path -Path $rootDirectory/src/aot/Akka.AOT.App/bin/Release/$targetNetFramework -ChildPath $osFolder/publish

if (-Not (Test-Path $testAppPath)) {
Write-Error "Test App path does not exist: $testAppPath"
Exit 1
}

Write-Host $testAppPath
pushd $testAppPath

Write-Host "Executing test App..."
if ($osPlatform -match "Windows") {
./Akka.AOT.App.exe
} else {
# Default to linux
./Akka.AOT.App
}

Write-Host "Finished executing test App"

if ($LastExitCode -ne 0)
{
Write-Host "There was an error while executing AotCompatibility Test App. LastExitCode is:", $LastExitCode
}

popd

Write-Host "Actual warning count is:", $actualWarningCount
$expectedWarningCount = 0

$testPassed = 0
if ($actualWarningCount -ne $expectedWarningCount)
{
$testPassed = 1
Write-Host "Actual warning count:", $actualWarningCount, "is not as expected. Expected warning count is:", $expectedWarningCount
}

Exit $testPassed
18 changes: 18 additions & 0 deletions src/aot/Akka.AOT.App/Actors/AotReceiveActor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// -----------------------------------------------------------------------
// <copyright file="AotReceiveActor.cs" company="Akka.NET Project">
// Copyright (C) 2009-2025 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using Akka.Actor;

namespace Akka.AOT.App.Actors;

public class AotReceiveActor : ReceiveActor
{
public AotReceiveActor()
{
ReceiveAny(o => Sender.Tell(o));
}
}
18 changes: 18 additions & 0 deletions src/aot/Akka.AOT.App/Actors/AotUntypedActor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// -----------------------------------------------------------------------
// <copyright file="AotUntypedActor.cs" company="Akka.NET Project">
// Copyright (C) 2009-2025 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using Akka.Actor;

namespace Akka.AOT.App.Actors;

public class AotUntypedActor : UntypedActor
{
protected override void OnReceive(object message)
{
Sender.Tell(message);
}
}
22 changes: 22 additions & 0 deletions src/aot/Akka.AOT.App/Akka.AOT.App.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(NetTestVersion)</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<PropertyGroup Label="AotSettings">
<PublishAot>true</PublishAot>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<SelfContained>true</SelfContained>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\core\Akka\Akka.csproj"/>
</ItemGroup>

</Project>
27 changes: 27 additions & 0 deletions src/aot/Akka.AOT.App/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Akka.Actor;
using Akka.AOT.App.Actors;

namespace Akka.AOT.App;

class Program
{
static async Task Main(string[] args)
{
var system = ActorSystem.Create("MySystem");

// create AotUntypedActor
var untypedActorProps = Props.Create(() => new AotUntypedActor());
var untypedActor = system.ActorOf(untypedActorProps, "untyped-actor");

// create AotReceiveActor
var receiveActorProps = Props.Create(() => new AotReceiveActor());
var receiveActor = system.ActorOf(receiveActorProps, "receive-actor");

// send a message to both actors
Console.WriteLine(await untypedActor.Ask("Hello, untyped actor!"));
Console.WriteLine(await receiveActor.Ask("Hello, receive actor!"));

// terminate the actor system
await system.Terminate();
}
}
13 changes: 6 additions & 7 deletions src/core/Akka.TestKit/TestKitBase_ActorOf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
//-----------------------------------------------------------------------

using System;
using System.Linq.Expressions;
using Akka.Actor;
using Akka.Actor.Dsl;

Expand Down Expand Up @@ -71,7 +70,7 @@ public IActorRef ActorOf(Props props, string name)
/// <typeparam name="TActor">The type of the actor.</typeparam>
/// <param name="factory">An expression that calls the constructor of <typeparamref name="TActor"/></param>
/// <returns>TBD</returns>
public IActorRef ActorOf<TActor>(Expression<Func<TActor>> factory) where TActor : ActorBase
public IActorRef ActorOf<TActor>(Func<TActor> factory) where TActor : ActorBase
{
return Sys.ActorOf(Props.Create(factory), null);
}
Expand All @@ -87,7 +86,7 @@ public IActorRef ActorOf<TActor>(Expression<Func<TActor>> factory) where TActor
/// <param name="factory">An expression that calls the constructor of <typeparamref name="TActor"/></param>
/// <param name="name">The name of the actor.</param>
/// <returns>TBD</returns>
public IActorRef ActorOf<TActor>(Expression<Func<TActor>> factory, string name) where TActor : ActorBase
public IActorRef ActorOf<TActor>(Func<TActor> factory, string name) where TActor : ActorBase
{
return Sys.ActorOf(Props.Create(factory), name);
}
Expand Down Expand Up @@ -201,7 +200,7 @@ public TestActorRef<TActor> ActorOfAsTestActorRef<TActor>(Props props, string na
/// <param name="supervisor">The supervisor</param>
/// <param name="name">Optional: The name.</param>
/// <returns>TBD</returns>
public TestActorRef<TActor> ActorOfAsTestActorRef<TActor>(Expression<Func<TActor>> factory, IActorRef supervisor, string name = null) where TActor : ActorBase
public TestActorRef<TActor> ActorOfAsTestActorRef<TActor>(Func<TActor> factory, IActorRef supervisor, string name = null) where TActor : ActorBase
{
return new TestActorRef<TActor>(Sys, Props.Create(factory), supervisor, name);
}
Expand All @@ -218,7 +217,7 @@ public TestActorRef<TActor> ActorOfAsTestActorRef<TActor>(Expression<Func<TActor
/// <param name="factory">An expression that calls the constructor of <typeparamref name="TActor"/></param>
/// <param name="name">Optional: The name.</param>
/// <returns>TBD</returns>
public TestActorRef<TActor> ActorOfAsTestActorRef<TActor>(Expression<Func<TActor>> factory, string name = null) where TActor : ActorBase
public TestActorRef<TActor> ActorOfAsTestActorRef<TActor>(Func<TActor> factory, string name = null) where TActor : ActorBase
{
return new TestActorRef<TActor>(Sys, Props.Create(factory), NoSupervisor, name);
}
Expand Down Expand Up @@ -333,7 +332,7 @@ public TestFSMRef<TFsmActor, TState, TData> ActorOfAsTestFSMRef<TFsmActor, TStat
/// <param name="name">Optional: The name.</param>
/// <param name="withLogging">Optional: If set to <c>true</c> logs state changes of the FSM as Debug messages. Default is <c>false</c>.</param>
/// <returns>TBD</returns>
public TestFSMRef<TFsmActor, TState, TData> ActorOfAsTestFSMRef<TFsmActor, TState, TData>(Expression<Func<TFsmActor>> factory, IActorRef supervisor, string name = null, bool withLogging = false)
public TestFSMRef<TFsmActor, TState, TData> ActorOfAsTestFSMRef<TFsmActor, TState, TData>(Func<TFsmActor> factory, IActorRef supervisor, string name = null, bool withLogging = false)
where TFsmActor : FSM<TState, TData>
{
return new TestFSMRef<TFsmActor, TState, TData>(Sys, Props.Create(factory), supervisor, name, withLogging);
Expand All @@ -351,7 +350,7 @@ public TestFSMRef<TFsmActor, TState, TData> ActorOfAsTestFSMRef<TFsmActor, TStat
/// <param name="name">Optional: The name.</param>
/// <param name="withLogging">Optional: If set to <c>true</c> logs state changes of the FSM as Debug messages. Default is <c>false</c>.</param>
/// <returns>TBD</returns>
public TestFSMRef<TFsmActor, TState, TData> ActorOfAsTestFSMRef<TFsmActor, TState, TData>(Expression<Func<TFsmActor>> factory, string name = null, bool withLogging = false)
public TestFSMRef<TFsmActor, TState, TData> ActorOfAsTestFSMRef<TFsmActor, TState, TData>(Func<TFsmActor> factory, string name = null, bool withLogging = false)
where TFsmActor : FSM<TState, TData>
{
return new TestFSMRef<TFsmActor, TState, TData>(Sys, Props.Create(factory), NoSupervisor, name, withLogging);
Expand Down
2 changes: 2 additions & 0 deletions src/core/Akka/Actor/ActorCell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ public virtual ActorTaskScheduler TaskScheduler
/// </summary>
/// <param name="sendSupervise">TBD</param>
/// <param name="mailboxType">TBD</param>
[UnconditionalSuppressMessage("AssemblyFiles", "IL2072",
Justification = "All MailboxType implementations in Akka.NET have IProducesMessageQueue<T> interfaces preserved, and all actor types have IRequiresMessageQueue<T> interfaces preserved")]
public void Init(bool sendSupervise, MailboxType mailboxType)
{
/*
Expand Down
17 changes: 17 additions & 0 deletions src/core/Akka/Actor/Deployer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,22 @@ private RouterConfig CreateRouterConfig(string routerTypeAlias, Config deploymen
if (deployment.IsNullOrEmpty())
throw ConfigurationException.NullOrEmptyConfig<RouterConfig>();

#if AOT_ENABLED
// In AOT mode, use factory functions from TypeHints
if (!Internal.TypeHints.DefaultRouterFactories.TryGetValue(routerTypeAlias, out var factory))
{
var message = $"Router type '{routerTypeAlias}' is not supported in AOT mode. " +
"Only built-in routers (round-robin, random, smallest-mailbox, broadcast, scatter-gather, " +
"consistent-hashing, tail-chopping) are available.";

if (routerTypeAlias is "cluster-metrics-adaptive-group" or "cluster-metrics-adaptive-pool")
message += " Cluster metrics routers require Akka.Cluster.Metrics which is not AOT-compatible yet.";

throw new ConfigurationException(message);
}

return factory(deployment);
#else
var path = string.Format("akka.actor.router.type-mapping.{0}", routerTypeAlias);
var routerTypeName = _settings.Config.GetString(path, null);

Expand Down Expand Up @@ -170,6 +186,7 @@ private RouterConfig CreateRouterConfig(string routerTypeAlias, Config deploymen
var routerConfig = (RouterConfig)Activator.CreateInstance(routerType, deployment);

return routerConfig;
#endif
}
}
}
5 changes: 3 additions & 2 deletions src/core/Akka/Actor/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//-----------------------------------------------------------------------

using System;
using System.Diagnostics.CodeAnalysis;

namespace Akka.Actor
{
Expand Down Expand Up @@ -107,7 +108,7 @@ public static T WithExtension<T>(this ActorSystem system) where T : class, IExte
/// <param name="system">The actor system from which to retrieve the extension or to register with if it does not exist.</param>
/// <param name="extensionId">The type of the extension to register if it does not exist in the given actor system.</param>
/// <returns>The extension retrieved from the given actor system.</returns>
public static T WithExtension<T>(this ActorSystem system, Type extensionId) where T : class, IExtension
public static T WithExtension<T>(this ActorSystem system, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type extensionId) where T : class, IExtension
{
if (system.HasExtension<T>())
return system.GetExtension<T>();
Expand All @@ -126,7 +127,7 @@ public static T WithExtension<T>(this ActorSystem system, Type extensionId) wher
/// <typeparam name="TI">The type associated with the extension to retrieve if it does not exist within the system.</typeparam>
/// <param name="system">The actor system from which to retrieve the extension or to register with if it does not exist.</param>
/// <returns>The extension retrieved from the given actor system.</returns>
public static T WithExtension<T,TI>(this ActorSystem system) where T : class, IExtension
public static T WithExtension<T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TI>(this ActorSystem system) where T : class, IExtension
where TI: IExtensionId
{
if (system.HasExtension<T>())
Expand Down
Loading