Skip to content

Commit 5b0fb1c

Browse files
committed
fix deadlock
readme disable sonarcloud fix readme
1 parent d251498 commit 5b0fb1c

File tree

11 files changed

+351
-82
lines changed

11 files changed

+351
-82
lines changed

.github/workflows/dotnet.yml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,17 @@ jobs:
4141
- name: List coverage files
4242
run: ls '${{ github.workspace }}/coverage/'
4343

44-
- name: SonarCloud Scan
45-
uses: sonarsource/sonarcloud-github-action@master
46-
with:
47-
args: >
48-
-Dsonar.organization=managedcode
49-
-Dsonar.projectKey=managedcode_Orleans.Graph
50-
-Dsonar.token=${{ secrets.SONAR_TOKEN }}
51-
-Dsonar.cs.opencover.reportsPaths=${{ github.workspace }}/coverage/
52-
env:
53-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54-
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
44+
# - name: SonarCloud Scan
45+
# uses: sonarsource/sonarcloud-github-action@master
46+
# with:
47+
# args: >
48+
# -Dsonar.organization=managedcode
49+
# -Dsonar.projectKey=managedcode_Orleans.Graph
50+
# -Dsonar.token=${{ secrets.SONAR_TOKEN }}
51+
# -Dsonar.cs.opencover.reportsPaths=${{ github.workspace }}/coverage/
52+
# env:
53+
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54+
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
5555

5656
- name: Upload coverage reports to Codecov
5757
uses: codecov/codecov-action@v3

ManagedCode.Orleans.Graph.Tests/GrainTransitionManagerTests.cs

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ public void IsTransitionAllowed_SingleValidTransition_ReturnsTrue()
1919
.Build();
2020

2121
var callHistory = new CallHistory();
22-
callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
23-
callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
22+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
23+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
2424

2525
Assert.True(graph.IsTransitionAllowed(callHistory));
2626
}
@@ -36,8 +36,8 @@ public void IsTransitionAllowed_InvalidTransition_ReturnsFalse()
3636
.Build();
3737

3838
var callHistory = new CallHistory();
39-
callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
40-
callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
39+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
40+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
4141

4242
Assert.False(graph.IsTransitionAllowed(callHistory));
4343
}
@@ -72,8 +72,8 @@ public void IsTransitionAllowed_ReentrancyAllowed_ReturnsTrue()
7272
.Build();
7373

7474
var callHistory = new CallHistory();
75-
callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
76-
callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
75+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
76+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
7777

7878
Assert.True(graph.IsTransitionAllowed(callHistory));
7979
}
@@ -89,8 +89,8 @@ public void IsTransitionAllowed_MethodRuleAllowed_ReturnsTrue()
8989
.Build();
9090

9191
var callHistory = new CallHistory();
92-
callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
93-
callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
92+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
93+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
9494

9595
Assert.True(graph.IsTransitionAllowed(callHistory));
9696
}
@@ -105,8 +105,8 @@ public void IsTransitionAllowed_SelfLoopTransition_ReturnsTrue()
105105
.Build();
106106

107107
var callHistory = new CallHistory();
108-
callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
109-
callHistory.Push(new Call(Direction.In, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
108+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
109+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
110110

111111
Assert.True(graph.IsTransitionAllowed(callHistory));
112112
}
@@ -122,8 +122,8 @@ public void IsTransitionAllowed_DisallowedTransition_ReturnsFalse()
122122
.Build();
123123

124124
var callHistory = new CallHistory();
125-
callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
126-
callHistory.Push(new Call(Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1)));
125+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
126+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1)));
127127

128128
Assert.False(graph.IsTransitionAllowed(callHistory));
129129
}
@@ -143,10 +143,10 @@ public void IsTransitionAllowed_MultipleValidTransitions_ReturnsTrue()
143143
.Build();
144144

145145
var callHistory = new CallHistory();
146-
callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
147-
callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
148-
callHistory.Push(new Call(Direction.Out, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
149-
callHistory.Push(new Call(Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1)));
146+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
147+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
148+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
149+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1)));
150150

151151
Assert.True(graph.IsTransitionAllowed(callHistory));
152152
}
@@ -162,8 +162,8 @@ public void IsTransitionAllowed_InvalidMethodRule_ReturnsFalse()
162162
.Build();
163163

164164
var callHistory = new CallHistory();
165-
callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
166-
callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, "MethodC2"));
165+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
166+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, "MethodC2"));
167167

168168
Assert.False(graph.IsTransitionAllowed(callHistory));
169169
}
@@ -212,12 +212,12 @@ public void IsTransitionAllowed_DetectsSimpleLoop_ReturnsFalse()
212212
.Build();
213213

214214
var callHistory = new CallHistory();
215-
callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
216-
callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
217-
callHistory.Push(new Call(Direction.Out, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
218-
callHistory.Push(new Call(Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1)));
219-
callHistory.Push(new Call(Direction.Out, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1)));
220-
callHistory.Push(new Call(Direction.In, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
215+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
216+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
217+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
218+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1)));
219+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1)));
220+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
221221

222222
Assert.False(graph.IsTransitionAllowed(callHistory));
223223
}
@@ -237,10 +237,10 @@ public void IsTransitionAllowed_NoLoop_ReturnsTrue()
237237
.Build();
238238

239239
var callHistory = new CallHistory();
240-
callHistory.Push(new Call(Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
241-
callHistory.Push(new Call(Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
242-
callHistory.Push(new Call(Direction.Out, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
243-
callHistory.Push(new Call(Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1)));
240+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainA).FullName, nameof(IGrainA.MethodA1)));
241+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
242+
callHistory.Push(new Call(null, null, Direction.Out, typeof(IGrainB).FullName, nameof(IGrainB.MethodB1)));
243+
callHistory.Push(new Call(null, null, Direction.In, typeof(IGrainC).FullName, nameof(IGrainC.MethodC1)));
244244

245245
Assert.True(graph.IsTransitionAllowed(callHistory));
246246
}

ManagedCode.Orleans.Graph.Tests/GraphTests.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ await _testApp.Cluster
4949

5050
exception.Message
5151
.Should()
52-
.StartWith("Transition is not allowed.");
52+
.StartWith("Transition from ORLEANS_GRAIN_CLIENT to ManagedCode.Orleans.Graph.Tests.Cluster.Grains.Interfaces.IGrainC is not allowed.");
5353
}
5454

5555

@@ -85,15 +85,22 @@ await _testApp.Cluster
8585

8686
exception.Message
8787
.Should()
88-
.StartWith("Transition is not allowed.");
88+
.StartWith("Transition from");
8989
}
9090

9191
[Fact]
9292
public async Task DeadLock_Tests()
9393
{
94-
await _testApp.Cluster
95-
.Client
96-
.GetGrain<IGrainA>("1")
97-
.MethodB2(1);
94+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
95+
{
96+
await _testApp.Cluster
97+
.Client
98+
.GetGrain<IGrainA>("1")
99+
.MethodB2(1);
100+
});
101+
102+
exception.Message
103+
.Should()
104+
.StartWith("Deadlock detected.");
98105
}
99106
}

ManagedCode.Orleans.Graph/Extensions/RequestContextHelper.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Linq;
23
using ManagedCode.Orleans.Graph.Interfaces;
34
using ManagedCode.Orleans.Graph.Models;
@@ -12,7 +13,7 @@ public static bool TrackIncomingCall(this IIncomingGrainCallContext context)
1213
{
1314
var call = context.GetCallHistory();
1415
//var caller = context.TargetContext!.GrainInstance!.GetType().Name;
15-
call.Push(new InCall(context.InterfaceName, context.MethodName));
16+
call.Push(new InCall(context.SourceId, context.TargetId, context.InterfaceName, context.MethodName));
1617
context.SetCallHistory(call);
1718
return true;
1819
}
@@ -23,10 +24,10 @@ public static bool TrackOutgoingCall(this IOutgoingGrainCallContext context)
2324
? Constants.ClientCallerId
2425
: context.SourceContext!.GrainInstance!.GetType()
2526
.GetInterfaces()
26-
.FirstOrDefault(i => typeof(IGrain).IsAssignableFrom(i))?.FullName;
27+
.FirstOrDefault(i => typeof(IGrain).IsAssignableFrom(i))?.FullName ?? string.Empty;
2728

2829
var call = context.GetCallHistory();
29-
call.Push(new OutCall(caller, context.InterfaceName, context.MethodName));
30+
call.Push(new OutCall(context.SourceId, context.TargetId, caller, context.InterfaceName, context.MethodName));
3031
context.SetCallHistory(call);
3132
return true;
3233
}

ManagedCode.Orleans.Graph/Filters/GraphIncomingGrainCallFilter.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@ public Task Invoke(IIncomingGrainCallContext context)
1515
{
1616
if (context.TrackIncomingCall(graphCallFilterConfig))
1717
{
18-
if (GraphManager?.IsTransitionAllowed(context.GetCallHistory()) == false )
19-
{
20-
throw new InvalidOperationException("Transition is not allowed.\n" + context.GetCallHistory());
21-
}
18+
GraphManager?.IsTransitionAllowed(context.GetCallHistory(), true);
19+
GraphManager?.DetectDeadlocks(context.GetCallHistory(), true);
2220
}
2321

2422
return context.Invoke();

ManagedCode.Orleans.Graph/Filters/GraphOutgoingGrainCallFilter.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@ public Task Invoke(IOutgoingGrainCallContext context)
1515
{
1616
if (context.TrackOutgoingCall(graphCallFilterConfig))
1717
{
18-
if (GraphManager?.IsTransitionAllowed(context.GetCallHistory()) == false )
19-
{
20-
throw new InvalidOperationException("Transition is not allowed.\n" + context.GetCallHistory());
21-
}
18+
GraphManager?.IsTransitionAllowed(context.GetCallHistory(), true);
19+
GraphManager?.DetectDeadlocks(context.GetCallHistory(), true);
2220
}
2321

2422
return context.Invoke();
Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Linq;
43
using ManagedCode.Orleans.Graph.Models;
4+
using Orleans.Runtime;
55

66
namespace ManagedCode.Orleans.Graph;
77

@@ -14,13 +14,15 @@ public GrainTransitionManager(DirectedGraph grainGraph)
1414
_grainGraph = grainGraph ?? throw new ArgumentNullException(nameof(grainGraph));
1515
}
1616

17-
public bool IsTransitionAllowed(CallHistory callHistory)
17+
public bool IsTransitionAllowed(CallHistory callHistory, bool throwOnViolation = false)
1818
{
1919
if (callHistory.IsEmpty())
20+
{
2021
return false;
22+
}
2123

2224
var calls = callHistory.History.ToArray();
23-
var visited = new HashSet<string>();
25+
2426

2527
for (var i = 0; i < calls.Length - 1; i++)
2628
{
@@ -30,41 +32,98 @@ public bool IsTransitionAllowed(CallHistory callHistory)
3032
// Only consider transitions from Out to In
3133
if (currentCall.Direction == Direction.Out && nextCall.Direction == Direction.In)
3234
{
33-
string transitionKey;
34-
if (currentCall is OutCall outCall)
35-
{
36-
transitionKey = $"{outCall.Caller}->{nextCall.Interface}";
37-
}
38-
else
39-
{
40-
transitionKey = $"{currentCall.Interface}->{nextCall.Interface}";
41-
}
42-
43-
// Check for cycles
44-
if (visited.Contains(transitionKey))
45-
{
46-
return false;
47-
}
48-
visited.Add(transitionKey);
49-
5035
// Check if the transition is allowed
5136
if (currentCall is OutCall outCallTransition)
5237
{
5338
if (!_grainGraph.IsTransitionAllowed(outCallTransition.Caller, nextCall.Interface, currentCall.Method, nextCall.Method))
5439
{
40+
if(throwOnViolation)
41+
{
42+
throw new InvalidOperationException($"Transition from {outCallTransition.Caller} to {nextCall.Interface} is not allowed.");
43+
}
5544
return false;
5645
}
5746
}
5847
else
5948
{
6049
if (!_grainGraph.IsTransitionAllowed(currentCall.Interface, nextCall.Interface, currentCall.Method, nextCall.Method))
6150
{
51+
if(throwOnViolation)
52+
{
53+
throw new InvalidOperationException($"Transition from {currentCall.Interface} to {nextCall.Interface} is not allowed.");
54+
}
6255
return false;
6356
}
6457
}
6558
}
6659
}
67-
60+
6861
return true;
6962
}
63+
64+
public bool DetectDeadlocks(CallHistory callHistory, bool throwOnViolation = false)
65+
{
66+
var graph = new Dictionary<GrainId, List<GrainId>>();
67+
68+
foreach (var call in callHistory.History)
69+
{
70+
if (call.SourceId.HasValue && call.TargetId.HasValue)
71+
{
72+
if (!graph.ContainsKey(call.SourceId.Value))
73+
{
74+
graph[call.SourceId.Value] = new List<GrainId>();
75+
}
76+
77+
graph[call.SourceId.Value].Add(call.TargetId.Value);
78+
}
79+
}
80+
81+
var visited = new HashSet<GrainId>();
82+
var stack = new HashSet<GrainId>();
83+
84+
foreach (var node in graph.Keys)
85+
{
86+
if (IsCyclic(node, graph, visited, stack))
87+
{
88+
if(throwOnViolation)
89+
{
90+
throw new InvalidOperationException($"Deadlock detected. GrainId: {node}");
91+
}
92+
93+
return true;
94+
}
95+
}
96+
97+
return false;
98+
}
99+
100+
private bool IsCyclic(GrainId node, Dictionary<GrainId, List<GrainId>> graph, HashSet<GrainId> visited, HashSet<GrainId> stack)
101+
{
102+
if (stack.Contains(node))
103+
{
104+
return true;
105+
}
106+
107+
if (visited.Contains(node))
108+
{
109+
return false;
110+
}
111+
112+
visited.Add(node);
113+
stack.Add(node);
114+
115+
if (graph.ContainsKey(node))
116+
{
117+
foreach (var neighbor in graph[node])
118+
{
119+
if (IsCyclic(neighbor, graph, visited, stack))
120+
{
121+
return true;
122+
}
123+
}
124+
}
125+
126+
stack.Remove(node);
127+
return false;
128+
}
70129
}

0 commit comments

Comments
 (0)