From b1edd84490b50c7a8267fb2663be8b3fb102a47b Mon Sep 17 00:00:00 2001 From: Konstantin Semenenko Date: Wed, 20 Nov 2024 14:35:25 +0100 Subject: [PATCH] readme --- .../GrainTransitionManagerTests.cs | 56 ++--- ManagedCode.Orleans.Graph.Tests/GraphTests.cs | 6 +- .../Extensions/RequestContextHelper.cs | 7 +- .../Filters/GraphIncomingGrainCallFilter.cs | 6 +- .../Filters/GraphOutgoingGrainCallFilter.cs | 6 +- .../GrainTransitionManager.cs | 48 +++-- ManagedCode.Orleans.Graph/Models/Call.cs | 11 +- ManagedCode.Orleans.Graph/Models/InCall.cs | 3 +- ManagedCode.Orleans.Graph/Models/OutCall.cs | 3 +- README.md | 201 +++++++++++++++++- 10 files changed, 278 insertions(+), 69 deletions(-) diff --git a/ManagedCode.Orleans.Graph.Tests/GrainTransitionManagerTests.cs b/ManagedCode.Orleans.Graph.Tests/GrainTransitionManagerTests.cs index 7a27630..6dbdd60 100644 --- a/ManagedCode.Orleans.Graph.Tests/GrainTransitionManagerTests.cs +++ b/ManagedCode.Orleans.Graph.Tests/GrainTransitionManagerTests.cs @@ -19,8 +19,8 @@ public void IsTransitionAllowed_SingleValidTransition_ReturnsTrue() .Build(); var callHistory = new CallHistory(); - callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); Assert.True(graph.IsTransitionAllowed(callHistory)); } @@ -36,8 +36,8 @@ public void IsTransitionAllowed_InvalidTransition_ReturnsFalse() .Build(); var callHistory = new CallHistory(); - callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); Assert.False(graph.IsTransitionAllowed(callHistory)); } @@ -72,8 +72,8 @@ public void IsTransitionAllowed_ReentrancyAllowed_ReturnsTrue() .Build(); var callHistory = new CallHistory(); - callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); Assert.True(graph.IsTransitionAllowed(callHistory)); } @@ -89,8 +89,8 @@ public void IsTransitionAllowed_MethodRuleAllowed_ReturnsTrue() .Build(); var callHistory = new CallHistory(); - callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); Assert.True(graph.IsTransitionAllowed(callHistory)); } @@ -105,8 +105,8 @@ public void IsTransitionAllowed_SelfLoopTransition_ReturnsTrue() .Build(); var callHistory = new CallHistory(); - callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); Assert.True(graph.IsTransitionAllowed(callHistory)); } @@ -122,8 +122,8 @@ public void IsTransitionAllowed_DisallowedTransition_ReturnsFalse() .Build(); var callHistory = new CallHistory(); - callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1))); Assert.False(graph.IsTransitionAllowed(callHistory)); } @@ -143,10 +143,10 @@ public void IsTransitionAllowed_MultipleValidTransitions_ReturnsTrue() .Build(); var callHistory = new CallHistory(); - callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); - callHistory.Push(new Call(Direction.Out, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1))); Assert.True(graph.IsTransitionAllowed(callHistory)); } @@ -162,8 +162,8 @@ public void IsTransitionAllowed_InvalidMethodRule_ReturnsFalse() .Build(); var callHistory = new CallHistory(); - callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, "MethodC2")); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, "MethodC2")); Assert.False(graph.IsTransitionAllowed(callHistory)); } @@ -212,12 +212,12 @@ public void IsTransitionAllowed_DetectsSimpleLoop_ReturnsFalse() .Build(); var callHistory = new CallHistory(); - callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); - callHistory.Push(new Call(Direction.Out, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1))); - callHistory.Push(new Call(Direction.Out, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); Assert.False(graph.IsTransitionAllowed(callHistory)); } @@ -237,10 +237,10 @@ public void IsTransitionAllowed_NoLoop_ReturnsTrue() .Build(); var callHistory = new CallHistory(); - callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); - callHistory.Push(new Call(Direction.Out, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); - callHistory.Push(new Call(Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); + callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1))); + callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1))); Assert.True(graph.IsTransitionAllowed(callHistory)); } diff --git a/ManagedCode.Orleans.Graph.Tests/GraphTests.cs b/ManagedCode.Orleans.Graph.Tests/GraphTests.cs index 789e90f..f6ae7c8 100644 --- a/ManagedCode.Orleans.Graph.Tests/GraphTests.cs +++ b/ManagedCode.Orleans.Graph.Tests/GraphTests.cs @@ -49,7 +49,7 @@ await _testApp.Cluster exception.Message .Should() - .StartWith("Transition is not allowed."); + .StartWith("Transition from ORLEANS_GRAIN_CLIENT to ManagedCode.Orleans.Graph.Tests.Cluster.Grains.Interfaces.IGrainC is not allowed."); } @@ -85,7 +85,7 @@ await _testApp.Cluster exception.Message .Should() - .StartWith("Transition is not allowed."); + .StartWith("Transition from"); } [Fact] @@ -101,6 +101,6 @@ await _testApp.Cluster exception.Message .Should() - .StartWith("Transition is not allowed."); + .StartWith("Deadlock detected."); } } \ No newline at end of file diff --git a/ManagedCode.Orleans.Graph/Extensions/RequestContextHelper.cs b/ManagedCode.Orleans.Graph/Extensions/RequestContextHelper.cs index 029d505..9db5ade 100644 --- a/ManagedCode.Orleans.Graph/Extensions/RequestContextHelper.cs +++ b/ManagedCode.Orleans.Graph/Extensions/RequestContextHelper.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using ManagedCode.Orleans.Graph.Interfaces; using ManagedCode.Orleans.Graph.Models; @@ -12,7 +13,7 @@ public static bool TrackIncomingCall(this IIncomingGrainCallContext context) { var call = context.GetCallHistory(); //var caller = context.TargetContext!.GrainInstance!.GetType().Name; - call.Push(new InCall(context.InterfaceName, context.MethodName)); + call.Push(new InCall(context.SourceId, context.TargetId, context.InterfaceName, context.MethodName)); context.SetCallHistory(call); return true; } @@ -23,10 +24,10 @@ public static bool TrackOutgoingCall(this IOutgoingGrainCallContext context) ? Constants.ClientCallerId : context.SourceContext!.GrainInstance!.GetType() .GetInterfaces() - .FirstOrDefault(i => typeof(IGrain).IsAssignableFrom(i))?.FullName; + .FirstOrDefault(i => typeof(IGrain).IsAssignableFrom(i))?.FullName ?? string.Empty; var call = context.GetCallHistory(); - call.Push(new OutCall(caller, context.InterfaceName, context.MethodName)); + call.Push(new OutCall(context.SourceId, context.TargetId, caller, context.InterfaceName, context.MethodName)); context.SetCallHistory(call); return true; } diff --git a/ManagedCode.Orleans.Graph/Filters/GraphIncomingGrainCallFilter.cs b/ManagedCode.Orleans.Graph/Filters/GraphIncomingGrainCallFilter.cs index 515bc74..396c3be 100644 --- a/ManagedCode.Orleans.Graph/Filters/GraphIncomingGrainCallFilter.cs +++ b/ManagedCode.Orleans.Graph/Filters/GraphIncomingGrainCallFilter.cs @@ -15,10 +15,8 @@ public Task Invoke(IIncomingGrainCallContext context) { if (context.TrackIncomingCall(graphCallFilterConfig)) { - if (GraphManager?.IsTransitionAllowed(context.GetCallHistory()) == false ) - { - throw new InvalidOperationException("Transition is not allowed.\n" + context.GetCallHistory()); - } + GraphManager?.IsTransitionAllowed(context.GetCallHistory(), true); + GraphManager?.DetectDeadlocks(context.GetCallHistory(), true); } return context.Invoke(); diff --git a/ManagedCode.Orleans.Graph/Filters/GraphOutgoingGrainCallFilter.cs b/ManagedCode.Orleans.Graph/Filters/GraphOutgoingGrainCallFilter.cs index 4264cf2..1784138 100644 --- a/ManagedCode.Orleans.Graph/Filters/GraphOutgoingGrainCallFilter.cs +++ b/ManagedCode.Orleans.Graph/Filters/GraphOutgoingGrainCallFilter.cs @@ -15,10 +15,8 @@ public Task Invoke(IOutgoingGrainCallContext context) { if (context.TrackOutgoingCall(graphCallFilterConfig)) { - if (GraphManager?.IsTransitionAllowed(context.GetCallHistory()) == false ) - { - throw new InvalidOperationException("Transition is not allowed.\n" + context.GetCallHistory()); - } + GraphManager?.IsTransitionAllowed(context.GetCallHistory(), true); + GraphManager?.DetectDeadlocks(context.GetCallHistory(), true); } return context.Invoke(); diff --git a/ManagedCode.Orleans.Graph/GrainTransitionManager.cs b/ManagedCode.Orleans.Graph/GrainTransitionManager.cs index c7f950d..0521600 100644 --- a/ManagedCode.Orleans.Graph/GrainTransitionManager.cs +++ b/ManagedCode.Orleans.Graph/GrainTransitionManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using ManagedCode.Orleans.Graph.Models; +using Orleans.Runtime; namespace ManagedCode.Orleans.Graph; @@ -13,7 +14,7 @@ public GrainTransitionManager(DirectedGraph grainGraph) _grainGraph = grainGraph ?? throw new ArgumentNullException(nameof(grainGraph)); } - public bool IsTransitionAllowed(CallHistory callHistory) + public bool IsTransitionAllowed(CallHistory callHistory, bool throwOnViolation = false) { if (callHistory.IsEmpty()) { @@ -36,6 +37,10 @@ public bool IsTransitionAllowed(CallHistory callHistory) { if (!_grainGraph.IsTransitionAllowed(outCallTransition.Caller, nextCall.Interface, currentCall.Method, nextCall.Method)) { + if(throwOnViolation) + { + throw new InvalidOperationException($"Transition from {outCallTransition.Caller} to {nextCall.Interface} is not allowed."); + } return false; } } @@ -43,57 +48,56 @@ public bool IsTransitionAllowed(CallHistory callHistory) { if (!_grainGraph.IsTransitionAllowed(currentCall.Interface, nextCall.Interface, currentCall.Method, nextCall.Method)) { + if(throwOnViolation) + { + throw new InvalidOperationException($"Transition from {currentCall.Interface} to {nextCall.Interface} is not allowed."); + } return false; } } } } - - // Check for deadlock or cycle - if (DetectCycle(calls)) - { - return false; - } - - + return true; } - private bool DetectCycle(Call[] calls) + public bool DetectDeadlocks(CallHistory callHistory, bool throwOnViolation = false) { - var graph = new Dictionary>(); + var graph = new Dictionary>(); - foreach (var call in calls) + foreach (var call in callHistory.History) { - if (call is OutCall outCall) + if (call.SourceId.HasValue && call.TargetId.HasValue) { - if (!graph.ContainsKey(outCall.Caller)) + if (!graph.ContainsKey(call.SourceId.Value)) { - graph[outCall.Caller] = new List(); + graph[call.SourceId.Value] = new List(); } - graph[outCall.Caller] - .Add(call.Interface); + graph[call.SourceId.Value].Add(call.TargetId.Value); } } - - var visited = new HashSet(); - var stack = new HashSet(); + var visited = new HashSet(); + var stack = new HashSet(); foreach (var node in graph.Keys) { if (IsCyclic(node, graph, visited, stack)) { + if(throwOnViolation) + { + throw new InvalidOperationException($"Deadlock detected. GrainId: {node}"); + } + return true; } } - return false; } - private bool IsCyclic(string node, Dictionary> graph, HashSet visited, HashSet stack) + private bool IsCyclic(GrainId node, Dictionary> graph, HashSet visited, HashSet stack) { if (stack.Contains(node)) { diff --git a/ManagedCode.Orleans.Graph/Models/Call.cs b/ManagedCode.Orleans.Graph/Models/Call.cs index 2e246e0..9901c4f 100644 --- a/ManagedCode.Orleans.Graph/Models/Call.cs +++ b/ManagedCode.Orleans.Graph/Models/Call.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using Orleans; +using Orleans.Runtime; namespace ManagedCode.Orleans.Graph.Models; @@ -7,7 +8,7 @@ namespace ManagedCode.Orleans.Graph.Models; [GenerateSerializer] [Alias("MC.Call")] [DebuggerDisplay("{ToString()}")] -public class Call(Direction direction, string type, string method) +public class Call(GrainId? sourceId, GrainId? targetId, Direction direction, string type, string method) { [Id(0)] public Direction Direction { get; set; } = direction; @@ -18,8 +19,14 @@ public class Call(Direction direction, string type, string method) [Id(2)] public string Method { get; set; } = method; + [Id(3)] + public GrainId? SourceId { get; set; } = sourceId; + + [Id(5)] + public GrainId? TargetId { get; set; } = targetId; + public override string ToString() { return $"Direction: {Direction,-3} | Interface: {Interface,-20} | Method: {Method}"; } -} \ No newline at end of file +} diff --git a/ManagedCode.Orleans.Graph/Models/InCall.cs b/ManagedCode.Orleans.Graph/Models/InCall.cs index 2168ece..69448b2 100644 --- a/ManagedCode.Orleans.Graph/Models/InCall.cs +++ b/ManagedCode.Orleans.Graph/Models/InCall.cs @@ -1,11 +1,12 @@ using Orleans; +using Orleans.Runtime; namespace ManagedCode.Orleans.Graph.Models; [Immutable] [GenerateSerializer] [Alias("MC.InCall")] -public class InCall(string type, string method) : Call(Direction.In, type, method) +public class InCall(GrainId? sourceId, GrainId? targetId, string type, string method) : Call(sourceId, targetId, Direction.In, type, method) { public override string ToString() { diff --git a/ManagedCode.Orleans.Graph/Models/OutCall.cs b/ManagedCode.Orleans.Graph/Models/OutCall.cs index 43b6215..cc30566 100644 --- a/ManagedCode.Orleans.Graph/Models/OutCall.cs +++ b/ManagedCode.Orleans.Graph/Models/OutCall.cs @@ -1,11 +1,12 @@ using Orleans; +using Orleans.Runtime; namespace ManagedCode.Orleans.Graph.Models; [Immutable] [GenerateSerializer] [Alias("MC.OutCall")] -public class OutCall(string caller, string type, string method) : Call(Direction.Out, type, method) +public class OutCall(GrainId? sourceId, GrainId? targetId, string caller, string type, string method) : Call(sourceId, targetId, Direction.Out, type, method) { [Id(0)] public string Caller { get; set; } = caller; diff --git a/README.md b/README.md index bb88deb..ac147d0 100644 --- a/README.md +++ b/README.md @@ -1 +1,200 @@ -# Orleans.Graph \ No newline at end of file +![img|300x200](https://raw.githubusercontent.com/managedcode/Orleans.Graph/main/logo.png) + +Here's a good README for the Orleans Graph project: + +# Orleans Graph + +[![NuGet version](https://badge.fury.io/nu/ManagedCode.Orleans.Graph.svg)](https://www.nuget.org/packages/ManagedCode.Orleans.Graph) +[![.NET](https://github.com/managedcode/Orleans.Graph/actions/workflows/dotnet.yml/badge.svg)](https://github.com/managedcode/Orleans.Graph/actions/workflows/dotnet.yml) +[![CodeQL](https://github.com/managedcode/Orleans.Graph/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/managedcode/Orleans.Graph/actions/workflows/codeql-analysis.yml) + +A library for managing and validating grain call transitions in Microsoft Orleans applications. It allows you to define allowed communication patterns between grains and enforce them at runtime. + +## Features + +- Define allowed grain-to-grain communication patterns +- Validate grain call transitions at runtime +- Detect and prevent circular dependencies +- Support for method-level granularity +- Reentrancy control +- Client-to-grain call validation + +## Installation + +```sh +dotnet add package ManagedCode.Orleans.Graph +``` + +## Usage + +### Configure Silo + +```csharp +builder.AddOrleansGraph() + .CreateGraph(graph => +{ + graph.AddGrainTransition() + .Method(a => a.MethodA1(), b => b.MethodB1()) + .And() + .AllowClientCallGrain(); +}); +``` + +### Configure Client + +```csharp +builder.AddOrleansGraph() + .CreateGraph(graph => +{ + graph.AddGrainTransition() + .AllMethods() + .And() + .AllowClientCallGrain(); +}); +``` + +## Example +Here are more examples of how to configure the graph in your Orleans application: + +### Example 1: Simple Grain-to-Grain Transition + +```csharp +builder.AddOrleansGraph() + .CreateGraph(graph => +{ + graph.AddGrainTransition() + .AllMethods(); +}); +``` + +### Example 2: Method-Level Transition + +```csharp +builder.AddOrleansGraph() + .CreateGraph(graph => +{ + graph.AddGrainTransition() + .Method(a => a.MethodA1(), b => b.MethodB1()); +}); +``` + +### Example 3: Allowing Reentrancy + +```csharp +builder.AddOrleansGraph() + .CreateGraph(graph => +{ + graph.AddGrainTransition() + .WithReentrancy() + .AllMethods(); +}); +``` + +### Example 4: Multiple Transitions + +```csharp +builder.AddOrleansGraph() + .CreateGraph(graph => +{ + graph.AddGrainTransition() + .AllMethods() + .And() + .AddGrainTransition() + .AllMethods(); +}); +``` + +### Example 5: Client-to-Grain Call Validation + +```csharp +builder.AddOrleansGraph() + .CreateGraph(graph => +{ + graph.AllowClientCallGrain() + .And() + .AddGrainTransition() + .AllMethods(); +}); +``` + +### Example 6: Detecting and Preventing Circular Dependencies + +```csharp +builder.AddOrleansGraph() + .CreateGraph(graph => +{ + graph.AddGrainTransition() + .AllMethods() + .And() + .AddGrainTransition() + .AllMethods() + .And() + .AddGrainTransition() + .AllMethods(); +}); +``` + +### Example 7: Simple Self-Call + +```csharp +builder.AddOrleansGraph() + .CreateGraph(graph => +{ + graph.AddGrain() + .WithReentrancy(); +}); +``` + +### Example 8: Self-Call with All Methods + +```csharp +builder.AddOrleansGraph() + .CreateGraph(graph => +{ + graph.AddGrain() + .AllMethods() + .WithReentrancy(); +}); +``` + +### Example 9: Self-Call with Specific Method and Parameters + +```csharp +builder.AddOrleansGraph() + .CreateGraph(graph => +{ + graph.AddGrain() + .Method(a => a.MethodA1(GraphParam.Any())) + .WithReentrancy(); +}); +``` + +### Example 10: Self-Call with Client Call Validation + +```csharp +builder.AddOrleansGraph() + .CreateGraph(graph => +{ + graph.AddGrain() + .AllowClientCallGrain() + .WithReentrancy(); +}); +``` + +These examples demonstrate various ways to configure a grain to call itself, including method-level granularity, reentrancy, and client call validation. +## License + +This project is licensed under the MIT License. + + + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## Support + +If you have any questions or run into issues, please open an issue on the [GitHub repository](https://github.com/managedcode/Orleans.Graph). + +--- +Created and maintained by [ManagedCode](https://github.com/managedcode) \ No newline at end of file