Skip to content

Commit 9c70b94

Browse files
authored
Merge branch 'master' into add-cause
2 parents 8d60359 + 3b9e18a commit 9c70b94

File tree

5 files changed

+160
-32
lines changed

5 files changed

+160
-32
lines changed

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ def _get_interop_filename():
316316
sources.append(os.path.join(root, filename))
317317

318318
for root, dirnames, filenames in os.walk("tools"):
319-
for ext in (".exe"):
319+
for ext in (".exe", ".py"):
320320
for filename in fnmatch.filter(filenames, "*" + ext):
321321
sources.append(os.path.join(root, filename))
322322

src/runtime/assemblymanager.cs

+112-29
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
using System;
2-
using System.IO;
32
using System.Collections;
4-
using System.Collections.Specialized;
3+
using System.IO;
4+
using System.Collections.Concurrent;
55
using System.Collections.Generic;
66
using System.Diagnostics;
77
using System.Reflection;
8-
using System.Reflection.Emit;
8+
using System.Threading;
99

1010
namespace Python.Runtime
1111
{
@@ -15,12 +15,16 @@ namespace Python.Runtime
1515
/// </summary>
1616
internal class AssemblyManager
1717
{
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;
1921
//static Dictionary<string, Dictionary<string, string>> generics;
2022
static AssemblyLoadEventHandler lhandler;
2123
static ResolveEventHandler rhandler;
24+
// updated only under GIL?
2225
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;
2428
internal static List<string> pypath;
2529

2630
private AssemblyManager()
@@ -36,10 +40,10 @@ private AssemblyManager()
3640
internal static void Initialize()
3741
{
3842
namespaces = new
39-
Dictionary<string, Dictionary<Assembly, string>>(32);
43+
ConcurrentDictionary<string, ConcurrentDictionary<Assembly, string>>();
4044
probed = new Dictionary<string, int>(32);
4145
//generics = new Dictionary<string, Dictionary<string, string>>();
42-
assemblies = new List<Assembly>(16);
46+
assemblies = new AssemblyList(16);
4347
pypath = new List<string>(16);
4448

4549
AppDomain domain = AppDomain.CurrentDomain;
@@ -105,9 +109,8 @@ static void AssemblyLoadHandler(Object ob, AssemblyLoadEventArgs args)
105109
static Assembly ResolveHandler(Object ob, ResolveEventArgs args)
106110
{
107111
string name = args.Name.ToLower();
108-
for (int i = 0; i < assemblies.Count; i++)
112+
foreach (Assembly a in assemblies)
109113
{
110-
Assembly a = (Assembly)assemblies[i];
111114
string full = a.FullName.ToLower();
112115
if (full.StartsWith(name))
113116
{
@@ -266,9 +269,8 @@ public static Assembly LoadAssemblyFullPath(string name)
266269

267270
public static Assembly FindLoadedAssembly(string name)
268271
{
269-
for (int i = 0; i < assemblies.Count; i++)
272+
foreach (Assembly a in assemblies)
270273
{
271-
Assembly a = (Assembly)assemblies[i];
272274
if (a.GetName().Name == name)
273275
{
274276
return a;
@@ -295,15 +297,15 @@ public static bool LoadImplicit(string name, bool warn = true)
295297
bool loaded = false;
296298
string s = "";
297299
Assembly lastAssembly = null;
298-
HashSet<Assembly> assemblies = null;
300+
HashSet<Assembly> assembliesSet = null;
299301
for (int i = 0; i < names.Length; i++)
300302
{
301303
s = (i == 0) ? names[0] : s + "." + names[i];
302304
if (!probed.ContainsKey(s))
303305
{
304-
if (assemblies == null)
306+
if (assembliesSet == null)
305307
{
306-
assemblies = new HashSet<Assembly>(AppDomain.CurrentDomain.GetAssemblies());
308+
assembliesSet = new HashSet<Assembly>(AppDomain.CurrentDomain.GetAssemblies());
307309
}
308310
Assembly a = FindLoadedAssembly(s);
309311
if (a == null)
@@ -314,7 +316,7 @@ public static bool LoadImplicit(string name, bool warn = true)
314316
{
315317
a = LoadAssembly(s);
316318
}
317-
if (a != null && !assemblies.Contains(a))
319+
if (a != null && !assembliesSet.Contains(a))
318320
{
319321
loaded = true;
320322
lastAssembly = a;
@@ -362,16 +364,13 @@ internal static void ScanAssembly(Assembly assembly)
362364
for (int n = 0; n < names.Length; n++)
363365
{
364366
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>());
369368
}
370369
}
371370

372-
if (ns != null && !namespaces[ns].ContainsKey(assembly))
371+
if (ns != null)
373372
{
374-
namespaces[ns].Add(assembly, String.Empty);
373+
namespaces[ns].TryAdd(assembly, String.Empty);
375374
}
376375

377376
if (ns != null && t.IsGenericTypeDefinition)
@@ -383,14 +382,12 @@ internal static void ScanAssembly(Assembly assembly)
383382

384383
public static AssemblyName[] ListAssemblies()
385384
{
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)
389387
{
390-
assembly = assemblies[i];
391-
names.SetValue(assembly.GetName(), i);
388+
names.Add(assembly.GetName());
392389
}
393-
return names;
390+
return names.ToArray();
394391
}
395392

396393
//===================================================================
@@ -471,9 +468,8 @@ public static List<string> GetNames(string nsname)
471468

472469
public static Type LookupType(string qname)
473470
{
474-
for (int i = 0; i < assemblies.Count; i++)
471+
foreach (Assembly assembly in assemblies)
475472
{
476-
Assembly assembly = (Assembly)assemblies[i];
477473
Type type = assembly.GetType(qname);
478474
if (type != null)
479475
{
@@ -482,5 +478,92 @@ public static Type LookupType(string qname)
482478
}
483479
return null;
484480
}
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+
}
485568
}
486569
}

src/testing/Python.Test.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
<Compile Include="indexertest.cs" />
123123
<Compile Include="interfacetest.cs" />
124124
<Compile Include="methodtest.cs" />
125+
<Compile Include="moduletest.cs" />
125126
<Compile Include="propertytest.cs" />
126127
<Compile Include="threadtest.cs" />
127128
<Compile Include="doctest.cs" />

src/testing/moduletest.cs

+25
Original file line numberDiff line numberDiff 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+
}

src/tests/test_module.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,11 @@ def testModuleInterface(self):
6565
import System
6666
self.assertEquals(type(System.__dict__), type({}))
6767
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)
7073
self.assertTrue(System.__doc__.startswith("Namespace containing types from the following assemblies:"))
7174
self.assertTrue(self.isCLRClass(System.String))
7275
self.assertTrue(self.isCLRClass(System.Int32))
@@ -353,6 +356,22 @@ def test_ClrAddReference(self):
353356
self.assertRaises(FileNotFoundException,
354357
AddReference, "somethingtotallysilly")
355358

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+
356375

357376
def test_suite():
358377
return unittest.makeSuite(ModuleTests)

0 commit comments

Comments
 (0)