Skip to content

Commit fcc0351

Browse files
authored
Fix a couple of issues with StackTraceSymbols.TryGetReader (#67300)
The implementation has a comment about how ConditionalWeakTable prevents multiple threads from racing to create readers, but CWT doesn't invole the delegate under its lock. So multiple threads can actually race to create a reader, and if one loses, it won't Dispose the reader it created. On top of this, every call to TryGetReader is allocating a closure, even if one of the fast paths is hit, because the cache callback captures all the parameters.
1 parent 3a4c9b0 commit fcc0351

File tree

4 files changed

+39
-13
lines changed

4 files changed

+39
-13
lines changed

src/libraries/System.Diagnostics.StackTrace/src/System/Diagnostics/StackTraceSymbols.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,20 @@ internal void GetSourceLineInfo(Assembly assembly, string assemblyPath, IntPtr l
123123
return null;
124124
}
125125

126-
// The ConditionalWeakTable's GetValue + callback will atomically create the cache entry for us
127-
// so we are protected from multiple threads racing to get/create the same MetadataReaderProvider
128-
MetadataReaderProvider? provider = _metadataCache.GetValue(assembly, (assembly) =>
126+
MetadataReaderProvider? provider;
127+
while (!_metadataCache.TryGetValue(assembly, out provider))
129128
{
130-
return (inMemoryPdbAddress != IntPtr.Zero) ?
131-
TryOpenReaderForInMemoryPdb(inMemoryPdbAddress, inMemoryPdbSize) :
132-
TryOpenReaderFromAssemblyFile(assemblyPath!, loadedPeAddress, loadedPeSize, isFileLayout);
133-
});
129+
provider = inMemoryPdbAddress != IntPtr.Zero ?
130+
TryOpenReaderForInMemoryPdb(inMemoryPdbAddress, inMemoryPdbSize) :
131+
TryOpenReaderFromAssemblyFile(assemblyPath!, loadedPeAddress, loadedPeSize, isFileLayout);
132+
133+
if (_metadataCache.TryAdd(assembly, provider))
134+
{
135+
break;
136+
}
137+
138+
provider?.Dispose();
139+
}
134140

135141
// The reader has already been open, so this doesn't throw.
136142
return provider?.GetMetadataReader();

src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,30 @@ public void Add(TKey key, TValue value)
8989
}
9090
}
9191

92+
/// <summary>Adds a key to the table if it doesn't already exist.</summary>
93+
/// <param name="key">The key to add.</param>
94+
/// <param name="value">The key's property value.</param>
95+
/// <returns>true if the key/value pair was added; false if the table already contained the key.</returns>
96+
public bool TryAdd(TKey key, TValue value) // TODO: Expose in ref assembly https://github.com/dotnet/runtime/issues/29368
97+
{
98+
if (key is null)
99+
{
100+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
101+
}
102+
103+
lock (_lock)
104+
{
105+
int entryIndex = _container.FindEntry(key, out _);
106+
if (entryIndex != -1)
107+
{
108+
return false;
109+
}
110+
111+
CreateEntry(key, value);
112+
return true;
113+
}
114+
}
115+
92116
/// <summary>Adds the key and value if the key doesn't exist, or updates the existing key's value if it does exist.</summary>
93117
/// <param name="key">key to add or update. May not be null.</param>
94118
/// <param name="value">value to associate with key.</param>

src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,8 @@ public static void SetDllImportResolver(Assembly assembly!!, DllImportResolver r
196196
new ConditionalWeakTable<Assembly, DllImportResolver>(), null);
197197
}
198198

199-
try
199+
if (!s_nativeDllResolveMap.TryAdd(assembly, resolver))
200200
{
201-
s_nativeDllResolveMap.Add(assembly, resolver);
202-
}
203-
catch (ArgumentException)
204-
{
205-
// ConditionalWeakTable throws ArgumentException if the Key already exists
206201
throw new InvalidOperationException(SR.InvalidOperation_CannotRegisterSecondResolver);
207202
}
208203
}

src/libraries/System.Runtime/src/MatchingRefApiCompatBaseline.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ MembersMustExist : Member 'public System.Diagnostics.DebugProvider System.Diagno
1212
MembersMustExist : Member 'protected System.ModuleHandle System.Reflection.Module.GetModuleHandleImpl()' does not exist in the reference but it does exist in the implementation.
1313
MembersMustExist : Member 'protected System.String System.String System.Resources.ResourceManager.BaseNameField' does not exist in the reference but it does exist in the implementation.
1414
MembersMustExist : Member 'protected System.Resources.IResourceReader System.Resources.IResourceReader System.Resources.ResourceSet.Reader' does not exist in the reference but it does exist in the implementation.
15+
MembersMustExist : Member 'public System.Boolean System.Runtime.CompilerServices.ConditionalWeakTable<TKey, TValue>.TryAdd(TKey, TValue)' does not exist in the reference but it does exist in the implementation.
1516
MembersMustExist : Member 'public System.Boolean System.Runtime.Serialization.SerializationInfo.DeserializationInProgress.get()' does not exist in the reference but it does exist in the implementation.
1617
MembersMustExist : Member 'public System.Runtime.Serialization.DeserializationToken System.Runtime.Serialization.SerializationInfo.StartDeserialization()' does not exist in the reference but it does exist in the implementation.
1718
MembersMustExist : Member 'public void System.Runtime.Serialization.SerializationInfo.ThrowIfDeserializationInProgress()' does not exist in the reference but it does exist in the implementation.

0 commit comments

Comments
 (0)