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