From 89a55047e73943eb4ee3bdf6df93a0944b393fa6 Mon Sep 17 00:00:00 2001 From: abessen Date: Wed, 26 Oct 2016 12:37:58 -0400 Subject: [PATCH 01/10] Use ConcurrentDictionary for thread safety of import clr --- src/runtime/assemblymanager.cs | 14 ++++++++++---- src/tests/test_module.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index a791b8195..fb86066c4 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -1,11 +1,13 @@ using System; using System.IO; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Specialized; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; +using System.Threading; namespace Python.Runtime { @@ -15,7 +17,7 @@ namespace Python.Runtime /// internal class AssemblyManager { - static Dictionary> namespaces; + static Dictionary> namespaces; //static Dictionary> generics; static AssemblyLoadEventHandler lhandler; static ResolveEventHandler rhandler; @@ -35,8 +37,9 @@ private AssemblyManager() internal static void Initialize() { + Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Initialize"); namespaces = new - Dictionary>(32); + Dictionary>(32); probed = new Dictionary(32); //generics = new Dictionary>(); assemblies = new List(16); @@ -364,14 +367,17 @@ internal static void ScanAssembly(Assembly assembly) s = (n == 0) ? names[0] : s + "." + names[n]; if (!namespaces.ContainsKey(s)) { - namespaces.Add(s, new Dictionary()); + Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: ScanAssembly, Add {s}"); + namespaces.Add(s, new ConcurrentDictionary()); } } } if (ns != null && !namespaces[ns].ContainsKey(assembly)) { - namespaces[ns].Add(assembly, String.Empty); + Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: ScanAssembly, Add {ns}, {assembly.FullName}"); + if (!namespaces[ns].TryAdd(assembly, String.Empty)) + throw new ArgumentException("Adding duplicate " + assembly); } if (ns != null && t.IsGenericTypeDefinition) diff --git a/src/tests/test_module.py b/src/tests/test_module.py index f03954d28..cf93a6820 100644 --- a/src/tests/test_module.py +++ b/src/tests/test_module.py @@ -353,6 +353,23 @@ def test_ClrAddReference(self): self.assertRaises(FileNotFoundException, AddReference, "somethingtotallysilly") + def test_ThreadSafety(self): + import threading + + def test_fn(i): + import time + time.sleep(i * 0.001) + import clr + from System.IO import FileNotFoundException + + threads = [threading.Thread(target=test_fn, args=[i]) for i in range(1, 20)] + + for thread in threads: + thread.start() + + for thread in threads: + thread.join() + def test_suite(): return unittest.makeSuite(ModuleTests) From cd1b9b9e3b0c48b03397fe10b24d60faf18667ae Mon Sep 17 00:00:00 2001 From: abessen Date: Wed, 26 Oct 2016 23:18:12 -0400 Subject: [PATCH 02/10] Add thread safety test, use ConcurrentDictionary, lock for assemblies --- src/runtime/assemblymanager.cs | 80 +++++++++++++++++++--------------- src/testing/Python.Test.csproj | 1 + src/testing/moduletest.cs | 17 ++++++++ src/tests/test_module.py | 23 ++++------ 4 files changed, 71 insertions(+), 50 deletions(-) create mode 100644 src/testing/moduletest.cs diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index fb86066c4..34318e785 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -1,13 +1,9 @@ using System; using System.IO; -using System.Collections; using System.Collections.Concurrent; -using System.Collections.Specialized; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; -using System.Reflection.Emit; -using System.Threading; namespace Python.Runtime { @@ -17,11 +13,16 @@ namespace Python.Runtime /// internal class AssemblyManager { - static Dictionary> namespaces; + // modified from event handlers below, potentially triggered from different .NET threads + // therefore this should be a ConcurrentDictionary + static ConcurrentDictionary> namespaces; //static Dictionary> generics; static AssemblyLoadEventHandler lhandler; static ResolveEventHandler rhandler; + // updated only under GIL? static Dictionary probed; + // modified from event handlers below, potentially triggered from different .NET threads + // we guard access to assemblies via lock(assemblies) { ... } blocks static List assemblies; internal static List pypath; @@ -37,9 +38,8 @@ private AssemblyManager() internal static void Initialize() { - Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Initialize"); namespaces = new - Dictionary>(32); + ConcurrentDictionary>(); probed = new Dictionary(32); //generics = new Dictionary>(); assemblies = new List(16); @@ -92,7 +92,10 @@ internal static void Shutdown() static void AssemblyLoadHandler(Object ob, AssemblyLoadEventArgs args) { Assembly assembly = args.LoadedAssembly; - assemblies.Add(assembly); + lock (assemblies) + { + assemblies.Add(assembly); + } ScanAssembly(assembly); } @@ -108,13 +111,16 @@ static void AssemblyLoadHandler(Object ob, AssemblyLoadEventArgs args) static Assembly ResolveHandler(Object ob, ResolveEventArgs args) { string name = args.Name.ToLower(); - for (int i = 0; i < assemblies.Count; i++) + lock (assemblies) { - Assembly a = (Assembly)assemblies[i]; - string full = a.FullName.ToLower(); - if (full.StartsWith(name)) + for (int i = 0; i < assemblies.Count; i++) { - return a; + Assembly a = (Assembly) assemblies[i]; + string full = a.FullName.ToLower(); + if (full.StartsWith(name)) + { + return a; + } } } return LoadAssemblyPath(args.Name); @@ -269,12 +275,15 @@ public static Assembly LoadAssemblyFullPath(string name) public static Assembly FindLoadedAssembly(string name) { - for (int i = 0; i < assemblies.Count; i++) + lock (assemblies) { - Assembly a = (Assembly)assemblies[i]; - if (a.GetName().Name == name) + for (int i = 0; i < assemblies.Count; i++) { - return a; + Assembly a = (Assembly) assemblies[i]; + if (a.GetName().Name == name) + { + return a; + } } } return null; @@ -365,19 +374,14 @@ internal static void ScanAssembly(Assembly assembly) for (int n = 0; n < names.Length; n++) { s = (n == 0) ? names[0] : s + "." + names[n]; - if (!namespaces.ContainsKey(s)) - { - Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: ScanAssembly, Add {s}"); - namespaces.Add(s, new ConcurrentDictionary()); - } + namespaces.TryAdd(s, new ConcurrentDictionary()); } } if (ns != null && !namespaces[ns].ContainsKey(assembly)) { - Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: ScanAssembly, Add {ns}, {assembly.FullName}"); if (!namespaces[ns].TryAdd(assembly, String.Empty)) - throw new ArgumentException("Adding duplicate " + assembly); + throw new ArgumentException("Adding duplicate assembly " + assembly); } if (ns != null && t.IsGenericTypeDefinition) @@ -389,14 +393,17 @@ internal static void ScanAssembly(Assembly assembly) public static AssemblyName[] ListAssemblies() { - AssemblyName[] names = new AssemblyName[assemblies.Count]; - Assembly assembly; - for (int i = 0; i < assemblies.Count; i++) + lock (assemblies) { - assembly = assemblies[i]; - names.SetValue(assembly.GetName(), i); + AssemblyName[] names = new AssemblyName[assemblies.Count]; + Assembly assembly; + for (int i = 0; i < assemblies.Count; i++) + { + assembly = assemblies[i]; + names.SetValue(assembly.GetName(), i); + } + return names; } - return names; } //=================================================================== @@ -477,13 +484,16 @@ public static List GetNames(string nsname) public static Type LookupType(string qname) { - for (int i = 0; i < assemblies.Count; i++) + lock (assemblies) { - Assembly assembly = (Assembly)assemblies[i]; - Type type = assembly.GetType(qname); - if (type != null) + for (int i = 0; i < assemblies.Count; i++) { - return type; + Assembly assembly = (Assembly) assemblies[i]; + Type type = assembly.GetType(qname); + if (type != null) + { + return type; + } } } return null; diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index 56efda8e3..a0d62d4f9 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -122,6 +122,7 @@ + diff --git a/src/testing/moduletest.cs b/src/testing/moduletest.cs new file mode 100644 index 000000000..66a9bb38c --- /dev/null +++ b/src/testing/moduletest.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading; + +namespace Python.Test { + public class ModuleTest { + public static void RunThreads() { + var thread = new Thread(() => { + var appdomain = AppDomain.CurrentDomain; + var assemblies = appdomain.GetAssemblies(); + foreach (var assembly in assemblies) { + assembly.GetTypes(); + } + }); + thread.Start(); + } + } +} \ No newline at end of file diff --git a/src/tests/test_module.py b/src/tests/test_module.py index cf93a6820..147b34573 100644 --- a/src/tests/test_module.py +++ b/src/tests/test_module.py @@ -353,22 +353,15 @@ def test_ClrAddReference(self): self.assertRaises(FileNotFoundException, AddReference, "somethingtotallysilly") - def test_ThreadSafety(self): - import threading - - def test_fn(i): - import time - time.sleep(i * 0.001) + def test_AssemblyLoadThreadSafety(self): + import time + from Python.Test import ModuleTest + # spin up .NET thread which loads assemblies and triggers AppDomain.AssemblyLoad event + ModuleTest.RunThreads() + time.sleep(1e-5) + # call import clr, which in AssemblyManager.GetNames iterates through the loaded types + for i in range(1, 100): import clr - from System.IO import FileNotFoundException - - threads = [threading.Thread(target=test_fn, args=[i]) for i in range(1, 20)] - - for thread in threads: - thread.start() - - for thread in threads: - thread.join() def test_suite(): From 596c210728ee67158eecbf0d7968c1360f4f008d Mon Sep 17 00:00:00 2001 From: abessen Date: Thu, 27 Oct 2016 00:04:34 -0400 Subject: [PATCH 03/10] Use atomic TryAdd call here as well --- src/runtime/assemblymanager.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index 34318e785..b107d483d 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -378,10 +378,9 @@ internal static void ScanAssembly(Assembly assembly) } } - if (ns != null && !namespaces[ns].ContainsKey(assembly)) + if (ns != null) { - if (!namespaces[ns].TryAdd(assembly, String.Empty)) - throw new ArgumentException("Adding duplicate assembly " + assembly); + namespaces[ns].TryAdd(assembly, String.Empty); } if (ns != null && t.IsGenericTypeDefinition) From b9e532d800177054dc2504e5669fe07bb4e59e2b Mon Sep 17 00:00:00 2001 From: abessen Date: Thu, 27 Oct 2016 00:17:05 -0400 Subject: [PATCH 04/10] Print System.__file__ if assertion fails --- src/tests/test_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tests/test_module.py b/src/tests/test_module.py index 147b34573..db4ad6fcb 100644 --- a/src/tests/test_module.py +++ b/src/tests/test_module.py @@ -66,7 +66,8 @@ def testModuleInterface(self): self.assertEquals(type(System.__dict__), type({})) self.assertEquals(System.__name__, 'System') # the filename can be any module from the System namespace (eg System.Data.dll or System.dll) - self.assertTrue(fnmatch(System.__file__, "*System*.dll")) + self.assertTrue(fnmatch(System.__file__, "*System*.dll"), + "unexpected System.__file__" + System.__file__) self.assertTrue(System.__doc__.startswith("Namespace containing types from the following assemblies:")) self.assertTrue(self.isCLRClass(System.String)) self.assertTrue(self.isCLRClass(System.Int32)) From 2742b1287035c97a3cf5d1321bd74f7259604c79 Mon Sep 17 00:00:00 2001 From: abessen Date: Thu, 27 Oct 2016 12:44:03 -0400 Subject: [PATCH 05/10] Wrap assemblies in thread-safe class, make sure forked thread joins in test_AssemblyLoadThreadSafety --- src/runtime/assemblymanager.cs | 141 +++++++++++++++++++++++---------- src/testing/moduletest.cs | 14 +++- src/tests/test_module.py | 1 + 3 files changed, 110 insertions(+), 46 deletions(-) diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index b107d483d..10d575b36 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -1,9 +1,11 @@ using System; +using System.Collections; using System.IO; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using System.Threading; namespace Python.Runtime { @@ -22,8 +24,7 @@ internal class AssemblyManager // updated only under GIL? static Dictionary probed; // modified from event handlers below, potentially triggered from different .NET threads - // we guard access to assemblies via lock(assemblies) { ... } blocks - static List assemblies; + static AssemblyList assemblies; internal static List pypath; private AssemblyManager() @@ -42,7 +43,7 @@ internal static void Initialize() ConcurrentDictionary>(); probed = new Dictionary(32); //generics = new Dictionary>(); - assemblies = new List(16); + assemblies = new AssemblyList(16); pypath = new List(16); AppDomain domain = AppDomain.CurrentDomain; @@ -92,10 +93,7 @@ internal static void Shutdown() static void AssemblyLoadHandler(Object ob, AssemblyLoadEventArgs args) { Assembly assembly = args.LoadedAssembly; - lock (assemblies) - { - assemblies.Add(assembly); - } + assemblies.Add(assembly); ScanAssembly(assembly); } @@ -111,16 +109,12 @@ static void AssemblyLoadHandler(Object ob, AssemblyLoadEventArgs args) static Assembly ResolveHandler(Object ob, ResolveEventArgs args) { string name = args.Name.ToLower(); - lock (assemblies) + foreach (Assembly a in assemblies) { - for (int i = 0; i < assemblies.Count; i++) + string full = a.FullName.ToLower(); + if (full.StartsWith(name)) { - Assembly a = (Assembly) assemblies[i]; - string full = a.FullName.ToLower(); - if (full.StartsWith(name)) - { - return a; - } + return a; } } return LoadAssemblyPath(args.Name); @@ -275,15 +269,11 @@ public static Assembly LoadAssemblyFullPath(string name) public static Assembly FindLoadedAssembly(string name) { - lock (assemblies) + foreach (Assembly a in assemblies) { - for (int i = 0; i < assemblies.Count; i++) + if (a.GetName().Name == name) { - Assembly a = (Assembly) assemblies[i]; - if (a.GetName().Name == name) - { - return a; - } + return a; } } return null; @@ -307,15 +297,15 @@ public static bool LoadImplicit(string name, bool warn = true) bool loaded = false; string s = ""; Assembly lastAssembly = null; - HashSet assemblies = null; + HashSet assembliesSet = null; for (int i = 0; i < names.Length; i++) { s = (i == 0) ? names[0] : s + "." + names[i]; if (!probed.ContainsKey(s)) { - if (assemblies == null) + if (assembliesSet == null) { - assemblies = new HashSet(AppDomain.CurrentDomain.GetAssemblies()); + assembliesSet = new HashSet(AppDomain.CurrentDomain.GetAssemblies()); } Assembly a = FindLoadedAssembly(s); if (a == null) @@ -326,7 +316,7 @@ public static bool LoadImplicit(string name, bool warn = true) { a = LoadAssembly(s); } - if (a != null && !assemblies.Contains(a)) + if (a != null && !assembliesSet.Contains(a)) { loaded = true; lastAssembly = a; @@ -392,17 +382,12 @@ internal static void ScanAssembly(Assembly assembly) public static AssemblyName[] ListAssemblies() { - lock (assemblies) + List names = new List(assemblies.Count); + foreach (Assembly assembly in assemblies) { - AssemblyName[] names = new AssemblyName[assemblies.Count]; - Assembly assembly; - for (int i = 0; i < assemblies.Count; i++) - { - assembly = assemblies[i]; - names.SetValue(assembly.GetName(), i); - } - return names; + names.Add(assembly.GetName()); } + return names.ToArray(); } //=================================================================== @@ -483,19 +468,89 @@ public static List GetNames(string nsname) public static Type LookupType(string qname) { - lock (assemblies) + foreach (Assembly assembly in assemblies) { - for (int i = 0; i < assemblies.Count; i++) + Type type = assembly.GetType(qname); + if (type != null) { - Assembly assembly = (Assembly) assemblies[i]; - Type type = assembly.GetType(qname); - if (type != null) - { - return type; - } + return type; } } return null; } + + /// + /// Wrapper around List for thread safe access + /// + private class AssemblyList : IEnumerable{ + private readonly List _list; + private readonly ReaderWriterLockSlim _lock; + + public AssemblyList(int capacity) { + _list = new List(capacity); + _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + } + + public int Count { get { return _list.Count; } } + + public void Add(Assembly assembly) { + _lock.EnterWriteLock(); + try + { + _list.Add(assembly); + } + finally + { + _lock.ExitWriteLock(); + } + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable) this).GetEnumerator(); + } + + /// + /// Enumerator wrapping around 's enumerator. + /// Acquires and releases a read lock on during enumeration + /// + private class Enumerator : IEnumerator + { + private readonly AssemblyList _assemblyList; + + private readonly IEnumerator _listEnumerator; + + public Enumerator(AssemblyList assemblyList) + { + _assemblyList = assemblyList; + _listEnumerator = _assemblyList._list.GetEnumerator(); + _assemblyList._lock.EnterReadLock(); + } + + public void Dispose() + { + _assemblyList._lock.ExitReadLock(); + } + + public bool MoveNext() + { + return _listEnumerator.MoveNext(); + } + + public void Reset() + { + _listEnumerator.Reset(); + } + + public Assembly Current { get { return _listEnumerator.Current; } } + + object IEnumerator.Current { get { return Current; } } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(this); + } + } } } diff --git a/src/testing/moduletest.cs b/src/testing/moduletest.cs index 66a9bb38c..8734f2569 100644 --- a/src/testing/moduletest.cs +++ b/src/testing/moduletest.cs @@ -3,15 +3,23 @@ namespace Python.Test { public class ModuleTest { - public static void RunThreads() { - var thread = new Thread(() => { + private static Thread _thread; + + public static void RunThreads() + { + _thread = new Thread(() => { var appdomain = AppDomain.CurrentDomain; var assemblies = appdomain.GetAssemblies(); foreach (var assembly in assemblies) { assembly.GetTypes(); } }); - thread.Start(); + _thread.Start(); + } + + public static void JoinThreads() + { + _thread.Join(); } } } \ No newline at end of file diff --git a/src/tests/test_module.py b/src/tests/test_module.py index db4ad6fcb..52550ba9e 100644 --- a/src/tests/test_module.py +++ b/src/tests/test_module.py @@ -363,6 +363,7 @@ def test_AssemblyLoadThreadSafety(self): # call import clr, which in AssemblyManager.GetNames iterates through the loaded types for i in range(1, 100): import clr + ModuleTest.JoinThreads() def test_suite(): From 6838be81c7beebed564c02f277e6ec33cbae697c Mon Sep 17 00:00:00 2001 From: abessen Date: Thu, 27 Oct 2016 22:20:34 -0400 Subject: [PATCH 06/10] No need to support recursion, also lock when we get _list.Count --- src/runtime/assemblymanager.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index 10d575b36..6633924dd 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -488,10 +488,22 @@ private class AssemblyList : IEnumerable{ public AssemblyList(int capacity) { _list = new List(capacity); - _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + _lock = new ReaderWriterLockSlim(); } - public int Count { get { return _list.Count; } } + public int Count + { + get + { + _lock.EnterReadLock(); + try { + return _list.Count; + } + finally { + _lock.ExitReadLock(); + } + } + } public void Add(Assembly assembly) { _lock.EnterWriteLock(); From 59b9ea1d29eb8140add97e332ea63ba47d0f7668 Mon Sep 17 00:00:00 2001 From: abessen Date: Fri, 28 Oct 2016 10:43:54 -0400 Subject: [PATCH 07/10] Import some .NET types as well and use them --- src/tests/test_module.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tests/test_module.py b/src/tests/test_module.py index 52550ba9e..b87777477 100644 --- a/src/tests/test_module.py +++ b/src/tests/test_module.py @@ -360,9 +360,14 @@ def test_AssemblyLoadThreadSafety(self): # spin up .NET thread which loads assemblies and triggers AppDomain.AssemblyLoad event ModuleTest.RunThreads() time.sleep(1e-5) - # call import clr, which in AssemblyManager.GetNames iterates through the loaded types for i in range(1, 100): + # call import clr, which in AssemblyManager.GetNames iterates through the loaded types import clr + # import some .NET types + from System import DateTime + from System import Guid + from System.Collections.Generic import Dictionary + dict = Dictionary[Guid,DateTime]() ModuleTest.JoinThreads() From 2f416c4fcc0167c23073b70c815ac630e2ea431c Mon Sep 17 00:00:00 2001 From: abessen Date: Fri, 28 Oct 2016 12:14:56 -0400 Subject: [PATCH 08/10] Part of the System namespace is implemented in mscorlib.dll --- src/tests/test_module.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tests/test_module.py b/src/tests/test_module.py index b87777477..18a41583e 100644 --- a/src/tests/test_module.py +++ b/src/tests/test_module.py @@ -65,9 +65,11 @@ def testModuleInterface(self): import System self.assertEquals(type(System.__dict__), type({})) self.assertEquals(System.__name__, 'System') - # the filename can be any module from the System namespace (eg System.Data.dll or System.dll) - self.assertTrue(fnmatch(System.__file__, "*System*.dll"), - "unexpected System.__file__" + System.__file__) + # the filename can be any module from the System namespace + # (eg System.Data.dll or System.dll, but also mscorlib.dll) + system_file = System.__file__ + self.assertTrue(fnmatch(system_file, "*System*.dll") or fnmatch(system_file, "mscorlib.dll"), + "unexpected System.__file__" + system_file) self.assertTrue(System.__doc__.startswith("Namespace containing types from the following assemblies:")) self.assertTrue(self.isCLRClass(System.String)) self.assertTrue(self.isCLRClass(System.Int32)) From 3844d19938c6f9a2e20c5235408e931afab3a243 Mon Sep 17 00:00:00 2001 From: abessen Date: Fri, 28 Oct 2016 12:22:16 -0400 Subject: [PATCH 09/10] Missed an asterisk --- src/tests/test_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/test_module.py b/src/tests/test_module.py index 18a41583e..a23f37d90 100644 --- a/src/tests/test_module.py +++ b/src/tests/test_module.py @@ -68,8 +68,8 @@ def testModuleInterface(self): # the filename can be any module from the System namespace # (eg System.Data.dll or System.dll, but also mscorlib.dll) system_file = System.__file__ - self.assertTrue(fnmatch(system_file, "*System*.dll") or fnmatch(system_file, "mscorlib.dll"), - "unexpected System.__file__" + system_file) + self.assertTrue(fnmatch(system_file, "*System*.dll") or fnmatch(system_file, "*mscorlib.dll"), + "unexpected System.__file__: " + system_file) self.assertTrue(System.__doc__.startswith("Namespace containing types from the following assemblies:")) self.assertTrue(self.isCLRClass(System.String)) self.assertTrue(self.isCLRClass(System.Int32)) From bce86dce6aa767f19cf9611fca654f36f3afdf25 Mon Sep 17 00:00:00 2001 From: abessen Date: Thu, 3 Nov 2016 23:12:42 -0400 Subject: [PATCH 10/10] Grab lock *before* getting enumerator --- src/runtime/assemblymanager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index 6633924dd..5d9759375 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -535,12 +535,13 @@ private class Enumerator : IEnumerator public Enumerator(AssemblyList assemblyList) { _assemblyList = assemblyList; - _listEnumerator = _assemblyList._list.GetEnumerator(); _assemblyList._lock.EnterReadLock(); + _listEnumerator = _assemblyList._list.GetEnumerator(); } public void Dispose() { + _listEnumerator.Dispose(); _assemblyList._lock.ExitReadLock(); }