Skip to content

Commit e51187b

Browse files
authored
Merge pull request #2607 from comintern/next
Add more thread safety to DeclarationFinder.
2 parents 265b67e + 84e1ff4 commit e51187b

File tree

1 file changed

+65
-47
lines changed

1 file changed

+65
-47
lines changed

Rubberduck.Parsing/Symbols/DeclarationFinder.cs

Lines changed: 65 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace Rubberduck.Parsing.Symbols
1616
internal static class DictionaryExtensions
1717
{
1818
public static IEnumerable<TValue> AllValues<TKey, TValue>(
19-
this IDictionary<TKey, TValue[]> source)
19+
this ConcurrentDictionary<TKey, ConcurrentBag<TValue>> source)
2020
{
2121
return source.SelectMany(item => item.Value).ToList();
2222
}
@@ -26,6 +26,11 @@ public static IEnumerable<TValue> AllValues<TKey, TValue>(
2626
{
2727
return source.SelectMany(item => item.Value).ToList();
2828
}
29+
30+
public static ConcurrentDictionary<TKey, ConcurrentBag<TValue>> ToConcurrentDictionary<TKey, TValue>(this IEnumerable<IGrouping<TKey, TValue>> source)
31+
{
32+
return new ConcurrentDictionary<TKey, ConcurrentBag<TValue>>(source.Select(x => new KeyValuePair<TKey, ConcurrentBag<TValue>>(x.Key, new ConcurrentBag<TValue>(x))));
33+
}
2934
}
3035

3136
public class DeclarationFinder
@@ -35,14 +40,14 @@ public class DeclarationFinder
3540

3641
private readonly IHostApplication _hostApp;
3742
private readonly AnnotationService _annotationService;
38-
private readonly ConcurrentDictionary<string, Declaration[]> _declarationsByName;
39-
private readonly ConcurrentDictionary<QualifiedModuleName, Declaration[]> _declarations;
40-
private readonly ConcurrentDictionary<QualifiedMemberName, IList<Declaration>> _undeclared;
43+
private readonly ConcurrentDictionary<string, ConcurrentBag<Declaration>> _declarationsByName;
44+
private readonly ConcurrentDictionary<QualifiedModuleName, ConcurrentBag<Declaration>> _declarations;
45+
private readonly ConcurrentDictionary<QualifiedMemberName, ConcurrentBag<Declaration>> _undeclared;
4146
private readonly ConcurrentBag<UnboundMemberDeclaration> _unresolved;
42-
private readonly ConcurrentDictionary<QualifiedModuleName, IAnnotation[]> _annotations;
43-
private readonly ConcurrentDictionary<Declaration, Declaration[]> _parametersByParent;
44-
private readonly ConcurrentDictionary<DeclarationType, Declaration[]> _userDeclarationsByType;
45-
47+
private readonly ConcurrentDictionary<QualifiedModuleName, ConcurrentBag<IAnnotation>> _annotations;
48+
private readonly ConcurrentDictionary<Declaration, ConcurrentBag<Declaration>> _parametersByParent;
49+
private readonly ConcurrentDictionary<DeclarationType, ConcurrentBag<Declaration>> _userDeclarationsByType;
50+
4651
private readonly Lazy<ConcurrentDictionary<Declaration, Declaration[]>> _handlersByWithEventsField;
4752
private readonly Lazy<ConcurrentDictionary<VBAParser.ImplementsStmtContext, Declaration[]>> _membersByImplementsContext;
4853
private readonly Lazy<ConcurrentDictionary<Declaration, Declaration[]>> _interfaceMembers;
@@ -52,26 +57,16 @@ public class DeclarationFinder
5257
public DeclarationFinder(IReadOnlyList<Declaration> declarations, IEnumerable<IAnnotation> annotations, IHostApplication hostApp = null)
5358
{
5459
_hostApp = hostApp;
55-
_annotations = new ConcurrentDictionary<QualifiedModuleName, IAnnotation[]>(annotations.GroupBy(node => node.QualifiedSelection.QualifiedName)
56-
.ToDictionary(grouping => grouping.Key, grouping => grouping.ToArray()));
57-
_declarations = new ConcurrentDictionary<QualifiedModuleName, Declaration[]>(declarations.GroupBy(item => item.QualifiedName.QualifiedModuleName)
58-
.ToDictionary(grouping => grouping.Key, grouping => grouping.ToArray()));
59-
60-
_declarationsByName = new ConcurrentDictionary<string, Declaration[]>(
61-
declarations.GroupBy(declaration => new { IdentifierName = declaration.IdentifierName.ToLowerInvariant() })
62-
.ToDictionary(grouping => grouping.Key.IdentifierName, grouping => grouping.ToArray(), NameComparer));
63-
_parametersByParent = new ConcurrentDictionary<Declaration, Declaration[]>(
64-
declarations.Where(declaration => declaration.DeclarationType == DeclarationType.Parameter)
65-
.GroupBy(declaration => declaration.ParentDeclaration)
66-
.ToDictionary(grouping => grouping.Key, grouping => grouping.ToArray()));
67-
_userDeclarationsByType = new ConcurrentDictionary<DeclarationType, Declaration[]>(
68-
declarations.Where(declaration => !declaration.IsBuiltIn)
69-
.GroupBy(declaration => declaration.DeclarationType)
70-
.ToDictionary(grouping => grouping.Key, grouping => grouping.ToArray()));
60+
_annotations = annotations.GroupBy(node => node.QualifiedSelection.QualifiedName).ToConcurrentDictionary();
61+
_declarations = declarations.GroupBy(item => item.QualifiedName.QualifiedModuleName).ToConcurrentDictionary();
62+
_declarationsByName = declarations.GroupBy(declaration => declaration.IdentifierName.ToLowerInvariant()).ToConcurrentDictionary();
63+
_parametersByParent = declarations.Where(declaration => declaration.DeclarationType == DeclarationType.Parameter)
64+
.GroupBy(declaration => declaration.ParentDeclaration).ToConcurrentDictionary();
65+
_userDeclarationsByType = declarations.Where(declaration => !declaration.IsBuiltIn).GroupBy(declaration => declaration.DeclarationType).ToConcurrentDictionary();
7166
_builtinEvents = new Lazy<ConcurrentBag<Declaration>>(() => FindBuiltInEventHandlers(declarations));
7267

73-
_projects = _projects = declarations.Where(d => d.DeclarationType == DeclarationType.Project).ToList();
74-
_classes = _declarations.AllValues().Where(d => d.DeclarationType == DeclarationType.ClassModule).ToList();
68+
_projects = _projects = new Lazy<ConcurrentBag<Declaration>>(() => new ConcurrentBag<Declaration>(declarations.Where(d => d.DeclarationType == DeclarationType.Project)));
69+
_classes = new Lazy<ConcurrentBag<Declaration>>(() => new ConcurrentBag<Declaration>(declarations.Where(d => d.DeclarationType == DeclarationType.ClassModule)));
7570

7671
var withEventsFields = UserDeclarations(DeclarationType.Variable).Where(item => item.IsWithEvents).ToArray();
7772
var events = withEventsFields.Select(field =>
@@ -95,7 +90,7 @@ public DeclarationFinder(IReadOnlyList<Declaration> declarations, IEnumerable<IA
9590
.ToDictionary(item => item.WithEventsField, item => item.Handlers.ToArray())
9691
));
9792

98-
_undeclared = new ConcurrentDictionary<QualifiedMemberName, IList<Declaration>>(new Dictionary<QualifiedMemberName, IList<Declaration>>());
93+
_undeclared = new ConcurrentDictionary<QualifiedMemberName, ConcurrentBag<Declaration>>(new Dictionary<QualifiedMemberName, ConcurrentBag<Declaration>>());
9994
_unresolved = new ConcurrentBag<UnboundMemberDeclaration>(new List<UnboundMemberDeclaration>());
10095
_annotationService = new AnnotationService(this);
10196

@@ -111,7 +106,7 @@ public DeclarationFinder(IReadOnlyList<Declaration> declarations, IEnumerable<IA
111106
});
112107

113108
_interfaceMembers = new Lazy<ConcurrentDictionary<Declaration, Declaration[]>>(() =>
114-
new ConcurrentDictionary<Declaration, Declaration[]>(interfaceMembers.ToDictionary(item => item.InterfaceModule, item => item.InterfaceMembers.ToArray())));
109+
new ConcurrentDictionary<Declaration, Declaration[]>(interfaceMembers.ToDictionary(item => item.InterfaceModule, item => item.InterfaceMembers.ToArray())));
115110

116111
var implementingNames = new Lazy<IEnumerable<string>>(() => implementsInstructions.SelectMany(item =>
117112
_declarations[item.IdentifierReference.Declaration.QualifiedName.QualifiedModuleName]
@@ -158,21 +153,40 @@ public IEnumerable<Declaration> FindBuiltinEventHandlers()
158153
}
159154
}
160155

161-
private readonly IEnumerable<Declaration> _classes;
162-
public IEnumerable<Declaration> Classes { get { return _classes; } }
156+
private readonly Lazy<ConcurrentBag<Declaration>> _classes;
157+
158+
public IEnumerable<Declaration> Classes
159+
{
160+
get
161+
{
162+
lock (ThreadLock)
163+
{
164+
return _classes.Value;
165+
}
166+
}
167+
}
168+
169+
private readonly Lazy<ConcurrentBag<Declaration>> _projects;
163170

164-
private readonly IEnumerable<Declaration> _projects;
165-
public IEnumerable<Declaration> Projects { get { return _projects; } }
171+
public IEnumerable<Declaration> Projects
172+
{
173+
get
174+
{
175+
lock (ThreadLock)
176+
{
177+
return _projects.Value;
178+
}
179+
}
180+
}
166181

167182
public IEnumerable<Declaration> UserDeclarations(DeclarationType type)
168183
{
169-
Declaration[] result;
184+
ConcurrentBag<Declaration> result;
170185
if (!_userDeclarationsByType.TryGetValue(type, out result))
171186
{
172-
result = _userDeclarationsByType
187+
result = new ConcurrentBag<Declaration>(_userDeclarationsByType
173188
.Where(item => item.Key.HasFlag(type))
174-
.SelectMany(item => item.Value)
175-
.ToArray();
189+
.SelectMany(item => item.Value));
176190
}
177191
return result;
178192
}
@@ -218,7 +232,7 @@ public Declaration FindParameter(Declaration procedure, string parameterName)
218232

219233
public IEnumerable<Declaration> FindMemberMatches(Declaration parent, string memberName)
220234
{
221-
Declaration[] children;
235+
ConcurrentBag<Declaration> children;
222236
if (_declarations.TryGetValue(parent.QualifiedName.QualifiedModuleName, out children))
223237
{
224238
return children.Where(item => item.DeclarationType.HasFlag(DeclarationType.Member)
@@ -230,7 +244,7 @@ public IEnumerable<Declaration> FindMemberMatches(Declaration parent, string mem
230244

231245
public IEnumerable<IAnnotation> FindAnnotations(QualifiedModuleName module)
232246
{
233-
IAnnotation[] result;
247+
ConcurrentBag<IAnnotation> result;
234248
return _annotations.TryGetValue(module, out result) ? result : Enumerable.Empty<IAnnotation>();
235249
}
236250

@@ -264,7 +278,7 @@ public Declaration FindLabel(Declaration procedure, string label)
264278
public IEnumerable<Declaration> MatchName(string name)
265279
{
266280
var normalizedName = ToNormalizedName(name);
267-
Declaration[] result;
281+
ConcurrentBag<Declaration> result;
268282
return _declarationsByName.TryGetValue(normalizedName, out result)
269283
? result
270284
: Enumerable.Empty<Declaration>();
@@ -305,7 +319,7 @@ public Declaration FindStdModule(string name, Declaration parent = null, bool in
305319
{
306320
var matches = MatchName(name);
307321
result = matches.SingleOrDefault(declaration => declaration.DeclarationType.HasFlag(DeclarationType.ProceduralModule)
308-
&& (parent == null || parent.Equals(declaration.ParentDeclaration))
322+
&& (parent.Equals(declaration.ParentDeclaration))
309323
&& (includeBuiltIn || !declaration.IsBuiltIn));
310324
}
311325
catch (InvalidOperationException exception)
@@ -324,7 +338,7 @@ public Declaration FindClassModule(string name, Declaration parent = null, bool
324338
{
325339
var matches = MatchName(name);
326340
result = matches.SingleOrDefault(declaration => declaration.DeclarationType.HasFlag(DeclarationType.ClassModule)
327-
&& (parent == null || parent.Equals(declaration.ParentDeclaration))
341+
&& (parent.Equals(declaration.ParentDeclaration))
328342
&& (includeBuiltIn || !declaration.IsBuiltIn));
329343
}
330344
catch (InvalidOperationException exception)
@@ -481,18 +495,22 @@ public Declaration OnUndeclaredVariable(Declaration enclosingProcedure, string i
481495
var hasUndeclared = _undeclared.ContainsKey(enclosingProcedure.QualifiedName);
482496
if (hasUndeclared)
483497
{
484-
var inScopeUndeclared = _undeclared[enclosingProcedure.QualifiedName].FirstOrDefault(d => d.IdentifierName == identifierName);
498+
ConcurrentBag<Declaration> undeclared;
499+
while (!_undeclared.TryGetValue(enclosingProcedure.QualifiedName, out undeclared))
500+
{
501+
_undeclared.TryGetValue(enclosingProcedure.QualifiedName, out undeclared);
502+
}
503+
var inScopeUndeclared = undeclared.FirstOrDefault(d => d.IdentifierName == identifierName);
485504
if (inScopeUndeclared != null)
486505
{
487506
return inScopeUndeclared;
488507
}
489-
_undeclared[enclosingProcedure.QualifiedName].Add(undeclaredLocal);
508+
undeclared.Add(undeclaredLocal);
490509
}
491510
else
492511
{
493-
_undeclared[enclosingProcedure.QualifiedName] = new List<Declaration> { undeclaredLocal };
512+
_undeclared.TryAdd(enclosingProcedure.QualifiedName, new ConcurrentBag<Declaration> { undeclaredLocal });
494513
}
495-
496514
return undeclaredLocal;
497515
}
498516

@@ -524,14 +542,14 @@ public Declaration OnBracketedExpression(string expression, ParserRuleContext co
524542

525543
var qualifiedName = hostApp.QualifiedName.QualifiedModuleName.QualifyMemberName(expression);
526544

527-
IList<Declaration> undeclared;
545+
ConcurrentBag<Declaration> undeclared;
528546
if (_undeclared.TryGetValue(qualifiedName, out undeclared))
529547
{
530548
return undeclared.SingleOrDefault();
531549
}
532550

533551
var item = new Declaration(qualifiedName, hostApp, hostApp, Tokens.Variant, string.Empty, false, false, Accessibility.Global, DeclarationType.BracketedExpression, context, context.GetSelection(), false, null);
534-
_undeclared.TryAdd(qualifiedName, new List<Declaration> { item });
552+
_undeclared.TryAdd(qualifiedName, new ConcurrentBag<Declaration> { item });
535553
return item;
536554
}
537555

0 commit comments

Comments
 (0)