Skip to content

Commit

Permalink
readme
Browse files Browse the repository at this point in the history
  • Loading branch information
KSemenenko committed Nov 20, 2024
1 parent 2e2b1e7 commit b1edd84
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 69 deletions.
56 changes: 28 additions & 28 deletions ManagedCode.Orleans.Graph.Tests/GrainTransitionManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)));

Check warning on line 22 in ManagedCode.Orleans.Graph.Tests/GrainTransitionManagerTests.cs

View workflow job for this annotation

GitHub Actions / nuget-pack

Possible null reference argument for parameter 'type' in 'Call.Call(GrainId? sourceId, GrainId? targetId, Direction direction, string type, string method)'.

Check warning on line 22 in ManagedCode.Orleans.Graph.Tests/GrainTransitionManagerTests.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'type' in 'Call.Call(GrainId? sourceId, GrainId? targetId, Direction direction, string type, string method)'.
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));

Check warning on line 23 in ManagedCode.Orleans.Graph.Tests/GrainTransitionManagerTests.cs

View workflow job for this annotation

GitHub Actions / nuget-pack

Possible null reference argument for parameter 'type' in 'Call.Call(GrainId? sourceId, GrainId? targetId, Direction direction, string type, string method)'.

Check warning on line 23 in ManagedCode.Orleans.Graph.Tests/GrainTransitionManagerTests.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'type' in 'Call.Call(GrainId? sourceId, GrainId? targetId, Direction direction, string type, string method)'.

Assert.True(graph.IsTransitionAllowed(callHistory));
}
Expand All @@ -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));
}
Expand Down Expand Up @@ -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));
}
Expand All @@ -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));
}
Expand All @@ -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));
}
Expand All @@ -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));
}
Expand All @@ -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));
}
Expand All @@ -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));
}
Expand Down Expand Up @@ -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));
}
Expand All @@ -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));
}
Expand Down
6 changes: 3 additions & 3 deletions ManagedCode.Orleans.Graph.Tests/GraphTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}


Expand Down Expand Up @@ -85,7 +85,7 @@ await _testApp.Cluster

exception.Message
.Should()
.StartWith("Transition is not allowed.");
.StartWith("Transition from");
}

[Fact]
Expand All @@ -101,6 +101,6 @@ await _testApp.Cluster

exception.Message
.Should()
.StartWith("Transition is not allowed.");
.StartWith("Deadlock detected.");
}
}
7 changes: 4 additions & 3 deletions ManagedCode.Orleans.Graph/Extensions/RequestContextHelper.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Linq;
using ManagedCode.Orleans.Graph.Interfaces;
using ManagedCode.Orleans.Graph.Models;
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
48 changes: 26 additions & 22 deletions ManagedCode.Orleans.Graph/GrainTransitionManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using ManagedCode.Orleans.Graph.Models;
using Orleans.Runtime;

namespace ManagedCode.Orleans.Graph;

Expand All @@ -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())
{
Expand All @@ -36,64 +37,67 @@ 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;
}
}
else
{
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<string, List<string>>();
var graph = new Dictionary<GrainId, List<GrainId>>();

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<string>();
graph[call.SourceId.Value] = new List<GrainId>();
}

graph[outCall.Caller]
.Add(call.Interface);
graph[call.SourceId.Value].Add(call.TargetId.Value);
}
}


var visited = new HashSet<string>();
var stack = new HashSet<string>();
var visited = new HashSet<GrainId>();
var stack = new HashSet<GrainId>();

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<string, List<string>> graph, HashSet<string> visited, HashSet<string> stack)
private bool IsCyclic(GrainId node, Dictionary<GrainId, List<GrainId>> graph, HashSet<GrainId> visited, HashSet<GrainId> stack)
{
if (stack.Contains(node))
{
Expand Down
11 changes: 9 additions & 2 deletions ManagedCode.Orleans.Graph/Models/Call.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System.Diagnostics;
using Orleans;
using Orleans.Runtime;

namespace ManagedCode.Orleans.Graph.Models;

[Immutable]
[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;
Expand All @@ -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}";
}
}
}
3 changes: 2 additions & 1 deletion ManagedCode.Orleans.Graph/Models/InCall.cs
Original file line number Diff line number Diff line change
@@ -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()
{
Expand Down
3 changes: 2 additions & 1 deletion ManagedCode.Orleans.Graph/Models/OutCall.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Loading

0 comments on commit b1edd84

Please sign in to comment.