Skip to content

Commit a8d800e

Browse files
authored
Merge pull request #1933 from paulvanbrenk/removeNodeCrash
Remove node crash
2 parents 95c1ed1 + 88c4bc4 commit a8d800e

File tree

6 files changed

+51
-71
lines changed

6 files changed

+51
-71
lines changed

Common/Product/SharedProject/CommonUtils.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,8 @@ public static string EnsureEndSeparator(string path)
550550
{
551551
if (string.IsNullOrEmpty(path))
552552
{
553-
return string.Empty;
553+
Debug.Fail("what??");
554+
return path;
554555
}
555556
else if (!HasEndSeparator(path))
556557
{

Nodejs/Product/Nodejs/SharedProject/CommonProjectNode.DiskMerger.cs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public async Task<bool> ContinueMergeAsync(bool hierarchyCreated)
5959
private async Task<bool> ContinueMergeAsyncWorker((string Name, HierarchyNode Parent) dir, bool hierarchyCreated)
6060
{
6161
var wasExpanded = hierarchyCreated ? dir.Parent.GetIsExpanded() : false;
62-
var missingChildren = new HashSet<HierarchyNode>(dir.Parent.AllChildren);
62+
var missingOnDisk = new HashSet<HierarchyNode>(dir.Parent.AllChildren);
6363

6464
var thread = this.project.Site.GetUIThread();
6565

@@ -92,15 +92,9 @@ private async Task<bool> ContinueMergeAsyncWorker((string Name, HierarchyNode Pa
9292
this.project.CreateSymLinkWatcher(curDir);
9393
}
9494

95-
var existing = this.project.FindNodeByFullPath(curDir);
96-
if (existing == null)
97-
{
98-
existing = this.project.AddAllFilesFolder(dir.Parent, curDir + Path.DirectorySeparatorChar, hierarchyCreated);
99-
}
100-
else
101-
{
102-
missingChildren.Remove(existing);
103-
}
95+
var existing = this.project.AddAllFilesFolder(dir.Parent, curDir, hierarchyCreated);
96+
missingOnDisk.Remove(existing);
97+
10498
this.remainingDirs.Push((curDir, existing));
10599
}
106100
}
@@ -136,7 +130,7 @@ private async Task<bool> ContinueMergeAsyncWorker((string Name, HierarchyNode Pa
136130
}
137131
else
138132
{
139-
missingChildren.Remove(existing);
133+
missingOnDisk.Remove(existing);
140134
}
141135
}
142136

@@ -156,7 +150,7 @@ private async Task<bool> ContinueMergeAsyncWorker((string Name, HierarchyNode Pa
156150
}
157151

158152
// remove the excluded children which are no longer there
159-
this.RemoveMissingChildren(missingChildren);
153+
this.RemoveMissingChildren(missingOnDisk);
160154

161155
if (hierarchyCreated)
162156
{

Nodejs/Product/Nodejs/SharedProject/CommonProjectNode.FileSystemChange.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ private async Task ChildCreatedAsync(HierarchyNode child)
184184
this.project.CreateSymLinkWatcher(this.path);
185185
}
186186

187-
var folderNode = this.project.AddAllFilesFolder(parent, this.path + Path.DirectorySeparatorChar);
187+
var folderNode = this.project.AddAllFilesFolder(parent, this.path);
188188
var folderNodeWasExpanded = folderNode.GetIsExpanded();
189189

190190
// then add the folder nodes

Nodejs/Product/Nodejs/SharedProject/CommonProjectNode.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -693,10 +693,12 @@ private HierarchyNode AddAllFilesFile(HierarchyNode curParent, string file)
693693
/// </summary>
694694
private HierarchyNode AddAllFilesFolder(HierarchyNode curParent, string curDir, bool hierarchyCreated = true)
695695
{
696-
var folderNode = FindNodeByFullPath(curDir);
696+
var safePath = CommonUtils.EnsureEndSeparator(curDir);
697+
698+
var folderNode = FindNodeByFullPath(safePath);
697699
if (folderNode == null)
698700
{
699-
folderNode = CreateFolderNode(new AllFilesProjectElement(curDir, "Folder", this));
701+
folderNode = CreateFolderNode(new AllFilesProjectElement(safePath, "Folder", this));
700702
AddAllFilesNode(curParent, folderNode);
701703

702704
if (hierarchyCreated)

Nodejs/Product/Nodejs/SharedProject/HierarchyIdMap.cs

Lines changed: 37 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
22

33
using System;
4-
using System.Collections.Generic;
4+
using System.Collections.Concurrent;
55
using System.Diagnostics;
66

77
namespace Microsoft.VisualStudioTools.Project
88
{
99
internal sealed class HierarchyIdMap
1010
{
11-
private readonly List<WeakReference<HierarchyNode>> ids = new List<WeakReference<HierarchyNode>>();
12-
private readonly Stack<int> freedIds = new Stack<int>();
11+
private readonly ConcurrentDictionary<uint, WeakReference<HierarchyNode>> nodes = new ConcurrentDictionary<uint, WeakReference<HierarchyNode>>();
12+
private readonly ConcurrentStack<uint> freedIds = new ConcurrentStack<uint>();
1313

14-
private readonly object theLock = new object();
14+
public readonly static HierarchyIdMap Instance = new HierarchyIdMap();
15+
16+
private HierarchyIdMap() { }
1517

1618
/// <summary>
1719
/// Must be called from the UI thread
1820
/// </summary>
1921
public uint Add(HierarchyNode node)
2022
{
23+
VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();
24+
Debug.Assert(node != null, "The node added here should never be null.");
2125
#if DEBUG
22-
foreach (var reference in this.ids)
26+
foreach (var reference in this.nodes.Values)
2327
{
2428
if (reference != null)
2529
{
@@ -30,24 +34,21 @@ public uint Add(HierarchyNode node)
3034
}
3135
}
3236
#endif
33-
34-
VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();
35-
36-
lock (this.theLock)
37+
if (!this.freedIds.TryPop(out var idx))
3738
{
38-
if (this.freedIds.Count > 0)
39-
{
40-
var i = this.freedIds.Pop();
41-
this.ids[i] = new WeakReference<HierarchyNode>(node);
42-
return (uint)i + 1;
43-
}
44-
else
45-
{
46-
this.ids.Add(new WeakReference<HierarchyNode>(node));
47-
// ids are 1 based
48-
return (uint)this.ids.Count;
49-
}
39+
idx = this.NextIndex();
5040
}
41+
42+
var addSuccess = this.nodes.TryAdd(idx, new WeakReference<HierarchyNode>(node));
43+
Debug.Assert(addSuccess, "Failed to add a new item");
44+
45+
return idx;
46+
}
47+
48+
private uint NextIndex()
49+
{
50+
// +1 since 0 is not a valid HierarchyId
51+
return (uint)this.nodes.Count + 1;
5152
}
5253

5354
/// <summary>
@@ -57,33 +58,17 @@ public void Remove(HierarchyNode node)
5758
{
5859
VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();
5960

60-
if (node == null)
61-
{
62-
throw new ArgumentNullException(nameof(node));
63-
}
61+
Debug.Assert(node != null, "Called with null node");
6462

65-
lock (this.theLock)
66-
{
67-
var i = (int)node.ID - 1;
68-
if (0 > i || i >= this.ids.Count)
69-
{
70-
throw new InvalidOperationException($"Invalid id. {i}");
71-
}
63+
var idx = node.ID;
7264

73-
var weakRef = this.ids[i];
74-
if (weakRef == null)
75-
{
76-
throw new InvalidOperationException("Trying to retrieve a node before adding.");
77-
}
65+
var removeCheck = this.nodes.TryRemove(idx, out var weakRef);
7866

79-
if (weakRef.TryGetTarget(out var found) && !object.ReferenceEquals(node, found))
80-
{
81-
throw new InvalidOperationException("The node has the wrong id.");
82-
}
67+
Debug.Assert(removeCheck, "How did we get an id, which we haven't seen before");
68+
Debug.Assert(weakRef != null, "How did we insert a null value.");
69+
Debug.Assert(weakRef.TryGetTarget(out var found) && object.ReferenceEquals(node, found), "The node has the wrong id, or was GC-ed before.");
8370

84-
this.ids[i] = null;
85-
this.freedIds.Push(i);
86-
}
71+
this.freedIds.Push(idx);
8772
}
8873

8974
/// <summary>
@@ -93,15 +78,16 @@ public HierarchyNode this[uint itemId]
9378
{
9479
get
9580
{
96-
var i = (int)itemId - 1;
97-
if (0 <= i && i < this.ids.Count)
81+
VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();
82+
83+
var idx = itemId;
84+
if (this.nodes.TryGetValue(idx, out var reference) && reference != null && reference.TryGetTarget(out var node))
9885
{
99-
var reference = this.ids[i];
100-
if (reference != null && reference.TryGetTarget(out var node))
101-
{
102-
return node;
103-
}
86+
Debug.Assert(node != null);
87+
return node;
10488
}
89+
90+
// This is a valid return value, this gets called by VS after we deleted the item
10591
return null;
10692
}
10793
}

Nodejs/Product/Nodejs/SharedProject/ProjectNode.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,6 @@ internal enum EventTriggering
158158
/// <summary>A project will only try to build if it can obtain a lock on this object</summary>
159159
private volatile static object BuildLock = new object();
160160

161-
/// <summary>Maps integer ids to project item instances</summary>
162-
private HierarchyIdMap itemIdMap = new HierarchyIdMap();
163-
164161
/// <summary>A service provider call back object provided by the IDE hosting the project manager</summary>
165162
private IServiceProvider site;
166163

@@ -590,7 +587,7 @@ public virtual string OutputBaseRelativePath
590587
/// <summary>
591588
/// Gets a collection of integer ids that maps to project item instances
592589
/// </summary>
593-
internal HierarchyIdMap ItemIdMap => this.itemIdMap;
590+
internal HierarchyIdMap ItemIdMap => HierarchyIdMap.Instance;
594591

595592
/// <summary>
596593
/// Get the helper object that track document changes.

0 commit comments

Comments
 (0)