Skip to content

Commit ee3395e

Browse files
authored
Merge pull request #38 from Martin-Molinero/initialization-performance-hang-fixes
Initialization hang fix
2 parents b97e38e + 3fef624 commit ee3395e

File tree

10 files changed

+174
-173
lines changed

10 files changed

+174
-173
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 1.0.5.26
2+
current_version = 1.0.5.28
33
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.(?P<release>[a-z]+)(?P<dev>\d+))?
44
serialize =
55
{major}.{minor}.{patch}.{release}{dev}

conda.recipe/meta.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package:
22
name: pythonnet
3-
version: "1.0.5.26"
3+
version: "1.0.5.28"
44

55
build:
66
skip: True # [not win]

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ def run(self):
485485

486486
setup(
487487
name="pythonnet",
488-
version="1.0.5.26",
488+
version="1.0.5.28",
489489
description=".Net and Mono integration for Python",
490490
url='https://pythonnet.github.io/',
491491
license='MIT',

src/SharedAssemblyInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@
2525
// Version Information. Keeping it simple. May need to revisit for Nuget
2626
// See: https://codingforsmarties.wordpress.com/2016/01/21/how-to-version-assemblies-destined-for-nuget/
2727
// AssemblyVersion can only be numeric
28-
[assembly: AssemblyVersion("1.0.5.26")]
28+
[assembly: AssemblyVersion("1.0.5.28")]

src/clrmodule/ClrModule.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static void initclr()
5353
{
5454
#if USE_PYTHON_RUNTIME_VERSION
5555
// Has no effect until SNK works. Keep updated anyways.
56-
Version = new Version("1.0.5.26"),
56+
Version = new Version("1.0.5.28"),
5757
#endif
5858
CultureInfo = CultureInfo.InvariantCulture
5959
};

src/runtime/assemblymanager.cs

Lines changed: 89 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
using System;
2-
using System.Collections;
32
using System.Collections.Concurrent;
43
using System.Collections.Generic;
54
using System.Diagnostics;
65
using System.IO;
76
using System.Reflection;
87
using System.Threading;
8+
using System.Threading.Tasks;
99

1010
namespace Python.Runtime
1111
{
@@ -17,17 +17,15 @@ internal class AssemblyManager
1717
{
1818
// modified from event handlers below, potentially triggered from different .NET threads
1919
// therefore this should be a ConcurrentDictionary
20-
private static ConcurrentDictionary<string, ConcurrentDictionary<Assembly, string>> namespaces;
21-
//private static Dictionary<string, Dictionary<string, string>> generics;
22-
private static AssemblyLoadEventHandler lhandler;
23-
private static ResolveEventHandler rhandler;
20+
private static ConcurrentDictionary<string, ConcurrentDictionary<Assembly, byte>> namespaces;
21+
private static ConcurrentDictionary<string, Assembly> assembliesNamesCache;
22+
private static ConcurrentDictionary<string, Type> lookupTypeCache;
23+
private static ConcurrentQueue<Assembly> assemblies;
24+
private static int pendingAssemblies;
2425

2526
// updated only under GIL?
2627
private static Dictionary<string, int> probed;
27-
28-
// modified from event handlers below, potentially triggered from different .NET threads
29-
private static ConcurrentQueue<Assembly> assemblies;
30-
internal static List<string> pypath;
28+
private static List<string> pypath;
3129

3230
private AssemblyManager()
3331
{
@@ -40,33 +38,41 @@ private AssemblyManager()
4038
/// </summary>
4139
internal static void Initialize()
4240
{
43-
namespaces = new ConcurrentDictionary<string, ConcurrentDictionary<Assembly, string>>();
41+
namespaces = new ConcurrentDictionary<string, ConcurrentDictionary<Assembly, byte>>();
42+
assembliesNamesCache = new ConcurrentDictionary<string, Assembly>();
43+
lookupTypeCache = new ConcurrentDictionary<string, Type>();
4444
probed = new Dictionary<string, int>(32);
45-
//generics = new Dictionary<string, Dictionary<string, string>>();
4645
assemblies = new ConcurrentQueue<Assembly>();
4746
pypath = new List<string>(16);
4847

4948
AppDomain domain = AppDomain.CurrentDomain;
5049

51-
lhandler = new AssemblyLoadEventHandler(AssemblyLoadHandler);
52-
domain.AssemblyLoad += lhandler;
53-
54-
rhandler = new ResolveEventHandler(ResolveHandler);
55-
domain.AssemblyResolve += rhandler;
50+
domain.AssemblyLoad += AssemblyLoadHandler;
51+
domain.AssemblyResolve += ResolveHandler;
5652

57-
Assembly[] items = domain.GetAssemblies();
58-
foreach (Assembly a in items)
53+
foreach (var assembly in domain.GetAssemblies())
5954
{
6055
try
6156
{
62-
ScanAssembly(a);
63-
assemblies.Enqueue(a);
57+
LaunchAssemblyLoader(assembly);
6458
}
6559
catch (Exception ex)
6660
{
67-
Debug.WriteLine("Error scanning assembly {0}. {1}", a, ex);
61+
Debug.WriteLine("Error scanning assembly {0}. {1}", assembly, ex);
6862
}
6963
}
64+
65+
var safeCount = 0;
66+
// lets wait until all assemblies are loaded
67+
do
68+
{
69+
if (safeCount++ > 200)
70+
{
71+
throw new TimeoutException("Timeout while waiting for assemblies to load");
72+
}
73+
74+
Thread.Sleep(50);
75+
} while (pendingAssemblies > 0);
7076
}
7177

7278

@@ -76,8 +82,8 @@ internal static void Initialize()
7682
internal static void Shutdown()
7783
{
7884
AppDomain domain = AppDomain.CurrentDomain;
79-
domain.AssemblyLoad -= lhandler;
80-
domain.AssemblyResolve -= rhandler;
85+
domain.AssemblyLoad -= AssemblyLoadHandler;
86+
domain.AssemblyResolve -= ResolveHandler;
8187
}
8288

8389

@@ -88,13 +94,41 @@ internal static void Shutdown()
8894
/// so that we can know about assemblies that get loaded after the
8995
/// Python runtime is initialized.
9096
/// </summary>
97+
/// <remarks>Scanning assemblies here caused internal hangs when calling
98+
/// <see cref="Assembly.GetTypes"/></remarks>
9199
private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args)
92100
{
93101
Assembly assembly = args.LoadedAssembly;
94-
assemblies.Enqueue(assembly);
95-
ScanAssembly(assembly);
102+
LaunchAssemblyLoader(assembly);
96103
}
97104

105+
/// <summary>
106+
/// Launches a new task that will load the provided assembly
107+
/// </summary>
108+
private static void LaunchAssemblyLoader(Assembly assembly)
109+
{
110+
if (assembly != null)
111+
{
112+
Interlocked.Increment(ref pendingAssemblies);
113+
Task.Factory.StartNew(() =>
114+
{
115+
try
116+
{
117+
if (assembliesNamesCache.TryAdd(assembly.GetName().Name, assembly))
118+
{
119+
assemblies.Enqueue(assembly);
120+
ScanAssembly(assembly);
121+
}
122+
}
123+
catch
124+
{
125+
// pass
126+
}
127+
128+
Interlocked.Decrement(ref pendingAssemblies);
129+
});
130+
}
131+
}
98132

99133
/// <summary>
100134
/// Event handler for assembly resolve events. This is needed because
@@ -106,12 +140,12 @@ private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args)
106140
private static Assembly ResolveHandler(object ob, ResolveEventArgs args)
107141
{
108142
string name = args.Name.ToLower();
109-
foreach (Assembly a in assemblies)
143+
foreach (var assembly in assemblies)
110144
{
111-
string full = a.FullName.ToLower();
145+
var full = assembly.FullName.ToLower();
112146
if (full.StartsWith(name))
113147
{
114-
return a;
148+
return assembly;
115149
}
116150
}
117151
return LoadAssemblyPath(args.Name);
@@ -133,54 +167,44 @@ internal static void UpdatePath()
133167
{
134168
IntPtr list = Runtime.PySys_GetObject("path");
135169
int count = Runtime.PyList_Size(list);
170+
var sep = Path.DirectorySeparatorChar;
171+
136172
if (count != pypath.Count)
137173
{
138174
pypath.Clear();
139175
probed.Clear();
176+
// add first the current path
177+
pypath.Add("");
140178
for (var i = 0; i < count; i++)
141179
{
142180
IntPtr item = Runtime.PyList_GetItem(list, i);
143181
string path = Runtime.GetManagedString(item);
144182
if (path != null)
145183
{
146-
pypath.Add(path);
184+
pypath.Add(path == string.Empty ? path : path + sep);
147185
}
148186
}
149187
}
150188
}
151189

152-
153190
/// <summary>
154191
/// Given an assembly name, try to find this assembly file using the
155192
/// PYTHONPATH. If not found, return null to indicate implicit load
156193
/// using standard load semantics (app base directory then GAC, etc.)
157194
/// </summary>
158195
public static string FindAssembly(string name)
159196
{
160-
char sep = Path.DirectorySeparatorChar;
161-
162-
foreach (string head in pypath)
197+
foreach (var head in pypath)
163198
{
164-
string path;
165-
if (head == null || head.Length == 0)
166-
{
167-
path = name;
168-
}
169-
else
199+
var dll = $"{head}{name}.dll";
200+
if (File.Exists(dll))
170201
{
171-
path = head + sep + name;
202+
return dll;
172203
}
173-
174-
string temp = path + ".dll";
175-
if (File.Exists(temp))
176-
{
177-
return temp;
178-
}
179-
180-
temp = path + ".exe";
181-
if (File.Exists(temp))
204+
var executable = $"{head}{name}.exe";
205+
if (File.Exists(executable))
182206
{
183-
return temp;
207+
return executable;
184208
}
185209
}
186210
return null;
@@ -200,10 +224,7 @@ public static Assembly LoadAssembly(string name)
200224
}
201225
catch (Exception)
202226
{
203-
//if (!(e is System.IO.FileNotFoundException))
204-
//{
205-
// throw;
206-
//}
227+
// ignored
207228
}
208229
return assembly;
209230
}
@@ -214,7 +235,7 @@ public static Assembly LoadAssembly(string name)
214235
/// </summary>
215236
public static Assembly LoadAssemblyPath(string name)
216237
{
217-
string path = FindAssembly(name);
238+
var path = FindAssembly(name);
218239
Assembly assembly = null;
219240
if (path != null)
220241
{
@@ -224,6 +245,7 @@ public static Assembly LoadAssemblyPath(string name)
224245
}
225246
catch (Exception)
226247
{
248+
// ignored
227249
}
228250
}
229251
return assembly;
@@ -251,6 +273,7 @@ public static Assembly LoadAssemblyFullPath(string name)
251273
}
252274
catch (Exception)
253275
{
276+
// ignored
254277
}
255278
}
256279
}
@@ -262,14 +285,8 @@ public static Assembly LoadAssemblyFullPath(string name)
262285
/// </summary>
263286
public static Assembly FindLoadedAssembly(string name)
264287
{
265-
foreach (Assembly a in assemblies)
266-
{
267-
if (a.GetName().Name == name)
268-
{
269-
return a;
270-
}
271-
}
272-
return null;
288+
Assembly result;
289+
return assembliesNamesCache.TryGetValue(name, out result) ? result : null;
273290
}
274291

275292
/// <summary>
@@ -306,10 +323,6 @@ public static bool LoadImplicit(string name, bool warn = true)
306323
{
307324
a = LoadAssemblyPath(s);
308325
}
309-
if (a == null)
310-
{
311-
a = LoadAssembly(s);
312-
}
313326
if (a != null && !assembliesSet.Contains(a))
314327
{
315328
loaded = true;
@@ -344,12 +357,6 @@ internal static void ScanAssembly(Assembly assembly)
344357
// gather a list of all of the namespaces contributed to by
345358
// the assembly.
346359

347-
// skip this assembly, it causes 'GetTypes' call to hang
348-
if (assembly.FullName.StartsWith("System.Windows.Forms"))
349-
{
350-
return;
351-
}
352-
353360
Type[] types = assembly.GetTypes();
354361
foreach (Type t in types)
355362
{
@@ -361,13 +368,13 @@ internal static void ScanAssembly(Assembly assembly)
361368
for (var n = 0; n < names.Length; n++)
362369
{
363370
s = n == 0 ? names[0] : s + "." + names[n];
364-
namespaces.TryAdd(s, new ConcurrentDictionary<Assembly, string>());
371+
namespaces.TryAdd(s, new ConcurrentDictionary<Assembly, byte>());
365372
}
366373
}
367374

368375
if (ns != null)
369376
{
370-
namespaces[ns].TryAdd(assembly, string.Empty);
377+
namespaces[ns].TryAdd(assembly, 1);
371378
}
372379

373380
if (ns != null && t.IsGenericTypeDefinition)
@@ -457,11 +464,17 @@ public static List<string> GetNames(string nsname)
457464
/// </summary>
458465
public static Type LookupType(string qname)
459466
{
467+
Type type;
468+
if (lookupTypeCache.TryGetValue(qname, out type))
469+
{
470+
return type;
471+
}
460472
foreach (Assembly assembly in assemblies)
461473
{
462-
Type type = assembly.GetType(qname);
474+
type = assembly.GetType(qname);
463475
if (type != null)
464476
{
477+
lookupTypeCache[qname] = type;
465478
return type;
466479
}
467480
}

src/runtime/classmanager.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,8 @@ static ClassManager()
4040
/// </summary>
4141
internal static ClassBase GetClass(Type type)
4242
{
43-
ClassBase cb = null;
44-
cache.TryGetValue(type, out cb);
45-
if (cb != null)
43+
ClassBase cb;
44+
if (cache.TryGetValue(type, out cb))
4645
{
4746
return cb;
4847
}

0 commit comments

Comments
 (0)