Skip to content

Simplify assembly ResolveHandler, and use official assembly name parsing #1570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 29 additions & 12 deletions src/runtime/assemblymanager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,15 @@ private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args)
/// </summary>
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);
}


Expand Down Expand Up @@ -154,6 +153,17 @@ internal static void UpdatePath()
}
}

/// <summary>
/// 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.)
/// </summary>
public static string FindAssembly(AssemblyName name)
{
if (name is null) throw new ArgumentNullException(nameof(name));

return FindAssembly(name.Name);
}

/// <summary>
/// Given an assembly name, try to find this assembly file using the
Expand All @@ -162,8 +172,13 @@ internal static void UpdatePath()
/// </summary>
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<string> FindAssemblyCandidates(string name)
{
foreach (string head in pypath)
{
string path;
Expand All @@ -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;
}


Expand Down Expand Up @@ -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<Assembly, string>());
if (namespaces.TryAdd(s, new ConcurrentDictionary<Assembly, string>()))
{
ImportHook.AddNamespace(s);
}
}
}

Expand Down
24 changes: 20 additions & 4 deletions src/runtime/importhook.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Collections.Concurrent;

namespace Python.Runtime
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -169,12 +171,26 @@ static void TeardownNameSpaceTracking()
Runtime.PyDict_SetItemString(root.dict, availableNsKey, Runtime.PyNone);
}

public static void AddNamespace(string name)
static readonly ConcurrentQueue<string> 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)
Expand Down
9 changes: 7 additions & 2 deletions src/runtime/moduleobject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
}
}
3 changes: 2 additions & 1 deletion src/runtime/native/TypeOffset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions tests/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down