11using System ;
2- using System . Collections ;
32using System . Collections . Concurrent ;
43using System . Collections . Generic ;
54using System . Diagnostics ;
65using System . IO ;
76using System . Reflection ;
87using System . Threading ;
8+ using System . Threading . Tasks ;
99
1010namespace 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 }
0 commit comments