-
Notifications
You must be signed in to change notification settings - Fork 747
Modernize import hook #1369
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
Modernize import hook #1369
Conversation
200ba3d
to
f502b8e
Compare
src/runtime/importhook.cs
Outdated
var finder_inst = Runtime.PyDict_GetItemString(mod_dict, "finder_inst").DangerousGetAddressOrNull(); | ||
Runtime.XIncref(finder); | ||
var metapath = Runtime.PySys_GetObject("meta_path"); | ||
Runtime.PyList_Append(metapath, finder); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These four lines look fishy: you don't use finder_inst and you are doing an incref on finder for no obvious reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh.. that should be finder_inst
, not finder
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, it makes sense to add an overload for PyList_Append
with proper reference types, then avoid manual refcounting.
Same for PySys_GetObject
- you can rename the original one to Dangerous_PySys_GetObject
to avoid refactoring other call sites.
This is a NIT though. Should come up with a better plan to switch everything.
src/runtime/importhook.cs
Outdated
BorrowedReference fromList = default; | ||
var fromlist = false; | ||
if (num_args >= 4) | ||
var newset = Runtime.PySet_New(new BorrowedReference(IntPtr.Zero)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You Dispose in a finally; should this be a using var
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: I'd replace new BorrowedReference(IntPtr.Zero)
with either BorrowedReference.Null
or simply default
.
src/runtime/importhook.cs
Outdated
{ | ||
if (IsLoadAll(fromList)) | ||
var nsSet = Runtime.PyDict_GetItemString(new BorrowedReference(root.dict), availableNsKey); | ||
if (!nsSet.IsNull || nsSet.DangerousGetAddress() != Runtime.PyNone) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should BorrowedReference have an IsNullOrNone property? It seems likely to be a common idiom.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd keep separate IsNull
and IsNone
. Thought there's already IsNone
.
0f1219b
to
f9a3974
Compare
Implement a meta path loader instead
This reverts commit 4684523.
* Return ModuleObject.pyHandle, do not convert. * Write domain tests generated code to file.
2860632
to
970a189
Compare
Don't piggyback on AssemblyManager's AssemblyLoadHandler method because it may be called from other threads, leading to deadlocks if it is called while Python code is executing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only reviewed in regards to usage of references and exceptions.
@filmor should still sign off on functionality.
src/runtime/BorrowedReference.cs
Outdated
@@ -9,6 +9,7 @@ namespace Python.Runtime | |||
{ | |||
readonly IntPtr pointer; | |||
public bool IsNull => this.pointer == IntPtr.Zero; | |||
public bool IsNone => this.pointer == Runtime.PyNone; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be directly on BorrowedReference
. None
is a concept of the currently running runtime, and BorrowedReference
might be a valid non-None reference from the previous runtime incarnation.
src/runtime/importhook.cs
Outdated
storage.GetValue("py_clr_module", out py_clr_module); | ||
var rootHandle = storage.GetValue<IntPtr>("root"); | ||
root = (CLRModule)ManagedType.GetManagedObject(rootHandle); | ||
BorrowedReference dict = Runtime.PyImport_GetModuleDict(); | ||
Runtime.PyDict_SetItemString(dict.DangerousGetAddress(), "clr", py_clr_module); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add a property ClrModuleReference
that returns a BorrowedReference
of py_clr_module
, and then used it in PyDict_SetItemString
overload, that works with BorrowedReferences
to avoid using Dangerous*
methods.
src/runtime/importhook.cs
Outdated
var mod_dict = Runtime.PyModule_GetDict(import_hook_module); | ||
Runtime.XIncref(mod_dict.DangerousGetAddress()); | ||
Runtime.PyTuple_SetItem(args.DangerousGetAddress(), 1, mod_dict.DangerousGetAddress()); | ||
Runtime.PyObject_Call(exec.DangerousGetAddress(), args.DangerousGetAddress(), IntPtr.Zero); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is better to make an overload with the new reference types when possible without rewriting existing code, than to call Dangerous*
methods. See for example
pythonnet/src/runtime/runtime.cs
Line 996 in 7d8f754
internal static IntPtr PyObject_Type(IntPtr op) |
One can also be made for PyTuple_SetItem
using the new StolenReference
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This applies to a few Runtime methods across the PR. Basically anything where you can add a new overload.
src/runtime/importhook.cs
Outdated
/// </summary> | ||
static void SetupNamespaceTracking() | ||
{ | ||
var newset = Runtime.PySet_New(default); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using var newset = ...
would remove the need for try ... finally ...
src/runtime/moduleobject.cs
Outdated
var pyname = Runtime.PyString_FromString(name); | ||
try | ||
{ | ||
if (Runtime.PyList_Append(new BorrowedReference(__all__), pyname) != 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Frankly, I screwed up PyList_Append
. Its only overload schizophrenically mixes references and raw pointers. Can you change it to either have two overloads with only references and with only pointers, or just one that takes only references?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are only a few calls to PyList_Append
throughout the code base, I changed all of them to use only references.
/// This is needed because the import mechanics need | ||
/// to set a few attributes | ||
/// </summary> | ||
[ForbidPythonThreads] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you have to forbid threads here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because there's a call to Runtime.PyDict_SetItem
. I might be missing something and this is always called from CPython and the GIL is guaranteed to be held.
src/runtime/moduleobject.cs
Outdated
ModuleObject mod = null; | ||
using (var modname = spec.GetAttr("name")) | ||
{ | ||
mod = ImportHook.Import(modname.ToString()); | ||
} | ||
return mod; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT:
using var modname = ...;
return ...;
src/runtime/runtime.cs
Outdated
@@ -1944,6 +1944,11 @@ internal static string PyModule_GetFilename(IntPtr module) | |||
|
|||
|
|||
internal static IntPtr PyImport_Import(IntPtr name) => Delegates.PyImport_Import(name); | |||
internal static int PyModule_AddObject(BorrowedReference module, string name, BorrowedReference stolenObject) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like stolenObject
parameter should be of type StolenReference
here and below (present in the latest master
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StolenReference here and below (present in the latest master)
🎉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although I'm reviewing the semantics, and this function only steals on success, so by using StolenReference
, we leak on failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dang you are right. Why did they not make it consistent? PyTuple_SetItem
for example does steal even when there's an error. ugh...
Maybe we should make a wrapper, that always steals?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that we should not use BorrowedReference
for the last parameter here. It makes false suggestion, that signature is valid, and borrowed reference is always enough when it is really not. I'd keep it IntPtr
for now, and explain the behavior in the parameter doc.
Or alternatively, make a wrapper, that takes either BorrowedReference
or StolenReference
and handles refcounting internally, and keep the real PInvoke private.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm leaning towards the IntPtr
parameter to be as "consistant" as possible with the CPython API.
src/runtime/converter.cs
Outdated
// pyHandle as is, do not convert. | ||
if (value is ModuleObject modobj) | ||
{ | ||
return modobj.pyHandle; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to return a new reference here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BadSingleton what about this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad missed that one.
src/runtime/runtime.cs
Outdated
@@ -1944,6 +1944,11 @@ internal static string PyModule_GetFilename(IntPtr module) | |||
|
|||
|
|||
internal static IntPtr PyImport_Import(IntPtr name) => Delegates.PyImport_Import(name); | |||
internal static int PyModule_AddObject(BorrowedReference module, string name, BorrowedReference stolenObject) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that we should not use BorrowedReference
for the last parameter here. It makes false suggestion, that signature is valid, and borrowed reference is always enough when it is really not. I'd keep it IntPtr
for now, and explain the behavior in the parameter doc.
Or alternatively, make a wrapper, that takes either BorrowedReference
or StolenReference
and handles refcounting internally, and keep the real PInvoke private.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@filmor I'm good with the code. Please see if you are OK with the approach.
I'll review this today. |
great news! :) |
What does this implement/fix? Explain your changes.
This replaces the current import hook replacement with a MetaPathFinder, letting python handle non-CLR module import.
Does this close any currently open issues?
#727 #1245 #547 #111 and probably others.
Any other comments?
This fix also helps working around a mono runtime behaviour where a mono owns all threads that execute .NET code.
Checklist
Check all those that are applicable and complete.
Make sure to include one or more tests for your change
If an enhancement PR, please create docs and at best an example
Add yourself to AUTHORS
Updated the CHANGELOG