| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 3eb0902 commit 3b9e18a
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,11 +1,11 @@ | |||
| 1 | 1 | using System; | |
| 2 | - using System.IO; | ||
| 3 | 2 | using System.Collections; | |
| 4 | - using System.Collections.Specialized; | ||
| 3 | + using System.IO; | ||
| 4 | + using System.Collections.Concurrent; | ||
| 5 | 5 | using System.Collections.Generic; | |
| 6 | 6 | using System.Diagnostics; | |
| 7 | 7 | using System.Reflection; | |
| 8 | - using System.Reflection.Emit; | ||
| 8 | + using System.Threading; | ||
| 9 | 9 | ||
| 10 | 10 | namespace Python.Runtime | |
| 11 | 11 | { | |
@@ -15,12 +15,16 @@ namespace Python.Runtime | |||
| 15 | 15 | /// </summary> | |
| 16 | 16 | internal class AssemblyManager | |
| 17 | 17 | { | |
| 18 | - static Dictionary<string, Dictionary<Assembly, string>> namespaces; | ||
| 18 | + // modified from event handlers below, potentially triggered from different .NET threads | ||
| 19 | + // therefore this should be a ConcurrentDictionary | ||
| 20 | + static ConcurrentDictionary<string, ConcurrentDictionary<Assembly, string>> namespaces; | ||
| 19 | 21 | //static Dictionary<string, Dictionary<string, string>> generics; | |
| 20 | 22 | static AssemblyLoadEventHandler lhandler; | |
| 21 | 23 | static ResolveEventHandler rhandler; | |
| 24 | + // updated only under GIL? | ||
| 22 | 25 | static Dictionary<string, int> probed; | |
| 23 | - static List<Assembly> assemblies; | ||
| 26 | + // modified from event handlers below, potentially triggered from different .NET threads | ||
| 27 | + static AssemblyList assemblies; | ||
| 24 | 28 | internal static List<string> pypath; | |
| 25 | 29 | ||
| 26 | 30 | private AssemblyManager() | |
@@ -36,10 +40,10 @@ private AssemblyManager() | |||
| 36 | 40 | internal static void Initialize() | |
| 37 | 41 | { | |
| 38 | 42 | namespaces = new | |
| 39 | - Dictionary<string, Dictionary<Assembly, string>>(32); | ||
| 43 | + ConcurrentDictionary<string, ConcurrentDictionary<Assembly, string>>(); | ||
| 40 | 44 | probed = new Dictionary<string, int>(32); | |
| 41 | 45 | //generics = new Dictionary<string, Dictionary<string, string>>(); | |
| 42 | - assemblies = new List<Assembly>(16); | ||
| 46 | + assemblies = new AssemblyList(16); | ||
| 43 | 47 | pypath = new List<string>(16); | |
| 44 | 48 | ||
| 45 | 49 | AppDomain domain = AppDomain.CurrentDomain; | |
@@ -105,9 +109,8 @@ static void AssemblyLoadHandler(Object ob, AssemblyLoadEventArgs args) | |||
| 105 | 109 | static Assembly ResolveHandler(Object ob, ResolveEventArgs args) | |
| 106 | 110 | { | |
| 107 | 111 | string name = args.Name.ToLower(); | |
| 108 | - for (int i = 0; i < assemblies.Count; i++) | ||
| 112 | + foreach (Assembly a in assemblies) | ||
| 109 | 113 | { | |
| 110 | - Assembly a = (Assembly)assemblies[i]; | ||
| 111 | 114 | string full = a.FullName.ToLower(); | |
| 112 | 115 | if (full.StartsWith(name)) | |
| 113 | 116 | { | |
@@ -266,9 +269,8 @@ public static Assembly LoadAssemblyFullPath(string name) | |||
| 266 | 269 | ||
| 267 | 270 | public static Assembly FindLoadedAssembly(string name) | |
| 268 | 271 | { | |
| 269 | - for (int i = 0; i < assemblies.Count; i++) | ||
| 272 | + foreach (Assembly a in assemblies) | ||
| 270 | 273 | { | |
| 271 | - Assembly a = (Assembly)assemblies[i]; | ||
| 272 | 274 | if (a.GetName().Name == name) | |
| 273 | 275 | { | |
| 274 | 276 | return a; | |
@@ -295,15 +297,15 @@ public static bool LoadImplicit(string name, bool warn = true) | |||
| 295 | 297 | bool loaded = false; | |
| 296 | 298 | string s = ""; | |
| 297 | 299 | Assembly lastAssembly = null; | |
| 298 | - HashSet<Assembly> assemblies = null; | ||
| 300 | + HashSet<Assembly> assembliesSet = null; | ||
| 299 | 301 | for (int i = 0; i < names.Length; i++) | |
| 300 | 302 | { | |
| 301 | 303 | s = (i == 0) ? names[0] : s + "." + names[i]; | |
| 302 | 304 | if (!probed.ContainsKey(s)) | |
| 303 | 305 | { | |
| 304 | - if (assemblies == null) | ||
| 306 | + if (assembliesSet == null) | ||
| 305 | 307 | { | |
| 306 | - assemblies = new HashSet<Assembly>(AppDomain.CurrentDomain.GetAssemblies()); | ||
| 308 | + assembliesSet = new HashSet<Assembly>(AppDomain.CurrentDomain.GetAssemblies()); | ||
| 307 | 309 | } | |
| 308 | 310 | Assembly a = FindLoadedAssembly(s); | |
| 309 | 311 | if (a == null) | |
@@ -314,7 +316,7 @@ public static bool LoadImplicit(string name, bool warn = true) | |||
| 314 | 316 | { | |
| 315 | 317 | a = LoadAssembly(s); | |
| 316 | 318 | } | |
| 317 | - if (a != null && !assemblies.Contains(a)) | ||
| 319 | + if (a != null && !assembliesSet.Contains(a)) | ||
| 318 | 320 | { | |
| 319 | 321 | loaded = true; | |
| 320 | 322 | lastAssembly = a; | |
@@ -362,16 +364,13 @@ internal static void ScanAssembly(Assembly assembly) | |||
| 362 | 364 | for (int n = 0; n < names.Length; n++) | |
| 363 | 365 | { | |
| 364 | 366 | s = (n == 0) ? names[0] : s + "." + names[n]; | |
| 365 | - if (!namespaces.ContainsKey(s)) | ||
| 366 | - { | ||
| 367 | - namespaces.Add(s, new Dictionary<Assembly, string>()); | ||
| 368 | - } | ||
| 367 | + namespaces.TryAdd(s, new ConcurrentDictionary<Assembly, string>()); | ||
| 369 | 368 | } | |
| 370 | 369 | } | |
| 371 | 370 | ||
| 372 | - if (ns != null && !namespaces[ns].ContainsKey(assembly)) | ||
| 371 | + if (ns != null) | ||
| 373 | 372 | { | |
| 374 | - namespaces[ns].Add(assembly, String.Empty); | ||
| 373 | + namespaces[ns].TryAdd(assembly, String.Empty); | ||
| 375 | 374 | } | |
| 376 | 375 | ||
| 377 | 376 | if (ns != null && t.IsGenericTypeDefinition) | |
@@ -383,14 +382,12 @@ internal static void ScanAssembly(Assembly assembly) | |||
| 383 | 382 | ||
| 384 | 383 | public static AssemblyName[] ListAssemblies() | |
| 385 | 384 | { | |
| 386 | - AssemblyName[] names = new AssemblyName[assemblies.Count]; | ||
| 387 | - Assembly assembly; | ||
| 388 | - for (int i = 0; i < assemblies.Count; i++) | ||
| 385 | + List<AssemblyName> names = new List<AssemblyName>(assemblies.Count); | ||
| 386 | + foreach (Assembly assembly in assemblies) | ||
| 389 | 387 | { | |
| 390 | - assembly = assemblies[i]; | ||
| 391 | - names.SetValue(assembly.GetName(), i); | ||
| 388 | + names.Add(assembly.GetName()); | ||
| 392 | 389 | } | |
| 393 | - return names; | ||
| 390 | + return names.ToArray(); | ||
| 394 | 391 | } | |
| 395 | 392 | ||
| 396 | 393 | //=================================================================== | |
@@ -471,9 +468,8 @@ public static List<string> GetNames(string nsname) | |||
| 471 | 468 | ||
| 472 | 469 | public static Type LookupType(string qname) | |
| 473 | 470 | { | |
| 474 | - for (int i = 0; i < assemblies.Count; i++) | ||
| 471 | + foreach (Assembly assembly in assemblies) | ||
| 475 | 472 | { | |
| 476 | - Assembly assembly = (Assembly)assemblies[i]; | ||
| 477 | 473 | Type type = assembly.GetType(qname); | |
| 478 | 474 | if (type != null) | |
| 479 | 475 | { | |
@@ -482,5 +478,92 @@ public static Type LookupType(string qname) | |||
| 482 | 478 | } | |
| 483 | 479 | return null; | |
| 484 | 480 | } | |
| 481 | + | ||
| 482 | + /// <summary> | ||
| 483 | + /// Wrapper around List<Assembly> for thread safe access | ||
| 484 | + /// </summary> | ||
| 485 | + private class AssemblyList : IEnumerable<Assembly>{ | ||
| 486 | + private readonly List<Assembly> _list; | ||
| 487 | + private readonly ReaderWriterLockSlim _lock; | ||
| 488 | + | ||
| 489 | + public AssemblyList(int capacity) { | ||
| 490 | + _list = new List<Assembly>(capacity); | ||
| 491 | + _lock = new ReaderWriterLockSlim(); | ||
| 492 | + } | ||
| 493 | + | ||
| 494 | + public int Count | ||
| 495 | + { | ||
| 496 | + get | ||
| 497 | + { | ||
| 498 | + _lock.EnterReadLock(); | ||
| 499 | + try { | ||
| 500 | + return _list.Count; | ||
| 501 | + } | ||
| 502 | + finally { | ||
| 503 | + _lock.ExitReadLock(); | ||
| 504 | + } | ||
| 505 | + } | ||
| 506 | + } | ||
| 507 | + | ||
| 508 | + public void Add(Assembly assembly) { | ||
| 509 | + _lock.EnterWriteLock(); | ||
| 510 | + try | ||
| 511 | + { | ||
| 512 | + _list.Add(assembly); | ||
| 513 | + } | ||
| 514 | + finally | ||
| 515 | + { | ||
| 516 | + _lock.ExitWriteLock(); | ||
| 517 | + } | ||
| 518 | + } | ||
| 519 | + | ||
| 520 | + public IEnumerator GetEnumerator() | ||
| 521 | + { | ||
| 522 | + return ((IEnumerable<Assembly>) this).GetEnumerator(); | ||
| 523 | + } | ||
| 524 | + | ||
| 525 | + /// <summary> | ||
| 526 | + /// Enumerator wrapping around <see cref="AssemblyList._list"/>'s enumerator. | ||
| 527 | + /// Acquires and releases a read lock on <see cref="AssemblyList._lock"/> during enumeration | ||
| 528 | + /// </summary> | ||
| 529 | + private class Enumerator : IEnumerator<Assembly> | ||
| 530 | + { | ||
| 531 | + private readonly AssemblyList _assemblyList; | ||
| 532 | + | ||
| 533 | + private readonly IEnumerator<Assembly> _listEnumerator; | ||
| 534 | + | ||
| 535 | + public Enumerator(AssemblyList assemblyList) | ||
| 536 | + { | ||
| 537 | + _assemblyList = assemblyList; | ||
| 538 | + _assemblyList._lock.EnterReadLock(); | ||
| 539 | + _listEnumerator = _assemblyList._list.GetEnumerator(); | ||
| 540 | + } | ||
| 541 | + | ||
| 542 | + public void Dispose() | ||
| 543 | + { | ||
| 544 | + _listEnumerator.Dispose(); | ||
| 545 | + _assemblyList._lock.ExitReadLock(); | ||
| 546 | + } | ||
| 547 | + | ||
| 548 | + public bool MoveNext() | ||
| 549 | + { | ||
| 550 | + return _listEnumerator.MoveNext(); | ||
| 551 | + } | ||
| 552 | + | ||
| 553 | + public void Reset() | ||
| 554 | + { | ||
| 555 | + _listEnumerator.Reset(); | ||
| 556 | + } | ||
| 557 | + | ||
| 558 | + public Assembly Current { get { return _listEnumerator.Current; } } | ||
| 559 | + | ||
| 560 | + object IEnumerator.Current { get { return Current; } } | ||
| 561 | + } | ||
| 562 | + | ||
| 563 | + IEnumerator<Assembly> IEnumerable<Assembly>.GetEnumerator() | ||
| 564 | + { | ||
| 565 | + return new Enumerator(this); | ||
| 566 | + } | ||
| 567 | + } | ||
| 485 | 568 | } | |
| 486 | 569 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -122,6 +122,7 @@ | |||
| 122 | 122 | <Compile Include="indexertest.cs" /> | |
| 123 | 123 | <Compile Include="interfacetest.cs" /> | |
| 124 | 124 | <Compile Include="methodtest.cs" /> | |
| 125 | + <Compile Include="moduletest.cs" /> | ||
| 125 | 126 | <Compile Include="propertytest.cs" /> | |
| 126 | 127 | <Compile Include="threadtest.cs" /> | |
| 127 | 128 | <Compile Include="doctest.cs" /> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,25 @@ | |||
| 1 | + using System; | ||
| 2 | + using System.Threading; | ||
| 3 | + | ||
| 4 | + namespace Python.Test { | ||
| 5 | + public class ModuleTest { | ||
| 6 | + private static Thread _thread; | ||
| 7 | + | ||
| 8 | + public static void RunThreads() | ||
| 9 | + { | ||
| 10 | + _thread = new Thread(() => { | ||
| 11 | + var appdomain = AppDomain.CurrentDomain; | ||
| 12 | + var assemblies = appdomain.GetAssemblies(); | ||
| 13 | + foreach (var assembly in assemblies) { | ||
| 14 | + assembly.GetTypes(); | ||
| 15 | + } | ||
| 16 | + }); | ||
| 17 | + _thread.Start(); | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + public static void JoinThreads() | ||
| 21 | + { | ||
| 22 | + _thread.Join(); | ||
| 23 | + } | ||
| 24 | + } | ||
| 25 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -65,8 +65,11 @@ def testModuleInterface(self): | |||
| 65 | 65 | import System | |
| 66 | 66 | self.assertEquals(type(System.__dict__), type({})) | |
| 67 | 67 | self.assertEquals(System.__name__, 'System') | |
| 68 | - # the filename can be any module from the System namespace (eg System.Data.dll or System.dll) | ||
| 69 | - self.assertTrue(fnmatch(System.__file__, "*System*.dll")) | ||
| 68 | + # the filename can be any module from the System namespace | ||
| 69 | + # (eg System.Data.dll or System.dll, but also mscorlib.dll) | ||
| 70 | + system_file = System.__file__ | ||
| 71 | + self.assertTrue(fnmatch(system_file, "*System*.dll") or fnmatch(system_file, "*mscorlib.dll"), | ||
| 72 | + "unexpected System.__file__: " + system_file) | ||
| 70 | 73 | self.assertTrue(System.__doc__.startswith("Namespace containing types from the following assemblies:")) | |
| 71 | 74 | self.assertTrue(self.isCLRClass(System.String)) | |
| 72 | 75 | self.assertTrue(self.isCLRClass(System.Int32)) | |
@@ -353,6 +356,22 @@ def test_ClrAddReference(self): | |||
| 353 | 356 | self.assertRaises(FileNotFoundException, | |
| 354 | 357 | AddReference, "somethingtotallysilly") | |
| 355 | 358 | ||
| 359 | + def test_AssemblyLoadThreadSafety(self): | ||
| 360 | + import time | ||
| 361 | + from Python.Test import ModuleTest | ||
| 362 | + # spin up .NET thread which loads assemblies and triggers AppDomain.AssemblyLoad event | ||
| 363 | + ModuleTest.RunThreads() | ||
| 364 | + time.sleep(1e-5) | ||
| 365 | + for i in range(1, 100): | ||
| 366 | + # call import clr, which in AssemblyManager.GetNames iterates through the loaded types | ||
| 367 | + import clr | ||
| 368 | + # import some .NET types | ||
| 369 | + from System import DateTime | ||
| 370 | + from System import Guid | ||
| 371 | + from System.Collections.Generic import Dictionary | ||
| 372 | + dict = Dictionary[Guid,DateTime]() | ||
| 373 | + ModuleTest.JoinThreads() | ||
| 374 | + | ||
| 356 | 375 | ||
| 357 | 376 | def test_suite(): | |
| 358 | 377 | return unittest.makeSuite(ModuleTests) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments