Skip to content

Commit

Permalink
Split server-only (grains) APIs to our Server package
Browse files Browse the repository at this point in the history
Related codegen is also moved there, with a detection for the project having the server package installed.

Due to an issue with Orleans codec generation (see dotnet/orleans#9092), end to end tests are failing since state is not being properly serialized for actor messages.

We can only ship this rework once that's fixed. Otherwise, we'll need to move serialization codegen back to the actor/domain project (which unfortunately brings a larger dependency on Orleans, which is undesirable).
  • Loading branch information
kzu committed Jul 30, 2024
1 parent 9ec33eb commit cf8f16a
Show file tree
Hide file tree
Showing 23 changed files with 173 additions and 49 deletions.
4 changes: 2 additions & 2 deletions CloudActors.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudActors.CodeFix", "src\
EndProject
Project("{13B669BE-BB05-4DDF-9536-439F39A36129}") = "CloudActors.Package", "src\CloudActors.Package\CloudActors.Package.msbuildproj", "{22150004-BDDD-4C7E-9E42-0B5A2EC48268}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudActors.Orleans", "src\CloudActors.Orleans\CloudActors.Orleans.csproj", "{F4256D7D-262F-401A-BFD1-C27F863FE5E1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudActors.Server", "src\CloudActors.Server\CloudActors.Server.csproj", "{F4256D7D-262F-401A-BFD1-C27F863FE5E1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudActors.Streamstone", "src\CloudActors.Streamstone\CloudActors.Streamstone.csproj", "{67E7E202-1AC7-477C-AE90-E7D7C04FF6DC}"
EndProject
Expand All @@ -26,7 +26,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestDomain", "src\TestDomain\TestDomain.csproj", "{1D75C6E9-C4E9-44D8-B598-3A2D45C25EE6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudActors.Orleans.CodeAnalysis", "src\CloudActors.Orleans.CodeAnalysis\CloudActors.Orleans.CodeAnalysis.csproj", "{68823E7F-269E-4947-8E2B-9905F163C96B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudActors.Server.CodeAnalysis", "src\CloudActors.Server.CodeAnalysis\CloudActors.Server.CodeAnalysis.csproj", "{68823E7F-269E-4947-8E2B-9905F163C96B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
10 changes: 8 additions & 2 deletions src/CloudActors.CodeAnalysis/ActorMessageGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,15 @@ namespace {{ns}}
}
""";

var orleans = OrleansGenerator.GenerateCode(options, output, message.Name, ctx.CancellationToken);

ctx.AddSource($"{message.ToFileName()}.Serializable.cs", output);
ctx.AddSource($"{message.ToFileName()}.Serializable.orleans.cs", orleans);

// This supports the scenario where the actor and messages exist in the server project itself,
// which is likely not very common but nevertheless it should be supported.
if (options.IsCloudActorsServer)
{
var orleans = OrleansGenerator.GenerateCode(options, output, message.Name, ctx.CancellationToken);
ctx.AddSource($"{message.ToFileName()}.Serializable.orleans.cs", orleans);
}
}
}
8 changes: 1 addition & 7 deletions src/CloudActors.CodeAnalysis/ActorStateGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,12 @@ public class ActorStateGenerator : IIncrementalGenerator

public void Initialize(IncrementalGeneratorInitializationContext context)
{
var options = context.GetOrleansOptions();

var actors = context.CompilationProvider
.SelectMany((x, _) => x.Assembly.GetAllTypes().OfType<INamedTypeSymbol>())
.Where(x => x.GetAttributes().Any(x => x.IsActor()));

context.RegisterImplementationSourceOutput(actors.Combine(options), (ctx, source) =>
context.RegisterImplementationSourceOutput(actors, (ctx, actor) =>
{
var (actor, options) = source;
var ns = actor.ContainingNamespace.ToDisplayString();

var props = actor.GetMembers()
Expand Down Expand Up @@ -59,10 +56,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
EventSourced: es);

var output = template.Render(model, member => member.Name);
var orleans = OrleansGenerator.GenerateCode(options, output, $"{actor.Name}.State", ctx.CancellationToken);

ctx.AddSource($"{actor.ToFileName()}.State.cs", output);
ctx.AddSource($"{actor.ToFileName()}.State.orleans.cs", orleans);
}
});
}
Expand Down
19 changes: 15 additions & 4 deletions src/CloudActors.CodeAnalysis/AnalysisExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,26 @@ public static class AnalysisExtensions
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.ExpandNullable);

public static IEnumerable<ITypeSymbol> GetAllTypes(this IAssemblySymbol assembly)
public static IEnumerable<INamedTypeSymbol> GetAllTypes(this Compilation compilation, bool includeReferenced = true)
=> compilation.Assembly
.GetAllTypes()
.OfType<INamedTypeSymbol>()
.Concat(compilation.GetUsedAssemblyReferences()
.SelectMany(r =>
{
if (compilation.GetAssemblyOrModuleSymbol(r) is IAssemblySymbol asm)
return asm.GetAllTypes().OfType<INamedTypeSymbol>();

return [];
}));

public static IEnumerable<INamedTypeSymbol> GetAllTypes(this IAssemblySymbol assembly)
=> GetAllTypes(assembly.GlobalNamespace);

static IEnumerable<ITypeSymbol> GetAllTypes(INamespaceSymbol namespaceSymbol)
static IEnumerable<INamedTypeSymbol> GetAllTypes(INamespaceSymbol namespaceSymbol)
{
foreach (var typeSymbol in namespaceSymbol.GetTypeMembers())
{
yield return typeSymbol;
}

foreach (var childNamespace in namespaceSymbol.GetNamespaceMembers())
{
Expand Down
4 changes: 4 additions & 0 deletions src/CloudActors.CodeAnalysis/CloudActors.CodeAnalysis.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
<PackageScribanIncludeSource>true</PackageScribanIncludeSource>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\CloudActors.Server.CodeAnalysis\OrleansGenerator.cs" Link="OrleansGenerator.cs" />
</ItemGroup>

<ItemGroup>
<!-- See: https://github.com/NuGet/Home/issues/6279. For now, ExcludeAssets doesn't work for analyzers/generators, so we ARE running
the Orleans source generator in this analyzer project itself, even if we don't want/need to. -->
Expand Down
25 changes: 25 additions & 0 deletions src/CloudActors.CodeAnalysis/CloudActorsGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.CodeAnalysis;

namespace Devlooped.CloudActors;

[Generator(LanguageNames.CSharp)]
public class CloudActorsGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterImplementationSourceOutput(context.CompilationProvider, (ctx, compilation) =>
{
ctx.AddSource($"CloudActors.cs",
$$"""
// <auto-generated/>
[assembly: Devlooped.CloudActors]
namespace Devlooped
{
[System.AttributeUsage(System.AttributeTargets.Assembly)]
class CloudActorsAttribute : System.Attribute { }
}
""");
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var options = context.GetOrleansOptions();

var actors = context.CompilationProvider
.SelectMany((x, _) => x.Assembly
.GetAllTypes()
.OfType<INamedTypeSymbol>()
.Where(t => t.IsActor())
.Concat(x.GetUsedAssemblyReferences()
.SelectMany(r =>
{
if (x.GetAssemblyOrModuleSymbol(r) is IAssemblySymbol asm)
return asm.GetAllTypes().OfType<INamedTypeSymbol>().Where(t => t.IsActor());

return [];
})));
.SelectMany((x, _) => x.GetAllTypes())
.Where(t => t.IsActor());

context.RegisterImplementationSourceOutput(actors.Combine(options), (ctx, source) =>
{
Expand Down
62 changes: 62 additions & 0 deletions src/CloudActors.Server.CodeAnalysis/ActorsAssemblyGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using static Devlooped.CloudActors.AnalysisExtensions;

namespace Devlooped.CloudActors;

/// <summary>
/// Analyzes referenced assemblies from current compilation and emits an attribute
/// for each one that instructs the Orleans code generator to inspect the assembly
/// and generate code for it: GenerateCodeForDeclaringAssembly.
/// This allows us to avoid polluting the domain/actor assemblies with Orleans-specifics
/// other than the [GenerateSerializer] attribute and state classes, but which have no
/// implementations. Grains and everything else are generated in the project referencing
/// the Orleans.Server package only.
/// </summary>
[Generator(LanguageNames.CSharp)]
public class ActorsAssemblyGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var options = context.GetOrleansOptions();
var assemblies = context.CompilationProvider
.SelectMany((x, _) => x.GetUsedAssemblyReferences().Select(r => new { Compilation = x, Reference = r }))
.Select((x, _) => x.Compilation.GetAssemblyOrModuleSymbol(x.Reference))
.Where(x => x is IAssemblySymbol asm && asm.GetAttributes().Any(
a => "Devlooped.CloudActorsAttribute".Equals(a.AttributeClass?.ToDisplayString(FullName))))
.Select((x, _) => (IAssemblySymbol)x!)
.Collect();

context.RegisterImplementationSourceOutput(assemblies.Combine(options), GenerateCode);
}

static void GenerateCode(SourceProductionContext ctx, (ImmutableArray<IAssemblySymbol>, OrleansGeneratorOptions) source)
{
var (assemblies, options) = source;

// Don't duplicate any of the already generated code for the current assembly
options = options with { Compilation = options.Compilation.RemoveAllSyntaxTrees() };

var output = new StringBuilder().AppendLine(
"""
// <auto-generated/>
using Orleans;
""");

foreach (var type in assemblies.Select(x => x.GetAllTypes()
.FirstOrDefault(x => x.ContainingType == null && options.Compilation.IsSymbolAccessibleWithin(x, options.Compilation.Assembly))))
{
if (type == null)
continue;

// [assembly: GenerateCodeForDeclaringAssembly(typeof(TestDomain.Account))]
output.AppendLine($"[assembly: GenerateCodeForDeclaringAssembly(typeof({type.ToDisplayString(FullName)}))]");
}

var orleans = OrleansGenerator.GenerateCode(options, output.ToString(), "References", ctx.CancellationToken);

ctx.AddSource($"CloudActors.orleans.cs", orleans);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyName>Devlooped.CloudActors.Orleans.CodeAnalysis</AssemblyName>
<AssemblyName>Devlooped.CloudActors.Server.CodeAnalysis</AssemblyName>
<TargetFramework>netstandard2.0</TargetFramework>

<IsRoslynComponent>true</IsRoslynComponent>
Expand All @@ -15,7 +15,6 @@

<ItemGroup>
<Compile Include="..\CloudActors.CodeAnalysis\AnalysisExtensions.cs" Link="AnalysisExtensions.cs" />
<Compile Include="..\CloudActors.CodeAnalysis\OrleansGenerator.cs" Link="OrleansGenerator.cs" />
</ItemGroup>

<ItemGroup>
Expand All @@ -40,6 +39,7 @@

<ItemGroup>
<EmbeddedResource Include="@(None -&gt; WithMetadataValue('Extension', '.sbntxt'))" Kind="text" />
<None Update="Devlooped.CloudActors.Server.targets" PackFolder="build" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<PropertyGroup>
<IsCloudActorsServer>true</IsCloudActorsServer>
</PropertyGroup>

<ItemGroup>
<CompilerVisibleProperty Include="IsCloudActorsServer" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@

namespace Devlooped.CloudActors;

public record OrleansGeneratorOptions(Compilation Compilation, CSharpParseOptions? ParseOptions, AnalyzerConfigOptions AnalyzerConfig);
public record OrleansGeneratorOptions(Compilation Compilation, CSharpParseOptions? ParseOptions, AnalyzerConfigOptions AnalyzerConfig)
{
public bool IsCloudActorsServer => AnalyzerConfig.TryGetValue("build_property.IsCloudActorsServer", out var value) &&
bool.TryParse(value, out var isServer) && isServer;
}

public static class OrleansGeneratorExtensions
{
Expand Down Expand Up @@ -72,7 +76,6 @@ public static string GenerateCode(OrleansGeneratorOptions orleans, string additi
var generator = new CodeGenerator(compilation, options);
var syntax = generator.GenerateCode(cancellation);

// Remove all attributes like: [assembly: global::Orleans.ApplicationPartAttribute]
syntax = syntax.RemoveNodes(
syntax.DescendantNodes()
.OfType<AttributeSyntax>()
Expand All @@ -97,22 +100,22 @@ static CodeGeneratorOptions CreateGeneratorOptions(OrleansGeneratorOptions orlea
var options = new CodeGeneratorOptions();
if (orleans.AnalyzerConfig.TryGetValue("build_property.orleans_immutableattributes", out var immutableAttributes) && immutableAttributes is { Length: > 0 })
{
options.ImmutableAttributes.AddRange(immutableAttributes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList());
options.ImmutableAttributes.AddRange([.. immutableAttributes.Split([';'], StringSplitOptions.RemoveEmptyEntries)]);
}

if (orleans.AnalyzerConfig.TryGetValue("build_property.orleans_aliasattributes", out var aliasAttributes) && aliasAttributes is { Length: > 0 })
{
options.AliasAttributes.AddRange(aliasAttributes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList());
options.AliasAttributes.AddRange([.. aliasAttributes.Split([';'], StringSplitOptions.RemoveEmptyEntries)]);
}

if (orleans.AnalyzerConfig.TryGetValue("build_property.orleans_idattributes", out var idAttributes) && idAttributes is { Length: > 0 })
{
options.IdAttributes.AddRange(idAttributes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList());
options.IdAttributes.AddRange([.. idAttributes.Split([';'], StringSplitOptions.RemoveEmptyEntries)]);
}

if (orleans.AnalyzerConfig.TryGetValue("build_property.orleans_generateserializerattributes", out var generateSerializerAttributes) && generateSerializerAttributes is { Length: > 0 })
{
options.GenerateSerializerAttributes.AddRange(generateSerializerAttributes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList());
options.GenerateSerializerAttributes.AddRange([.. generateSerializerAttributes.Split([';'], StringSplitOptions.RemoveEmptyEntries)]);
}

if (orleans.AnalyzerConfig.TryGetValue("build_property.orleans_generatefieldids", out var generateFieldIds) && generateFieldIds is { Length: > 0 })
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
"CodeAnalysis": {
"commandName": "DebugRoslynComponent",
"targetProject": "..\\Tests\\Tests.csproj"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Orleans;
using Orleans.Core;
using Orleans.Runtime;

Expand Down Expand Up @@ -97,10 +95,15 @@ TState IStorage<TState>.State
}

public Task ClearStateAsync() => persistence.ClearStateAsync()
.ContinueWith(t => Actor.SetState(persistence.State));
.ContinueWith(t => Actor.SetState(persistence.State), TaskContinuationOptions.OnlyOnRanToCompletion);

public Task ReadStateAsync() => persistence.ReadStateAsync()
.ContinueWith(t => Actor.SetState(persistence.State));
.ContinueWith(t =>
{
// Don't overwrite state that may already have initial values from actor constructor.
if (persistence.RecordExists)
Actor.SetState(persistence.State);
}, TaskContinuationOptions.OnlyOnRanToCompletion);

public Task WriteStateAsync()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyName>Devlooped.CloudActors.Server</AssemblyName>
<TargetFramework>net8.0</TargetFramework>
<PackageId>$(AssemblyName)</PackageId>
<Description>Cloud Native Actors Server/Hosting</Description>
<Description>Cloud Native Actors Orleans Server/Hosting</Description>
<PackageTags>dotnet orleans actor server</PackageTags>
</PropertyGroup>

Expand All @@ -15,7 +15,7 @@

<ItemGroup>
<ProjectReference Include="..\CloudActors\CloudActors.csproj" />
<ProjectReference Include="..\CloudActors.Orleans.CodeAnalysis\CloudActors.Orleans.CodeAnalysis.csproj" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\CloudActors.Server.CodeAnalysis\CloudActors.Server.CodeAnalysis.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>

</Project>
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions src/CloudActors.Streamstone/CloudActors.Streamstone.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CloudActors.Orleans\CloudActors.Orleans.csproj" />
<ProjectReference Include="..\CloudActors.Package\CloudActors.Package.msbuildproj" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\CloudActors\CloudActors.csproj" Pack="false" />
<ProjectReference Include="..\CloudActors.Server\CloudActors.Server.csproj" />
<ProjectReference Include="..\CloudActors.Package\CloudActors.Package.msbuildproj" ReferenceOutputAssembly="false" />
</ItemGroup>

</Project>
3 changes: 2 additions & 1 deletion src/CloudActors/CloudActors.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyName>Devlooped.CloudActors</AssemblyName>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Orleans.Serialization.Abstractions" Version="8.2.0" />
<PackageReference Include="NuGetizer" Version="1.2.2" />
<PackageReference Include="PolySharp" PrivateAssets="All" Version="1.14.1" />
</ItemGroup>
Expand Down
Loading

0 comments on commit cf8f16a

Please sign in to comment.