77using System . Linq ;
88using System . Reflection ;
99using System . Threading ;
10+ using System . Threading . Tasks ;
1011
1112namespace 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