Skip to content

Commit 50835ca

Browse files
committed
Synced with remote master. Merge branch 'rubberduck-vba/next' into next
2 parents 53222bd + 0dae8e5 commit 50835ca

File tree

1 file changed

+145
-98
lines changed

1 file changed

+145
-98
lines changed

Rubberduck.Parsing/VBA/ParseCoordinator.cs

Lines changed: 145 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public class ParseCoordinator : IParseCoordinator
3131
private const int _maxDegreeOfDeclarationResolverParallelism = -1;
3232
private const int _maxDegreeOfReferenceResolverParallelism = -1;
3333
private const int _maxDegreeOfModuleStateChangeParallelism = -1;
34+
private const int _maxReferenceLoadingConcurrency = -1;
3435

3536
private readonly IDictionary<IVBComponent, IDictionary<Tuple<string, DeclarationType>, Attributes>> _componentAttributes
3637
= new Dictionary<IVBComponent, IDictionary<Tuple<string, DeclarationType>, Attributes>>();
@@ -721,142 +722,188 @@ private string GetReferenceProjectId(IReference reference, IReadOnlyList<IVBProj
721722

722723
private void SyncComReferences(IReadOnlyList<IVBProject> projects, CancellationToken token)
723724
{
725+
var unmapped = new ConcurrentBag<IReference>();
726+
727+
var referencesToLoad = GetReferencesToLoadAndSaveReferencePriority(projects);
728+
729+
State.OnStatusMessageUpdate(ParserState.LoadingReference.ToString());
730+
731+
var referenceLoadingTaskScheduler = ThrottelingTaskScheduler(_maxReferenceLoadingConcurrency);
732+
733+
//Parallel.ForEach is not used because loading the references can contain IO-bound operations.
724734
var loadTasks = new List<Task>();
725-
var unmapped = new List<IReference>();
735+
foreach(var reference in referencesToLoad)
736+
{
737+
var localReference = reference;
738+
loadTasks.Add(Task.Factory.StartNew(
739+
() => LoadReference(localReference, unmapped),
740+
token,
741+
TaskCreationOptions.None,
742+
referenceLoadingTaskScheduler
743+
));
744+
}
745+
746+
var notMappedReferences = NonMappedReferences(projects);
747+
foreach (var item in notMappedReferences)
748+
{
749+
unmapped.Add(item);
750+
}
751+
752+
try
753+
{
754+
Task.WaitAll(loadTasks.ToArray(), token);
755+
}
756+
catch (AggregateException exception)
757+
{
758+
if (exception.Flatten().InnerExceptions.All(ex => ex is OperationCanceledException))
759+
{
760+
throw exception.InnerException; //This eliminates the stack trace, but for the cancellation, this is irrelevant.
761+
}
762+
State.SetStatusAndFireStateChanged(this, ParserState.Error);
763+
throw;
764+
}
765+
token.ThrowIfCancellationRequested();
766+
767+
foreach (var reference in unmapped)
768+
{
769+
UnloadComReference(reference, projects);
770+
}
771+
}
772+
773+
private List<IReference> GetReferencesToLoadAndSaveReferencePriority(IReadOnlyList<IVBProject> projects)
774+
{
775+
var referencesToLoad = new List<IReference>();
726776

727777
foreach (var vbProject in projects)
728778
{
729779
var projectId = QualifiedModuleName.GetProjectId(vbProject);
730780
var references = vbProject.References;
781+
782+
// use a 'for' loop to store the order of references as a 'priority'.
783+
// reference resolver needs this to know which declaration to prioritize when a global identifier exists in multiple libraries.
784+
for (var priority = 1; priority <= references.Count; priority++)
731785
{
732-
// use a 'for' loop to store the order of references as a 'priority'.
733-
// reference resolver needs this to know which declaration to prioritize when a global identifier exists in multiple libraries.
734-
for (var priority = 1; priority <= references.Count; priority++)
786+
var reference = references[priority];
787+
if (reference.IsBroken)
735788
{
736-
var reference = references[priority];
737-
if (reference.IsBroken)
738-
{
739-
continue;
740-
}
741-
742-
// skip loading Rubberduck.tlb (GUID is defined in AssemblyInfo.cs)
743-
if (reference.Guid == "{E07C841C-14B4-4890-83E9-8C80B06DD59D}")
744-
{
745-
// todo: figure out why Rubberduck.tlb *sometimes* throws
746-
//continue;
747-
}
748-
var referencedProjectId = GetReferenceProjectId(reference, projects);
749-
750-
ReferencePriorityMap map = null;
751-
foreach (var item in _projectReferences)
752-
{
753-
if (item.ReferencedProjectId == referencedProjectId)
754-
{
755-
map = map != null ? null : item;
756-
}
757-
}
758-
759-
if (map == null)
760-
{
761-
map = new ReferencePriorityMap(referencedProjectId) { { projectId, priority } };
762-
_projectReferences.Add(map);
763-
}
764-
else
765-
{
766-
map[projectId] = priority;
767-
}
768-
769-
if (!map.IsLoaded)
770-
{
771-
State.OnStatusMessageUpdate(ParserState.LoadingReference.ToString());
772-
773-
var localReference = reference;
774-
775-
loadTasks.Add(
776-
Task.Run(() =>
777-
{
778-
try
779-
{
780-
Logger.Trace(string.Format("Loading referenced type '{0}'.", localReference.Name));
781-
782-
var comReflector = new ReferencedDeclarationsCollector(State, localReference, _serializedDeclarationsPath);
783-
if (comReflector.SerializedVersionExists)
784-
{
785-
Logger.Trace(string.Format("Deserializing reference '{0}'.", localReference.Name));
786-
foreach (var declaration in comReflector.LoadDeclarationsFromXml())
787-
{
788-
State.AddDeclaration(declaration);
789-
}
790-
}
791-
else
792-
{
793-
Logger.Trace(string.Format("COM reflecting reference '{0}'.", localReference.Name));
794-
foreach (var declaration in comReflector.LoadDeclarationsFromLibrary())
795-
{
796-
State.AddDeclaration(declaration);
797-
}
798-
}
799-
}
800-
catch (Exception exception)
801-
{
802-
unmapped.Add(reference);
803-
Logger.Warn(string.Format("Types were not loaded from referenced type library '{0}'.", reference.Name));
804-
Logger.Error(exception);
805-
}
806-
}));
807-
map.IsLoaded = true;
808-
}
789+
continue;
790+
}
791+
792+
// skip loading Rubberduck.tlb (GUID is defined in AssemblyInfo.cs)
793+
if (reference.Guid == "{E07C841C-14B4-4890-83E9-8C80B06DD59D}")
794+
{
795+
// todo: figure out why Rubberduck.tlb *sometimes* throws
796+
//continue;
797+
}
798+
var referencedProjectId = GetReferenceProjectId(reference, projects);
799+
800+
var map = _projectReferences.FirstOrDefault(item => item.ReferencedProjectId == referencedProjectId);
801+
802+
if (map == null)
803+
{
804+
map = new ReferencePriorityMap(referencedProjectId) { { projectId, priority } };
805+
_projectReferences.Add(map);
806+
}
807+
else
808+
{
809+
map[projectId] = priority;
810+
}
811+
812+
if (!map.IsLoaded)
813+
{
814+
referencesToLoad.Add(reference);
815+
map.IsLoaded = true;
809816
}
810817
}
811818
}
819+
return referencesToLoad;
820+
}
812821

813-
var mappedIds = new List<string>();
814-
foreach (var item in _projectReferences)
822+
private TaskScheduler ThrottelingTaskScheduler(int maxLevelOfConcurrency)
823+
{
824+
if (maxLevelOfConcurrency <= 0)
815825
{
816-
mappedIds.Add(item.ReferencedProjectId);
826+
return TaskScheduler.Default;
817827
}
828+
else
829+
{
830+
var taskSchedulerPair = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, maxLevelOfConcurrency);
831+
return taskSchedulerPair.ConcurrentScheduler;
832+
}
833+
}
818834

819-
foreach (var project in projects)
835+
private void LoadReference(IReference localReference, ConcurrentBag<IReference> unmapped)
836+
{
837+
Logger.Trace(string.Format("Loading referenced type '{0}'.", localReference.Name));
838+
var comReflector = new ReferencedDeclarationsCollector(State, localReference, _serializedDeclarationsPath);
839+
try
820840
{
821-
var references = project.References;
841+
if (comReflector.SerializedVersionExists)
822842
{
823-
foreach (var item in references)
824-
{
825-
if (!mappedIds.Contains(GetReferenceProjectId(item, projects)))
826-
{
827-
unmapped.Add(item);
828-
}
829-
}
843+
LoadReferenceByDeserialization(localReference, comReflector);
844+
}
845+
else
846+
{
847+
LoadReferenceByCOMReflection(localReference, comReflector);
830848
}
831849
}
850+
catch (Exception exception)
851+
{
852+
unmapped.Add(localReference);
853+
Logger.Warn(string.Format("Types were not loaded from referenced type library '{0}'.", localReference.Name));
854+
Logger.Error(exception);
855+
}
856+
}
832857

833-
Task.WaitAll(loadTasks.ToArray(), token);
858+
private void LoadReferenceByDeserialization(IReference localReference, ReferencedDeclarationsCollector comReflector)
859+
{
860+
Logger.Trace(string.Format("Deserializing reference '{0}'.", localReference.Name));
861+
var declarations = comReflector.LoadDeclarationsFromXml();
862+
foreach (var declaration in declarations)
863+
{
864+
State.AddDeclaration(declaration);
865+
}
866+
}
834867

835-
foreach (var reference in unmapped)
868+
private void LoadReferenceByCOMReflection(IReference localReference, ReferencedDeclarationsCollector comReflector)
869+
{
870+
Logger.Trace(string.Format("COM reflecting reference '{0}'.", localReference.Name));
871+
var declarations = comReflector.LoadDeclarationsFromLibrary();
872+
foreach (var declaration in declarations)
836873
{
837-
UnloadComReference(reference, projects);
874+
State.AddDeclaration(declaration);
838875
}
839876
}
877+
878+
private List<IReference> NonMappedReferences(IReadOnlyList<IVBProject> projects)
879+
{
880+
var mappedIds = _projectReferences.Select(item => item.ReferencedProjectId).ToHashSet();
881+
var references = projects.SelectMany(project => project.References);
882+
return references.Where(item => !mappedIds.Contains(GetReferenceProjectId(item, projects))).ToList();
883+
}
840884

841885
private void UnloadComReference(IReference reference, IReadOnlyList<IVBProject> projects)
842886
{
843887
var referencedProjectId = GetReferenceProjectId(reference, projects);
844888

845889
ReferencePriorityMap map = null;
846-
foreach (var item in _projectReferences)
890+
try
847891
{
848-
if (item.ReferencedProjectId == referencedProjectId)
849-
{
850-
map = map != null ? null : item;
851-
}
892+
map = _projectReferences.SingleOrDefault(item => item.ReferencedProjectId == referencedProjectId);
893+
}
894+
catch (InvalidOperationException exception)
895+
{
896+
//There are multiple maps with the same referencedProjectId. That should not happen. (ghost?).
897+
Logger.Error(exception, "Failed To unload com reference with referencedProjectID {0} because RD stores multiple instances of it.", referencedProjectId);
898+
return;
852899
}
853900

854901
if (map == null || !map.IsLoaded)
855902
{
856903
// we're removing a reference we weren't tracking? ...this shouldn't happen.
857-
//Debug.Assert(false);
858904
return;
859905
}
906+
860907
map.Remove(referencedProjectId);
861908
if (map.Count == 0)
862909
{

0 commit comments

Comments
 (0)