7
7
using System . Linq ;
8
8
using System . Reflection ;
9
9
using System . Threading ;
10
+ using System . Threading . Tasks ;
10
11
11
12
namespace Python . Runtime
12
13
{
@@ -25,18 +26,17 @@ internal class AssemblyManager
25
26
// than it can end up referring to assemblies that are already unloaded (default behavior after unload appDomain -
26
27
// unless LoaderOptimization.MultiDomain is used);
27
28
// 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 ;
33
35
34
36
// updated only under GIL?
35
37
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 > > ( ) ;
40
40
41
41
private AssemblyManager ( )
42
42
{
@@ -49,30 +49,34 @@ private AssemblyManager()
49
49
/// </summary>
50
50
internal static void Initialize ( )
51
51
{
52
- assemblies = new ConcurrentQueue < Assembly > ( ) ;
53
- pypath = new List < string > ( 16 ) ;
54
-
55
52
AppDomain domain = AppDomain . CurrentDomain ;
56
53
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 ;
62
56
63
- Assembly [ ] items = domain . GetAssemblies ( ) ;
64
- foreach ( Assembly a in items )
57
+ foreach ( var assembly in domain . GetAssemblies ( ) )
65
58
{
66
59
try
67
60
{
68
- ScanAssembly ( a ) ;
69
- assemblies . Enqueue ( a ) ;
61
+ LaunchAssemblyLoader ( assembly ) ;
70
62
}
71
63
catch ( Exception ex )
72
64
{
73
- Debug . WriteLine ( "Error scanning assembly {0}. {1}" , a , ex ) ;
65
+ Debug . WriteLine ( "Error scanning assembly {0}. {1}" , assembly , ex ) ;
74
66
}
75
67
}
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 ) ;
76
80
}
77
81
78
82
@@ -82,8 +86,8 @@ internal static void Initialize()
82
86
internal static void Shutdown ( )
83
87
{
84
88
AppDomain domain = AppDomain . CurrentDomain ;
85
- domain . AssemblyLoad -= lhandler ;
86
- domain . AssemblyResolve -= rhandler ;
89
+ domain . AssemblyLoad -= AssemblyLoadHandler ;
90
+ domain . AssemblyResolve -= ResolveHandler ;
87
91
}
88
92
89
93
@@ -97,8 +101,35 @@ internal static void Shutdown()
97
101
private static void AssemblyLoadHandler ( object ob , AssemblyLoadEventArgs args )
98
102
{
99
103
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
+ }
102
133
}
103
134
104
135
@@ -112,12 +143,12 @@ private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args)
112
143
private static Assembly ResolveHandler ( object ob , ResolveEventArgs args )
113
144
{
114
145
string name = args . Name . ToLower ( ) ;
115
- foreach ( Assembly a in assemblies )
146
+ foreach ( var assembly in assemblies )
116
147
{
117
- string full = a . FullName . ToLower ( ) ;
148
+ var full = assembly . FullName . ToLower ( ) ;
118
149
if ( full . StartsWith ( name ) )
119
150
{
120
- return a ;
151
+ return assembly ;
121
152
}
122
153
}
123
154
return LoadAssemblyPath ( args . Name ) ;
@@ -139,19 +170,61 @@ internal static void UpdatePath()
139
170
{
140
171
BorrowedReference list = Runtime . PySys_GetObject ( "path" ) ;
141
172
var count = Runtime . PyList_Size ( list ) ;
173
+ var sep = Path . DirectorySeparatorChar ;
174
+
142
175
if ( count != pypath . Count )
143
176
{
144
177
pypath . Clear ( ) ;
145
178
probed . Clear ( ) ;
179
+ // add first the current path
180
+ pypath . Add ( "" ) ;
146
181
for ( var i = 0 ; i < count ; i ++ )
147
182
{
148
183
BorrowedReference item = Runtime . PyList_GetItem ( list , i ) ;
149
184
string path = Runtime . GetManagedString ( item ) ;
150
185
if ( path != null )
151
186
{
152
- pypath . Add ( path ) ;
187
+ pypath . Add ( path == string . Empty ? path : path + sep ) ;
153
188
}
154
189
}
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
+ } ) ;
155
228
}
156
229
}
157
230
@@ -163,30 +236,18 @@ internal static void UpdatePath()
163
236
/// </summary>
164
237
public static string FindAssembly ( string name )
165
238
{
166
- char sep = Path . DirectorySeparatorChar ;
167
-
168
- foreach ( string head in pypath )
239
+ foreach ( var kvp in filesInPath )
169
240
{
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 ) )
182
243
{
183
- return temp ;
244
+ return kvp . Key + dll ;
184
245
}
185
246
186
- temp = path + " .exe";
187
- if ( File . Exists ( temp ) )
247
+ var executable = $ " { name } .exe";
248
+ if ( kvp . Value . Contains ( executable ) )
188
249
{
189
- return temp ;
250
+ return kvp . Key + executable ;
190
251
}
191
252
}
192
253
return null ;
@@ -242,14 +303,8 @@ public static Assembly LoadAssemblyFullPath(string name)
242
303
/// </summary>
243
304
public static Assembly FindLoadedAssembly ( string name )
244
305
{
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 ;
253
308
}
254
309
255
310
/// <summary>
@@ -264,16 +319,10 @@ internal static void ScanAssembly(Assembly assembly)
264
319
{
265
320
return ;
266
321
}
267
-
268
- // skip this assembly, it causes 'GetTypes' call to hang
269
- if ( assembly . FullName . StartsWith ( "System.Windows.Forms" ) )
270
- {
271
- return ;
272
- }
273
-
274
322
// A couple of things we want to do here: first, we want to
275
323
// gather a list of all of the namespaces contributed to by
276
324
// the assembly.
325
+
277
326
foreach ( Type t in GetTypes ( assembly ) )
278
327
{
279
328
string ns = t . Namespace ?? "" ;
@@ -284,13 +333,13 @@ internal static void ScanAssembly(Assembly assembly)
284
333
for ( var n = 0 ; n < names . Length ; n ++ )
285
334
{
286
335
s = n == 0 ? names [ 0 ] : s + "." + names [ n ] ;
287
- namespaces . TryAdd ( s , new ConcurrentDictionary < Assembly , string > ( ) ) ;
336
+ namespaces . TryAdd ( s , new ConcurrentDictionary < Assembly , byte > ( ) ) ;
288
337
}
289
338
}
290
339
291
340
if ( ns != null )
292
341
{
293
- namespaces [ ns ] . TryAdd ( assembly , string . Empty ) ;
342
+ namespaces [ ns ] . TryAdd ( assembly , 1 ) ;
294
343
}
295
344
296
345
if ( ns != null && t . IsGenericTypeDefinition )
0 commit comments