From 645d2704b738f61d6ba91ed23917b202dd6834cd Mon Sep 17 00:00:00 2001 From: Konstantin Semenenko Date: Mon, 18 Nov 2024 14:12:31 +0100 Subject: [PATCH] implementation --- .../Cluster/Grains/GrainA.cs | 9 +- .../Cluster/Grains/GrainB.cs | 3 +- .../Cluster/Grains/GrainC.cs | 4 +- .../Cluster/Grains/GrainD.cs | 3 +- .../Cluster/Grains/GrainE.cs | 3 +- .../Cluster/Grains/Interfaces/IGrainA.cs | 4 +- .../Cluster/TestSiloConfigurations.cs | 23 ++- ManagedCode.Orleans.Graph.Tests/GraphTests.cs | 12 +- .../ManagedCode.Orleans.Graph.Tests.csproj | 26 ++-- .../OrleansCleintGraphExtensions.cs | 34 ++++ .../Extensions/OrleansGraphExtensions.cs | 27 ---- .../Extensions/RequestContextHelper.cs | 61 ++++---- .../Filters/GraphIncomingGrainCallFilter.cs | 66 +------- .../Filters/GraphOutgoingGrainCallFilter.cs | 64 +------- .../Interfaces/Constants.cs | 4 +- .../ManagedCode.Orleans.Graph.csproj | 10 +- .../DirectedGraph.cs} | 79 +++------- .../GrainGraphConfigurationAttribute.cs | 8 + .../Managers/GrainGraphManager.cs | 53 +++++++ .../Managers/GrainGraphSourceGenerator.cs | 114 ++++++++++++++ ManagedCode.Orleans.Graph/Models/Call.cs | 5 +- .../Models/CallHistory.cs | 23 +-- ManagedCode.Orleans.Graph/Models/InCall.cs | 2 +- ManagedCode.Orleans.Graph/Models/OutCall.cs | 2 +- ManagedCode.Orleans.Graph/gene.cs | 147 ------------------ 25 files changed, 353 insertions(+), 433 deletions(-) create mode 100644 ManagedCode.Orleans.Graph/Extensions/OrleansCleintGraphExtensions.cs rename ManagedCode.Orleans.Graph/{GrainGraph.cs => Managers/DirectedGraph.cs} (61%) create mode 100644 ManagedCode.Orleans.Graph/Managers/GrainGraphConfigurationAttribute.cs create mode 100644 ManagedCode.Orleans.Graph/Managers/GrainGraphManager.cs create mode 100644 ManagedCode.Orleans.Graph/Managers/GrainGraphSourceGenerator.cs delete mode 100644 ManagedCode.Orleans.Graph/gene.cs diff --git a/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainA.cs b/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainA.cs index 2a5c141..2379a67 100644 --- a/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainA.cs +++ b/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainA.cs @@ -11,16 +11,19 @@ public async Task MethodA1(int input) public async Task MethodB1(int input) { - return await GrainFactory.GetGrain(this.GetPrimaryKeyString()).MethodB1(input); + return await GrainFactory.GetGrain(this.GetPrimaryKeyString()) + .MethodB1(input); } public async Task MethodB2(int input) { - return await GrainFactory.GetGrain(this.GetPrimaryKeyString()).MethodC2(input); + return await GrainFactory.GetGrain(this.GetPrimaryKeyString()) + .MethodC2(input); } public async Task MethodC1(int input) { - return await GrainFactory.GetGrain(this.GetPrimaryKeyString()).MethodC1(input); + return await GrainFactory.GetGrain(this.GetPrimaryKeyString()) + .MethodC1(input); } } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainB.cs b/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainB.cs index 28fb5d9..bb0ffe5 100644 --- a/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainB.cs +++ b/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainB.cs @@ -11,6 +11,7 @@ public async Task MethodB1(int input) public async Task MethodC2(int input) { - return await GrainFactory.GetGrain(this.GetPrimaryKeyString()).MethodA2(input); + return await GrainFactory.GetGrain(this.GetPrimaryKeyString()) + .MethodA2(input); } } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainC.cs b/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainC.cs index 450fa95..2f4df55 100644 --- a/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainC.cs +++ b/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainC.cs @@ -11,7 +11,7 @@ public async Task MethodC1(int input) public async Task MethodA2(int input) { - return await GrainFactory.GetGrain(this.GetPrimaryKeyString()).MethodA1(input); + return await GrainFactory.GetGrain(this.GetPrimaryKeyString()) + .MethodA1(input); } - } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainD.cs b/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainD.cs index 746e185..e3764c6 100644 --- a/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainD.cs +++ b/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainD.cs @@ -11,6 +11,7 @@ public async Task MethodD1(int input) public async Task MethodE2(int input) { - return await GrainFactory.GetGrain(this.GetPrimaryKeyString()).MethodE1(input); + return await GrainFactory.GetGrain(this.GetPrimaryKeyString()) + .MethodE1(input); } } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainE.cs b/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainE.cs index b748303..47fc427 100644 --- a/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainE.cs +++ b/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/GrainE.cs @@ -11,6 +11,7 @@ public async Task MethodE1(int input) public async Task MethodD2(int input) { - return await GrainFactory.GetGrain(this.GetPrimaryKeyString()).MethodD1(input); + return await GrainFactory.GetGrain(this.GetPrimaryKeyString()) + .MethodD1(input); } } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/Interfaces/IGrainA.cs b/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/Interfaces/IGrainA.cs index ae51880..1af0ef9 100644 --- a/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/Interfaces/IGrainA.cs +++ b/ManagedCode.Orleans.Graph.Tests/Cluster/Grains/Interfaces/IGrainA.cs @@ -3,9 +3,9 @@ namespace ManagedCode.Orleans.Graph.Tests.Cluster.Grains.Interfaces; public interface IGrainA : IGrainWithStringKey { Task MethodA1(int input); - + Task MethodB1(int input); Task MethodB2(int input); - + Task MethodC1(int input); } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph.Tests/Cluster/TestSiloConfigurations.cs b/ManagedCode.Orleans.Graph.Tests/Cluster/TestSiloConfigurations.cs index c019026..a9082eb 100644 --- a/ManagedCode.Orleans.Graph.Tests/Cluster/TestSiloConfigurations.cs +++ b/ManagedCode.Orleans.Graph.Tests/Cluster/TestSiloConfigurations.cs @@ -8,18 +8,17 @@ public class TestSiloConfigurations : ISiloConfigurator { public void Configure(ISiloBuilder siloBuilder) { - siloBuilder.AddOrleansGraph() - .CreateGraph(graph => graph - .AddAllowedTransition() - .AddAllowedTransition() - - // .AddAllowedTransition() - // - // .AddAllowedTransition() - // - // .AddAllowedTransition() - // .AddAllowedTransition() - ); + siloBuilder.AddOrleansGraph() + .CreateGraph(graph => graph.AddAllowedTransition() + .AddAllowedTransition() + .AddAllowedTransition() + // .AddAllowedTransition() + // + // .AddAllowedTransition() + // + // .AddAllowedTransition() + // .AddAllowedTransition() + ); } } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph.Tests/GraphTests.cs b/ManagedCode.Orleans.Graph.Tests/GraphTests.cs index b5ea119..b6aac1a 100644 --- a/ManagedCode.Orleans.Graph.Tests/GraphTests.cs +++ b/ManagedCode.Orleans.Graph.Tests/GraphTests.cs @@ -16,7 +16,7 @@ public GraphTests(TestClusterApplication testApp, ITestOutputHelper outputHelper _testApp = testApp; _outputHelper = outputHelper; } - + [Fact] public async Task GrainA_B1Test() { @@ -33,19 +33,18 @@ await _testApp.Cluster .Client .GetGrain("1") .MethodA1(1); - + await _testApp.Cluster .Client .GetGrain("1") .MethodB1(1); - + await _testApp.Cluster .Client .GetGrain("1") .MethodC1(1); - } - + [Fact] public async Task TestGrainTests() { @@ -53,8 +52,5 @@ await _testApp.Cluster .Client .GetGrain("1") .MethodB2(1); - - - } } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph.Tests/ManagedCode.Orleans.Graph.Tests.csproj b/ManagedCode.Orleans.Graph.Tests/ManagedCode.Orleans.Graph.Tests.csproj index ed28041..11984d7 100644 --- a/ManagedCode.Orleans.Graph.Tests/ManagedCode.Orleans.Graph.Tests.csproj +++ b/ManagedCode.Orleans.Graph.Tests/ManagedCode.Orleans.Graph.Tests.csproj @@ -11,17 +11,17 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -38,14 +38,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - + + + + + + + + diff --git a/ManagedCode.Orleans.Graph/Extensions/OrleansCleintGraphExtensions.cs b/ManagedCode.Orleans.Graph/Extensions/OrleansCleintGraphExtensions.cs new file mode 100644 index 0000000..2c92ad3 --- /dev/null +++ b/ManagedCode.Orleans.Graph/Extensions/OrleansCleintGraphExtensions.cs @@ -0,0 +1,34 @@ +using System; +using ManagedCode.Orleans.Graph.Filters; +using ManagedCode.Orleans.Graph.Models; +using Microsoft.Extensions.DependencyInjection; +using Orleans.Hosting; + +namespace ManagedCode.Orleans.Graph.Extensions; + +public static class OrleansCleintGraphExtensions +{ + public static IClientBuilder AddOrleansGraph(this IClientBuilder builder) + { + return builder.AddOrleansGraph(_ => { }); + } + + + public static IClientBuilder AddOrleansGraph(this IClientBuilder builder, Action config) + { + var graphCallFilterConfig = new GraphCallFilterConfig(); + config.Invoke(graphCallFilterConfig); + builder.Services.AddSingleton(graphCallFilterConfig); + + builder.AddOutgoingGrainCallFilter(); + return builder; + } + + public static IClientBuilder CreateGraph(this IClientBuilder builder, Action graph) + { + var grainGraph = new GrainGraphManager(); + graph(grainGraph); + builder.ConfigureServices(services => services.AddSingleton(grainGraph)); + return builder; + } +} \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Extensions/OrleansGraphExtensions.cs b/ManagedCode.Orleans.Graph/Extensions/OrleansGraphExtensions.cs index eab947b..9dca6fd 100644 --- a/ManagedCode.Orleans.Graph/Extensions/OrleansGraphExtensions.cs +++ b/ManagedCode.Orleans.Graph/Extensions/OrleansGraphExtensions.cs @@ -32,31 +32,4 @@ public static ISiloBuilder CreateGraph(this ISiloBuilder builder, Action services.AddSingleton(grainGraph)); return builder; } -} - -public static class OrleansCleintGraphExtensions -{ - public static IClientBuilder AddOrleansGraph(this IClientBuilder builder) - { - return builder.AddOrleansGraph(_ => { }); - } - - - public static IClientBuilder AddOrleansGraph(this IClientBuilder builder, Action config) - { - var graphCallFilterConfig = new GraphCallFilterConfig(); - config.Invoke(graphCallFilterConfig); - builder.Services.AddSingleton(graphCallFilterConfig); - - builder.AddOutgoingGrainCallFilter(); - return builder; - } - - public static IClientBuilder CreateGraph(this IClientBuilder builder, Action graph) - { - var grainGraph = new GrainGraphManager(); - graph(grainGraph); - builder.ConfigureServices(services => services.AddSingleton(grainGraph)); - return builder; - } } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Extensions/RequestContextHelper.cs b/ManagedCode.Orleans.Graph/Extensions/RequestContextHelper.cs index 9685ed7..2c1816d 100644 --- a/ManagedCode.Orleans.Graph/Extensions/RequestContextHelper.cs +++ b/ManagedCode.Orleans.Graph/Extensions/RequestContextHelper.cs @@ -1,4 +1,4 @@ -using System.Linq; +using ManagedCode.Orleans.Graph.Interfaces; using ManagedCode.Orleans.Graph.Models; using Orleans; using Orleans.Runtime; @@ -7,48 +7,55 @@ namespace ManagedCode.Orleans.Graph.Extensions; public static class RequestContextHelper { - public static void TrackIncomingCall(this IIncomingGrainCallContext context) + public static bool TrackIncomingCall(this IIncomingGrainCallContext context) { - CallHistory call = context.GetCallHistory(); - call.Push( new InCall($"{context.Request.GetInterfaceName()}.{context.Request.GetMethod().Name}")); + var call = context.GetCallHistory(); + //var caller = context.TargetContext!.GrainInstance!.GetType().Name; + call.Push(new InCall(context.InterfaceName, context.MethodName)); context.SetCallHistory(call); + return true; } - - public static void TrackIncomingCall(this IIncomingGrainCallContext context, GraphCallFilterConfig graphCallFilterConfig) + + public static bool TrackOutgoingCall(this IOutgoingGrainCallContext context) { - if(!graphCallFilterConfig.TrackOrleansCalls && context.Request.GetType().Namespace!.Contains("Orleans")) - return; - - context.TrackIncomingCall(); + var caller = context.SourceId is null + ? Constants.ClientCallerId + : context.SourceContext!.GrainInstance!.GetType() + .Name; + var call = context.GetCallHistory(); + call.Push(new OutCall(caller, context.InterfaceName, context.MethodName)); + context.SetCallHistory(call); + return true; } - - public static void TrackOutgoingCall(this IOutgoingGrainCallContext context, GraphCallFilterConfig graphCallFilterConfig) + + public static bool TrackIncomingCall(this IIncomingGrainCallContext context, GraphCallFilterConfig graphCallFilterConfig) { - if(!graphCallFilterConfig.TrackOrleansCalls && context.InterfaceName.Contains("Orleans")) - return; - - context.TrackOutgoingCall(); + if (!graphCallFilterConfig.TrackOrleansCalls && context.ImplementationMethod.Module.Name.StartsWith("Orleans.")) + return false; + + return context.TrackIncomingCall(); } - - public static void TrackOutgoingCall(this IOutgoingGrainCallContext context) + + public static bool TrackOutgoingCall(this IOutgoingGrainCallContext context, GraphCallFilterConfig graphCallFilterConfig) { - CallHistory call = context.GetCallHistory(); - call.Push( new OutCall(context.SourceContext?.GrainInstance?.GetType().Name ?? Interfaces.Constants.ClientCallerId, $"{context.Request.GetInterfaceName()}.{context.Request.GetMethod().Name}")); - context.SetCallHistory(call); + if (!graphCallFilterConfig.TrackOrleansCalls && context.InterfaceMethod.Module.Name.StartsWith("Orleans.")) + return false; + + return context.TrackOutgoingCall(); } - + public static CallHistory GetCallHistory(this IGrainCallContext context) { - return RequestContext.Get(Interfaces.Constants.RequestContextKey) as CallHistory ?? new CallHistory(); + return RequestContext.Get(Constants.RequestContextKey) as CallHistory ?? new CallHistory(); } - + public static bool IsCallHistoryExist(this IGrainCallContext context) { - return RequestContext.Get(Interfaces.Constants.RequestContextKey) is CallHistory; + return RequestContext.Get(Constants.RequestContextKey) is CallHistory; } - + public static void SetCallHistory(this IGrainCallContext context, CallHistory callHistory) { - RequestContext.Set(Interfaces.Constants.RequestContextKey, callHistory); + RequestContext.Set(Constants.RequestContextKey, callHistory); } } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Filters/GraphIncomingGrainCallFilter.cs b/ManagedCode.Orleans.Graph/Filters/GraphIncomingGrainCallFilter.cs index 0a62b89..b4effce 100644 --- a/ManagedCode.Orleans.Graph/Filters/GraphIncomingGrainCallFilter.cs +++ b/ManagedCode.Orleans.Graph/Filters/GraphIncomingGrainCallFilter.cs @@ -1,71 +1,21 @@ using System; -using System.Linq; using System.Threading.Tasks; using ManagedCode.Orleans.Graph.Extensions; using ManagedCode.Orleans.Graph.Models; using Microsoft.Extensions.DependencyInjection; using Orleans; -using Orleans.Runtime; namespace ManagedCode.Orleans.Graph.Filters; public class GraphIncomingGrainCallFilter(IServiceProvider serviceProvider, GraphCallFilterConfig graphCallFilterConfig) : IIncomingGrainCallFilter { - GrainGraphManager graphManager => serviceProvider.GetService(); - - public async Task Invoke(IIncomingGrainCallContext context) + private GrainGraphManager? GraphManager => serviceProvider.GetService(); + + public Task Invoke(IIncomingGrainCallContext context) { - context.TrackIncomingCall(graphCallFilterConfig); - - var transitionAllowed = graphManager?.IsTransitionAllowed(context.TargetContext?.GrainInstance?.GetType(), context.TargetContext?.GrainInstance?.GetType()); - - await context.Invoke(); - - if (context.IsCallHistoryExist()) - { - var history = context.GetCallHistory(); - var x = 5; - } - } -} + if (context.TrackIncomingCall(graphCallFilterConfig)) + GraphManager?.IsTransitionAllowed(context.GetCallHistory()); - - - - - -// var id = RequestContext.Get("ID") ?? Guid.NewGuid().ToString(); -// RequestContext.Set("ID", id); -// -// var targetContext = context.TargetContext; -// var interfaceNameSource = targetContext?.GrainReference?.InterfaceName; -// -// var interfaceName = context.InterfaceName; -// var methodName = context.MethodName; -// -// -// var grainType = context.Grain.GetType().Name; -// -// var callerGrainType = context.TargetContext?.GrainInstance?.GetType().Name; -// -// var requestInterface = context.Request.GetInterfaceName(); -// var requestMethod = context.Request.GetMethod().Name; -// -// if (interfaceName.StartsWith("ManagedCode")) -// { -// var x = 5; -// var transitionAllowed = graphManager?.IsTransitionAllowed(context.TargetContext?.GrainInstance?.GetType(), context.TargetContext?.GrainInstance?.GetType()); -// RequestContext.Set(Guid.NewGuid().ToString(), $"in = > {requestInterface}.{requestMethod}"); -// } -// -// -// RequestContextHelper.IncomingCall() -// -// -// await context.Invoke(); -// -// if (interfaceName.StartsWith("ManagedCode")) -// { -// var x = 5; -// var xx = RequestContext.Entries?.ToArray() ?? []; -// } \ No newline at end of file + return context.Invoke(); + } +} \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Filters/GraphOutgoingGrainCallFilter.cs b/ManagedCode.Orleans.Graph/Filters/GraphOutgoingGrainCallFilter.cs index d4918a2..481b8ed 100644 --- a/ManagedCode.Orleans.Graph/Filters/GraphOutgoingGrainCallFilter.cs +++ b/ManagedCode.Orleans.Graph/Filters/GraphOutgoingGrainCallFilter.cs @@ -1,69 +1,21 @@ using System; -using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; using ManagedCode.Orleans.Graph.Extensions; using ManagedCode.Orleans.Graph.Models; using Microsoft.Extensions.DependencyInjection; using Orleans; -using Orleans.Runtime; namespace ManagedCode.Orleans.Graph.Filters; public class GraphOutgoingGrainCallFilter(IServiceProvider serviceProvider, GraphCallFilterConfig graphCallFilterConfig) : IOutgoingGrainCallFilter { - GrainGraphManager graphManager => serviceProvider.GetService(); - - - public async Task Invoke(IOutgoingGrainCallContext context) - { - context.TrackOutgoingCall(graphCallFilterConfig); - - var transitionAllowed = graphManager?.IsTransitionAllowed(context.Grain.GetType(), context.Grain.GetType()); - - await context.Invoke(); - - if (context.IsCallHistoryExist()) - { - var history = context.GetCallHistory(); - var x = 5; - } - } -} + private GrainGraphManager? GraphManager => serviceProvider.GetService(); + public Task Invoke(IOutgoingGrainCallContext context) + { + if (context.TrackOutgoingCall(graphCallFilterConfig)) + GraphManager?.IsTransitionAllowed(context.GetCallHistory()); - - -// var id = RequestContext.Get("ID") ?? Guid.NewGuid().ToString(); -// RequestContext.Set("ID", id); -// -// var sourceContext = context.SourceContext; -// var interfaceNameSource = sourceContext?.GrainReference?.InterfaceName; -// -// var interfaceName = context.InterfaceName; -// var methodName = context.MethodName; -// -// -// var callerGrainType = context.SourceContext?.GrainInstance?.GetType().Name ?? "CLIENT"; -// -// var grainType = context.Grain.GetType().Name; -// -// var targetInterface = context.Request.GetInterfaceName(); -// var tergettMethod = context.Request.GetMethod().Name; -// -// if (interfaceName.StartsWith("ManagedCode")) -// { -// var x = 5; -// var transitionAllowed = graphManager?.IsTransitionAllowed(context.Grain.GetType(), context.Grain.GetType()); -// RequestContext.Set(Guid.NewGuid().ToString(), $"out => {callerGrainType} > {targetInterface}.{tergettMethod}"); -// } -// -// -// -// await context.Invoke(); -// -// if (interfaceName.StartsWith("ManagedCode")) -// { -// var x = 5; -// var xx = RequestContext.Entries?.ToArray() ?? []; -// } \ No newline at end of file + return context.Invoke(); + } +} \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Interfaces/Constants.cs b/ManagedCode.Orleans.Graph/Interfaces/Constants.cs index 5216495..28e9e8f 100644 --- a/ManagedCode.Orleans.Graph/Interfaces/Constants.cs +++ b/ManagedCode.Orleans.Graph/Interfaces/Constants.cs @@ -2,6 +2,6 @@ namespace ManagedCode.Orleans.Graph.Interfaces; public static class Constants { - public static readonly string RequestContextKey ="mc.callhistory"; - public static readonly string ClientCallerId ="ORLEANS_GRAIN_CLIENT"; + public static readonly string RequestContextKey = "mc.callhistory"; + public static readonly string ClientCallerId = "ORLEANS_GRAIN_CLIENT"; } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/ManagedCode.Orleans.Graph.csproj b/ManagedCode.Orleans.Graph/ManagedCode.Orleans.Graph.csproj index cb1c9c8..b75bbcb 100644 --- a/ManagedCode.Orleans.Graph/ManagedCode.Orleans.Graph.csproj +++ b/ManagedCode.Orleans.Graph/ManagedCode.Orleans.Graph.csproj @@ -9,11 +9,11 @@ - - - - - + + + + + diff --git a/ManagedCode.Orleans.Graph/GrainGraph.cs b/ManagedCode.Orleans.Graph/Managers/DirectedGraph.cs similarity index 61% rename from ManagedCode.Orleans.Graph/GrainGraph.cs rename to ManagedCode.Orleans.Graph/Managers/DirectedGraph.cs index 8fe2eb3..4c86bb1 100644 --- a/ManagedCode.Orleans.Graph/GrainGraph.cs +++ b/ManagedCode.Orleans.Graph/Managers/DirectedGraph.cs @@ -1,22 +1,17 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Orleans; +using System; +using System.Collections.Generic; namespace ManagedCode.Orleans.Graph; -using System; -using System.Collections.Generic; -using System.Linq; - public class DirectedGraph where T : class { private readonly Dictionary> _adjacencyList; + private readonly bool _allowSelfLoops; private readonly HashSet _vertices; - public DirectedGraph() + public DirectedGraph(bool allowSelfLoops = false) { + _allowSelfLoops = allowSelfLoops; _adjacencyList = new Dictionary>(); _vertices = new HashSet(); } @@ -32,22 +27,28 @@ public void AddVertex(T vertex) public void AddEdge(T source, T destination) { + if (_allowSelfLoops && source.Equals(destination)) + return; + // Add vertices if they don't exist AddVertex(source); AddVertex(destination); - _adjacencyList[source].Add(destination); - + _adjacencyList[source] + .Add(destination); + // Check for cycles immediately when adding edge if (HasCycle()) - { - throw new InvalidOperationException($"Adding edge from {source} to {destination} creates a cycle in the graph"); - } + throw new InvalidOperationException($"Adding edge from {source} to {destination} creates a cycle in the graph."); } public bool IsTransitionAllowed(T source, T destination) { - return _adjacencyList.ContainsKey(source) && _adjacencyList[source].Contains(destination); + if (_allowSelfLoops && source.Equals(destination)) + return true; + + return _adjacencyList.ContainsKey(source) && _adjacencyList[source] + .Contains(destination); } private bool HasCycle() @@ -56,12 +57,8 @@ private bool HasCycle() var recursionStack = new HashSet(); foreach (var vertex in _vertices) - { if (IsCyclicUtil(vertex, visited, recursionStack)) - { return true; - } - } return false; } @@ -76,13 +73,10 @@ private bool IsCyclicUtil(T vertex, HashSet visited, HashSet recursionStac foreach (var neighbor in _adjacencyList[vertex]) { if (!visited.Contains(neighbor) && IsCyclicUtil(neighbor, visited, recursionStack)) - { return true; - } - else if (recursionStack.Contains(neighbor)) - { + + if (recursionStack.Contains(neighbor)) return true; - } } } @@ -90,38 +84,13 @@ private bool IsCyclicUtil(T vertex, HashSet visited, HashSet recursionStac return false; } - public IEnumerable GetAllVertices() => _vertices; - - public IEnumerable GetAdjacentVertices(T vertex) - { - return _adjacencyList.TryGetValue(vertex, out var value) ? value : []; - } -} - -[GrainGraphConfiguration] -public class GrainGraphManager -{ - private readonly DirectedGraph _grainGraph; - - public GrainGraphManager() - { - _grainGraph = new DirectedGraph(); - } - - public GrainGraphManager AddAllowedTransition(Type sourceGrain, Type targetGrain) - { - _grainGraph.AddEdge(sourceGrain, targetGrain); - return this; - } - - public GrainGraphManager AddAllowedTransition() + public IEnumerable GetAllVertices() { - return AddAllowedTransition(typeof(T1), typeof(T2)); + return _vertices; } - public bool IsTransitionAllowed(Type sourceGrain, Type targetGrain) + public IEnumerable GetAdjacentVertices(T vertex) { - return _grainGraph.IsTransitionAllowed(sourceGrain, targetGrain); + return _adjacencyList.TryGetValue(vertex, out var value) ? value : []; } -} - +} \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Managers/GrainGraphConfigurationAttribute.cs b/ManagedCode.Orleans.Graph/Managers/GrainGraphConfigurationAttribute.cs new file mode 100644 index 0000000..f27872f --- /dev/null +++ b/ManagedCode.Orleans.Graph/Managers/GrainGraphConfigurationAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace ManagedCode.Orleans.Graph; + +[AttributeUsage(AttributeTargets.Class)] +public class GrainGraphConfigurationAttribute : Attribute +{ +} \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Managers/GrainGraphManager.cs b/ManagedCode.Orleans.Graph/Managers/GrainGraphManager.cs new file mode 100644 index 0000000..f43aa54 --- /dev/null +++ b/ManagedCode.Orleans.Graph/Managers/GrainGraphManager.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using ManagedCode.Orleans.Graph.Models; + +namespace ManagedCode.Orleans.Graph; + +[GrainGraphConfiguration] +public class GrainGraphManager +{ + private readonly DirectedGraph _grainGraph; + + public GrainGraphManager() + { + _grainGraph = new DirectedGraph(true); + } + + public GrainGraphManager AddAllowedTransition(Type sourceGrain, Type targetGrain) + { + _grainGraph.AddEdge(sourceGrain.FullName!, targetGrain.FullName!); + return this; + } + + public GrainGraphManager AddAllowedTransition() + { + return AddAllowedTransition(typeof(T1), typeof(T2)); + } + + public GrainGraphManager AddAllowedTransition() + { + return AddAllowedTransition(typeof(T1), typeof(T1)); + } + + public bool IsTransitionAllowed(CallHistory callHistory) + { + if (callHistory.IsEmpty()) + return false; + + var calls = callHistory.History + .Reverse() + .ToArray(); + + for (var i = 0; i < calls.Length - 1; i++) + { + var source = calls[i].Interface; + var target = calls[i + 1].Interface; + + if (!_grainGraph.IsTransitionAllowed(source, target)) + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Managers/GrainGraphSourceGenerator.cs b/ManagedCode.Orleans.Graph/Managers/GrainGraphSourceGenerator.cs new file mode 100644 index 0000000..ef54a8e --- /dev/null +++ b/ManagedCode.Orleans.Graph/Managers/GrainGraphSourceGenerator.cs @@ -0,0 +1,114 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ManagedCode.Orleans.Graph; + +[Generator] +public class GrainGraphSourceGenerator : ISourceGenerator +{ + private const string DiagnosticId = "GG001"; + + private static readonly DiagnosticDescriptor CycleRule = new(DiagnosticId, "Cycle detected in grain graph", + "The grain configuration contains cycles in builder pattern", "GrainGraph", DiagnosticSeverity.Error, true); + + public void Execute(GeneratorExecutionContext context) + { + if (context.SyntaxReceiver is not GrainGraphSyntaxReceiver receiver) + return; + + foreach (var graphConfig in receiver.GraphConfigurations) + { + var model = context.Compilation.GetSemanticModel(graphConfig.SyntaxTree); + var transitions = FindTransitions(graphConfig, model); + + if (HasCycles(transitions)) + context.ReportDiagnostic(Diagnostic.Create(CycleRule, graphConfig.GetLocation())); + } + } + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new GrainGraphSyntaxReceiver()); + } + + private static IEnumerable<(ITypeSymbol Source, ITypeSymbol Target)> FindTransitions(InvocationExpressionSyntax graphConfig, SemanticModel model) + { + var transitions = new List<(ITypeSymbol, ITypeSymbol)>(); + + var addTransitionCalls = graphConfig.DescendantNodes() + .OfType() + .Where(i => i.Expression + .ToString() + .Contains("AddAllowedTransition")); + + foreach (var call in addTransitionCalls) + if (call.ArgumentList.Arguments.Count == 2) + { + var sourceArg = call.ArgumentList.Arguments[0].Expression; + var targetArg = call.ArgumentList.Arguments[1].Expression; + + var sourceType = model.GetTypeInfo(sourceArg) + .Type; + var targetType = model.GetTypeInfo(targetArg) + .Type; + + if (sourceType != null && targetType != null) + transitions.Add((sourceType, targetType)); + } + + return transitions; + } + + private static bool HasCycles(IEnumerable<(ITypeSymbol Source, ITypeSymbol Target)> transitions) + { + var graph = transitions.GroupBy(t => t.Source, SymbolEqualityComparer.Default) + .ToDictionary(g => g.Key, g => g.Select(t => t.Target) + .ToHashSet(SymbolEqualityComparer.Default), SymbolEqualityComparer.Default); + + var visited = new HashSet(SymbolEqualityComparer.Default); + var recursionStack = new HashSet(SymbolEqualityComparer.Default); + + foreach (var vertex in graph.Keys) + if (IsCyclicUtil(vertex, graph, visited, recursionStack)) + return true; + return false; + } + + private static bool IsCyclicUtil(ISymbol vertex, Dictionary> graph, HashSet visited, + HashSet recursionStack) + { + if (!visited.Contains(vertex)) + { + visited.Add(vertex); + recursionStack.Add(vertex); + + if (graph.TryGetValue(vertex, out var neighbors)) + foreach (var neighbor in neighbors) + { + if (!visited.Contains(neighbor) && IsCyclicUtil(neighbor, graph, visited, recursionStack)) + return true; + if (recursionStack.Contains(neighbor)) + return true; + } + } + + recursionStack.Remove(vertex); + return false; + } + + private class GrainGraphSyntaxReceiver : ISyntaxReceiver + { + public List GraphConfigurations { get; } = new(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is InvocationExpressionSyntax invocation && invocation.Expression + .ToString() + .Contains("CreateGraph")) + GraphConfigurations.Add(invocation); + } + } +} \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Models/Call.cs b/ManagedCode.Orleans.Graph/Models/Call.cs index 0bd50ef..b46680f 100644 --- a/ManagedCode.Orleans.Graph/Models/Call.cs +++ b/ManagedCode.Orleans.Graph/Models/Call.cs @@ -5,11 +5,14 @@ namespace ManagedCode.Orleans.Graph.Models; [Immutable] [GenerateSerializer] [Alias("MC.Call")] -public class Call(Direction direction, string method) +public class Call(Direction direction, string type, string method) { [Id(0)] public Direction Direction { get; set; } = direction; [Id(1)] + public string Interface { get; set; } = type; + + [Id(2)] public string Method { get; set; } = method; } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Models/CallHistory.cs b/ManagedCode.Orleans.Graph/Models/CallHistory.cs index 3890827..4169c67 100644 --- a/ManagedCode.Orleans.Graph/Models/CallHistory.cs +++ b/ManagedCode.Orleans.Graph/Models/CallHistory.cs @@ -1,24 +1,27 @@ using System; -using System.Collections; using System.Collections.Generic; using Orleans; namespace ManagedCode.Orleans.Graph.Models; - [Immutable] [GenerateSerializer] [Alias("MC.CallHistory")] public class CallHistory { - [Id(0)] - public Guid Id = Guid.NewGuid(); + [Id(0)] public Guid Id = Guid.NewGuid(); [Id(1)] - public Stack History { get; private set; } = new(); - - - public void Push(Call call) => History.Push(call); - - public bool IsEmpty() => History.Count == 0; + public Stack History { get; } = new(); + + + public void Push(Call call) + { + History.Push(call); + } + + public bool IsEmpty() + { + return History.Count == 0; + } } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Models/InCall.cs b/ManagedCode.Orleans.Graph/Models/InCall.cs index dfb2252..9432f8d 100644 --- a/ManagedCode.Orleans.Graph/Models/InCall.cs +++ b/ManagedCode.Orleans.Graph/Models/InCall.cs @@ -5,4 +5,4 @@ namespace ManagedCode.Orleans.Graph.Models; [Immutable] [GenerateSerializer] [Alias("MC.InCall")] -public class InCall(string method) : Call(Direction.In, method); \ No newline at end of file +public class InCall(string type, string method) : Call(Direction.In, type, method); \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Models/OutCall.cs b/ManagedCode.Orleans.Graph/Models/OutCall.cs index 54f83bc..f972f4c 100644 --- a/ManagedCode.Orleans.Graph/Models/OutCall.cs +++ b/ManagedCode.Orleans.Graph/Models/OutCall.cs @@ -5,7 +5,7 @@ namespace ManagedCode.Orleans.Graph.Models; [Immutable] [GenerateSerializer] [Alias("MC.OutCall")] -public class OutCall(string caller, string method) : Call(Direction.Out, method) +public class OutCall(string caller, string type, string method) : Call(Direction.Out, type, method) { [Id(0)] public string Caller { get; set; } = caller; diff --git a/ManagedCode.Orleans.Graph/gene.cs b/ManagedCode.Orleans.Graph/gene.cs deleted file mode 100644 index b579d98..0000000 --- a/ManagedCode.Orleans.Graph/gene.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using System.Linq; - -namespace ManagedCode.Orleans.Graph; - - -[AttributeUsage(AttributeTargets.Class)] -public class GrainGraphConfigurationAttribute : Attribute { } - -[Generator] -public class GrainGraphSourceGenerator : ISourceGenerator -{ - private const string DiagnosticId = "GG001"; - private static readonly DiagnosticDescriptor CycleRule = new( - DiagnosticId, - "Cycle detected in grain graph", - "The grain configuration contains cycles in builder pattern", - "GrainGraph", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - - public void Execute(GeneratorExecutionContext context) - { - if (context.SyntaxReceiver is not GrainGraphSyntaxReceiver receiver) - return; - - foreach (var graphConfig in receiver.GraphConfigurations) - { - var model = context.Compilation.GetSemanticModel(graphConfig.SyntaxTree); - var transitions = FindTransitions(graphConfig, model); - - if (HasCycles(transitions)) - { - context.ReportDiagnostic( - Diagnostic.Create(CycleRule, graphConfig.GetLocation()) - ); - } - } - } - - private static IEnumerable<(ITypeSymbol Source, ITypeSymbol Target)> FindTransitions( - InvocationExpressionSyntax graphConfig, - SemanticModel model) -{ - var transitions = new List<(ITypeSymbol, ITypeSymbol)>(); - - var addTransitionCalls = graphConfig - .DescendantNodes() - .OfType() - .Where(i => i.Expression.ToString().Contains("AddAllowedTransition")); - - foreach (var call in addTransitionCalls) - { - if (call.ArgumentList.Arguments.Count == 2) - { - var sourceArg = call.ArgumentList.Arguments[0].Expression; - var targetArg = call.ArgumentList.Arguments[1].Expression; - - var sourceType = model.GetTypeInfo(sourceArg).Type; - var targetType = model.GetTypeInfo(targetArg).Type; - - if (sourceType != null && targetType != null) - { - transitions.Add((sourceType, targetType)); - } - } - } - - return transitions; -} - -private static bool HasCycles(IEnumerable<(ITypeSymbol Source, ITypeSymbol Target)> transitions) -{ - var graph = transitions - .GroupBy(t => t.Source, SymbolEqualityComparer.Default) - .ToDictionary( - g => g.Key, - g => g.Select(t => t.Target).ToHashSet(SymbolEqualityComparer.Default), - SymbolEqualityComparer.Default - ); - - var visited = new HashSet(SymbolEqualityComparer.Default); - var recursionStack = new HashSet(SymbolEqualityComparer.Default); - - foreach (var vertex in graph.Keys) - { - if (IsCyclicUtil(vertex, graph, visited, recursionStack)) - return true; - } - return false; -} - -private static bool IsCyclicUtil( - ISymbol vertex, - Dictionary> graph, - HashSet visited, - HashSet recursionStack) -{ - if (!visited.Contains(vertex)) - { - visited.Add(vertex); - recursionStack.Add(vertex); - - if (graph.TryGetValue(vertex, out var neighbors)) - { - foreach (var neighbor in neighbors) - { - if (!visited.Contains(neighbor) && - IsCyclicUtil(neighbor, graph, visited, recursionStack)) - { - return true; - } - if (recursionStack.Contains(neighbor)) - { - return true; - } - } - } - } - - recursionStack.Remove(vertex); - return false; -} - - public void Initialize(GeneratorInitializationContext context) - { - context.RegisterForSyntaxNotifications(() => new GrainGraphSyntaxReceiver()); - } - - private class GrainGraphSyntaxReceiver : ISyntaxReceiver - { - public List GraphConfigurations { get; } = new(); - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is InvocationExpressionSyntax invocation && - invocation.Expression.ToString().Contains("CreateGraph")) - { - GraphConfigurations.Add(invocation); - } - } - } -} \ No newline at end of file