diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index d44f5f666..74720e1a6 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -110,16 +110,15 @@ private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args) /// private static Assembly ResolveHandler(object ob, ResolveEventArgs args) { - string name = args.Name.ToLower(); - foreach (Assembly a in assemblies) + var name = new AssemblyName(args.Name); + foreach (var alreadyLoaded in assemblies) { - string full = a.FullName.ToLower(); - if (full.StartsWith(name)) + if (AssemblyName.ReferenceMatchesDefinition(name, alreadyLoaded.GetName())) { - return a; + return alreadyLoaded; } } - return LoadAssemblyPath(args.Name); + return LoadAssemblyPath(name.Name); } @@ -154,6 +153,17 @@ internal static void UpdatePath() } } + /// + /// Given an assembly name, try to find this assembly file using the + /// PYTHONPATH. If not found, return null to indicate implicit load + /// using standard load semantics (app base directory then GAC, etc.) + /// + public static string FindAssembly(AssemblyName name) + { + if (name is null) throw new ArgumentNullException(nameof(name)); + + return FindAssembly(name.Name); + } /// /// Given an assembly name, try to find this assembly file using the @@ -162,8 +172,13 @@ internal static void UpdatePath() /// public static string FindAssembly(string name) { - char sep = Path.DirectorySeparatorChar; + if (name is null) throw new ArgumentNullException(nameof(name)); + + return FindAssemblyCandidates(name).FirstOrDefault(); + } + static IEnumerable FindAssemblyCandidates(string name) + { foreach (string head in pypath) { string path; @@ -173,22 +188,21 @@ public static string FindAssembly(string name) } else { - path = head + sep + name; + path = Path.Combine(head, name); } string temp = path + ".dll"; if (File.Exists(temp)) { - return temp; + yield return temp; } temp = path + ".exe"; if (File.Exists(temp)) { - return temp; + yield return temp; } } - return null; } @@ -276,7 +290,10 @@ internal static void ScanAssembly(Assembly assembly) for (var n = 0; n < names.Length; n++) { s = n == 0 ? names[0] : s + "." + names[n]; - namespaces.TryAdd(s, new ConcurrentDictionary()); + if (namespaces.TryAdd(s, new ConcurrentDictionary())) + { + ImportHook.AddNamespace(s); + } } } diff --git a/src/runtime/importhook.cs b/src/runtime/importhook.cs index d3592c15d..0feb06b89 100644 --- a/src/runtime/importhook.cs +++ b/src/runtime/importhook.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; +using System.Collections.Concurrent; namespace Python.Runtime { @@ -37,6 +36,9 @@ def find_spec(klass, fullname, paths=None, target=None): if 'clr' not in sys.modules: return None clr = sys.modules['clr'] + + clr._add_pending_namespaces() + if clr._available_namespaces and fullname in clr._available_namespaces: return importlib.machinery.ModuleSpec(fullname, DotNetLoader(), is_package=True) return None @@ -169,12 +171,26 @@ static void TeardownNameSpaceTracking() Runtime.PyDict_SetItemString(root.dict, availableNsKey, Runtime.PyNone); } - public static void AddNamespace(string name) + static readonly ConcurrentQueue addPending = new(); + public static void AddNamespace(string name) => addPending.Enqueue(name); + + internal static int AddPendingNamespaces() + { + int added = 0; + while (addPending.TryDequeue(out string ns)) + { + AddNamespaceWithGIL(ns); + added++; + } + return added; + } + + internal static void AddNamespaceWithGIL(string name) { var pyNs = Runtime.PyString_FromString(name); try { - var nsSet = Runtime.PyDict_GetItemString(new BorrowedReference(root.dict), availableNsKey); + var nsSet = Runtime.PyDict_GetItemString(root.DictRef, availableNsKey); if (!(nsSet.IsNull || nsSet.DangerousGetAddress() == Runtime.PyNone)) { if (Runtime.PySet_Add(nsSet, new BorrowedReference(pyNs)) != 0) diff --git a/src/runtime/moduleobject.cs b/src/runtime/moduleobject.cs index c2614b1d8..569d2e00c 100644 --- a/src/runtime/moduleobject.cs +++ b/src/runtime/moduleobject.cs @@ -535,8 +535,9 @@ public static Assembly AddReference(string name) // method because it may be called from other threads, leading to deadlocks // if it is called while Python code is executing. var currNs = AssemblyManager.GetNamespaces().Except(origNs); - foreach(var ns in currNs){ - ImportHook.AddNamespace(ns); + foreach(var ns in currNs) + { + ImportHook.AddNamespaceWithGIL(ns); } return assembly; } @@ -602,5 +603,9 @@ public static ModuleObject _load_clr_module(PyObject spec) mod = ImportHook.Import(modname.ToString()); return mod; } + + [ModuleFunction] + [ForbidPythonThreads] + public static int _add_pending_namespaces() => ImportHook.AddPendingNamespaces(); } } diff --git a/src/runtime/native/TypeOffset.cs b/src/runtime/native/TypeOffset.cs index b5957a9c7..a3b4f4a24 100644 --- a/src/runtime/native/TypeOffset.cs +++ b/src/runtime/native/TypeOffset.cs @@ -161,7 +161,8 @@ static void ValidateRequiredOffsetsPresent(PropertyInfo[] offsetProperties) "Initialize", "InitializeSlots", "ListAssemblies", - "_load_clr_module", + nameof(CLRModule._load_clr_module), + nameof(CLRModule._add_pending_namespaces), "Release", "Reset", "set_SuppressDocs", diff --git a/tests/test_module.py b/tests/test_module.py index d0378e91e..3737dccf6 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -232,11 +232,11 @@ def test_explicit_assembly_load(): from System.Reflection import Assembly import System, sys - assembly = Assembly.LoadWithPartialName('System.Runtime') + assembly = Assembly.LoadWithPartialName('Microsoft.CSharp') assert assembly is not None - import System.Runtime - assert 'System.Runtime' in sys.modules + import Microsoft.CSharp + assert 'Microsoft.CSharp' in sys.modules assembly = Assembly.LoadWithPartialName('SpamSpamSpamSpamEggsAndSpam') assert assembly is None