Skip to content

Commit c362816

Browse files
committed
Reflect PR #38 Partial: Assembly Manager Improvements
1 parent 5839d21 commit c362816

File tree

1 file changed

+115
-66
lines changed

1 file changed

+115
-66
lines changed

src/runtime/assemblymanager.cs

+115-66
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Linq;
88
using System.Reflection;
99
using System.Threading;
10+
using System.Threading.Tasks;
1011

1112
namespace Python.Runtime
1213
{
@@ -25,18 +26,17 @@ internal class AssemblyManager
2526
// than it can end up referring to assemblies that are already unloaded (default behavior after unload appDomain -
2627
// unless LoaderOptimization.MultiDomain is used);
2728
// So for multidomain support it is better to have the dict. recreated for each app-domain initialization
28-
private static ConcurrentDictionary<string, ConcurrentDictionary<Assembly, string>> namespaces =
29-
new ConcurrentDictionary<string, ConcurrentDictionary<Assembly, string>>();
30-
//private static Dictionary<string, Dictionary<string, string>> generics;
31-
private static AssemblyLoadEventHandler lhandler;
32-
private static ResolveEventHandler rhandler;
29+
private static ConcurrentDictionary<string, ConcurrentDictionary<Assembly, byte>> namespaces =
30+
new ConcurrentDictionary<string, ConcurrentDictionary<Assembly, byte>>();
31+
private static ConcurrentDictionary<string, Assembly> assembliesNamesCache =
32+
new ConcurrentDictionary<string, Assembly>();
33+
private static ConcurrentQueue<Assembly> assemblies = new ConcurrentQueue<Assembly>();
34+
private static int pendingAssemblies;
3335

3436
// updated only under GIL?
3537
private static Dictionary<string, int> probed = new Dictionary<string, int>(32);
36-
37-
// modified from event handlers below, potentially triggered from different .NET threads
38-
private static ConcurrentQueue<Assembly> assemblies;
39-
internal static List<string> pypath;
38+
private static List<string> pypath = new List<string>(16);
39+
private static Dictionary<string, HashSet<string>> filesInPath = new Dictionary<string, HashSet<string>>();
4040

4141
private AssemblyManager()
4242
{
@@ -49,30 +49,34 @@ private AssemblyManager()
4949
/// </summary>
5050
internal static void Initialize()
5151
{
52-
assemblies = new ConcurrentQueue<Assembly>();
53-
pypath = new List<string>(16);
54-
5552
AppDomain domain = AppDomain.CurrentDomain;
5653

57-
lhandler = new AssemblyLoadEventHandler(AssemblyLoadHandler);
58-
domain.AssemblyLoad += lhandler;
59-
60-
rhandler = new ResolveEventHandler(ResolveHandler);
61-
domain.AssemblyResolve += rhandler;
54+
domain.AssemblyLoad += AssemblyLoadHandler;
55+
domain.AssemblyResolve += ResolveHandler;
6256

63-
Assembly[] items = domain.GetAssemblies();
64-
foreach (Assembly a in items)
57+
foreach (var assembly in domain.GetAssemblies())
6558
{
6659
try
6760
{
68-
ScanAssembly(a);
69-
assemblies.Enqueue(a);
61+
LaunchAssemblyLoader(assembly);
7062
}
7163
catch (Exception ex)
7264
{
73-
Debug.WriteLine("Error scanning assembly {0}. {1}", a, ex);
65+
Debug.WriteLine("Error scanning assembly {0}. {1}", assembly, ex);
7466
}
7567
}
68+
69+
var safeCount = 0;
70+
// lets wait until all assemblies are loaded
71+
do
72+
{
73+
if (safeCount++ > 200)
74+
{
75+
throw new TimeoutException("Timeout while waiting for assemblies to load");
76+
}
77+
78+
Thread.Sleep(50);
79+
} while (pendingAssemblies > 0);
7680
}
7781

7882

@@ -82,8 +86,8 @@ internal static void Initialize()
8286
internal static void Shutdown()
8387
{
8488
AppDomain domain = AppDomain.CurrentDomain;
85-
domain.AssemblyLoad -= lhandler;
86-
domain.AssemblyResolve -= rhandler;
89+
domain.AssemblyLoad -= AssemblyLoadHandler;
90+
domain.AssemblyResolve -= ResolveHandler;
8791
}
8892

8993

@@ -97,8 +101,35 @@ internal static void Shutdown()
97101
private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args)
98102
{
99103
Assembly assembly = args.LoadedAssembly;
100-
assemblies.Enqueue(assembly);
101-
ScanAssembly(assembly);
104+
LaunchAssemblyLoader(assembly);
105+
}
106+
107+
/// <summary>
108+
/// Launches a new task that will load the provided assembly
109+
/// </summary>
110+
private static void LaunchAssemblyLoader(Assembly assembly)
111+
{
112+
if (assembly != null)
113+
{
114+
Interlocked.Increment(ref pendingAssemblies);
115+
Task.Factory.StartNew(() =>
116+
{
117+
try
118+
{
119+
if (assembliesNamesCache.TryAdd(assembly.GetName().Name, assembly))
120+
{
121+
assemblies.Enqueue(assembly);
122+
ScanAssembly(assembly);
123+
}
124+
}
125+
catch
126+
{
127+
// pass
128+
}
129+
130+
Interlocked.Decrement(ref pendingAssemblies);
131+
});
132+
}
102133
}
103134

104135

@@ -112,12 +143,12 @@ private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args)
112143
private static Assembly ResolveHandler(object ob, ResolveEventArgs args)
113144
{
114145
string name = args.Name.ToLower();
115-
foreach (Assembly a in assemblies)
146+
foreach (var assembly in assemblies)
116147
{
117-
string full = a.FullName.ToLower();
148+
var full = assembly.FullName.ToLower();
118149
if (full.StartsWith(name))
119150
{
120-
return a;
151+
return assembly;
121152
}
122153
}
123154
return LoadAssemblyPath(args.Name);
@@ -139,19 +170,61 @@ internal static void UpdatePath()
139170
{
140171
BorrowedReference list = Runtime.PySys_GetObject("path");
141172
var count = Runtime.PyList_Size(list);
173+
var sep = Path.DirectorySeparatorChar;
174+
142175
if (count != pypath.Count)
143176
{
144177
pypath.Clear();
145178
probed.Clear();
179+
// add first the current path
180+
pypath.Add("");
146181
for (var i = 0; i < count; i++)
147182
{
148183
BorrowedReference item = Runtime.PyList_GetItem(list, i);
149184
string path = Runtime.GetManagedString(item);
150185
if (path != null)
151186
{
152-
pypath.Add(path);
187+
pypath.Add(path == string.Empty ? path : path + sep);
153188
}
154189
}
190+
191+
// for performance we will search for all files in each directory in the path once
192+
Parallel.ForEach(pypath.Where(s =>
193+
{
194+
try
195+
{
196+
lock (filesInPath)
197+
{
198+
// only search in directory if it exists and we haven't already analyzed it
199+
return Directory.Exists(s) && !filesInPath.ContainsKey(s);
200+
}
201+
}
202+
catch
203+
{
204+
// just in case, file operations can throw
205+
}
206+
return false;
207+
}), path =>
208+
{
209+
var container = new HashSet<string>();
210+
try
211+
{
212+
foreach (var file in Directory.EnumerateFiles(path)
213+
.Where(file => file.EndsWith(".dll") || file.EndsWith(".exe")))
214+
{
215+
container.Add(Path.GetFileName(file));
216+
}
217+
}
218+
catch
219+
{
220+
// just in case, file operations can throw
221+
}
222+
223+
lock (filesInPath)
224+
{
225+
filesInPath[path] = container;
226+
}
227+
});
155228
}
156229
}
157230

@@ -163,30 +236,18 @@ internal static void UpdatePath()
163236
/// </summary>
164237
public static string FindAssembly(string name)
165238
{
166-
char sep = Path.DirectorySeparatorChar;
167-
168-
foreach (string head in pypath)
239+
foreach (var kvp in filesInPath)
169240
{
170-
string path;
171-
if (head == null || head.Length == 0)
172-
{
173-
path = name;
174-
}
175-
else
176-
{
177-
path = head + sep + name;
178-
}
179-
180-
string temp = path + ".dll";
181-
if (File.Exists(temp))
241+
var dll = $"{name}.dll";
242+
if (kvp.Value.Contains(dll))
182243
{
183-
return temp;
244+
return kvp.Key + dll;
184245
}
185246

186-
temp = path + ".exe";
187-
if (File.Exists(temp))
247+
var executable = $"{name}.exe";
248+
if (kvp.Value.Contains(executable))
188249
{
189-
return temp;
250+
return kvp.Key + executable;
190251
}
191252
}
192253
return null;
@@ -242,14 +303,8 @@ public static Assembly LoadAssemblyFullPath(string name)
242303
/// </summary>
243304
public static Assembly FindLoadedAssembly(string name)
244305
{
245-
foreach (Assembly a in assemblies)
246-
{
247-
if (a.GetName().Name == name)
248-
{
249-
return a;
250-
}
251-
}
252-
return null;
306+
Assembly result;
307+
return assembliesNamesCache.TryGetValue(name, out result) ? result : null;
253308
}
254309

255310
/// <summary>
@@ -264,16 +319,10 @@ internal static void ScanAssembly(Assembly assembly)
264319
{
265320
return;
266321
}
267-
268-
// skip this assembly, it causes 'GetTypes' call to hang
269-
if (assembly.FullName.StartsWith("System.Windows.Forms"))
270-
{
271-
return;
272-
}
273-
274322
// A couple of things we want to do here: first, we want to
275323
// gather a list of all of the namespaces contributed to by
276324
// the assembly.
325+
277326
foreach (Type t in GetTypes(assembly))
278327
{
279328
string ns = t.Namespace ?? "";
@@ -284,13 +333,13 @@ internal static void ScanAssembly(Assembly assembly)
284333
for (var n = 0; n < names.Length; n++)
285334
{
286335
s = n == 0 ? names[0] : s + "." + names[n];
287-
namespaces.TryAdd(s, new ConcurrentDictionary<Assembly, string>());
336+
namespaces.TryAdd(s, new ConcurrentDictionary<Assembly, byte>());
288337
}
289338
}
290339

291340
if (ns != null)
292341
{
293-
namespaces[ns].TryAdd(assembly, string.Empty);
342+
namespaces[ns].TryAdd(assembly, 1);
294343
}
295344

296345
if (ns != null && t.IsGenericTypeDefinition)

0 commit comments

Comments
 (0)