-
Notifications
You must be signed in to change notification settings - Fork 749
Reset the type slots (split from #958) #1016
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
Conversation
On shutdown, clear the managed Types' slots.
src/runtime/interop.cs
Outdated
} | ||
} | ||
|
||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] |
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: none of the fields are declared strings, so why does CharSet
matter?
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 doesn't, I'll remove it.
src/runtime/metatype.cs
Outdated
SlotsHolder.ReleaseTypeSlots(PyCLRMetaType); | ||
} | ||
Runtime.XDecref(PyCLRMetaType); | ||
PyCLRMetaType = 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.
Since you are setting PyCLRMetaType
to 0, I think it would be a good idea to check for it in the beginning of the method, and throw something like ObjectDisposedException
@@ -22,6 +22,15 @@ public static IntPtr Initialize() | |||
return PyCLRMetaType; | |||
} | |||
|
|||
public static void Release() | |||
{ | |||
if (Runtime.Refcount(PyCLRMetaType) > 1) |
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.
Not sure I understand the reasoning behind > 1
condition.
In fact, why even use Refcount
? This class should have its own way to track if PyCLRMetaType
is allocated.
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 the reasoning is the same as with the similar pattern in typemanager.cs#38. The only way I can see this class tracking it's allocation is to look if the PyCLRMetaType
is IntPtr.Zero
or not.
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.
The logic is: If the refcount is 1, then at line 31 when decref, it'll go to zero and the entire type will be released. In that case we don't care about clearing the slots. It's a standard optimization in a ref-counting framework.
It's correct assuming that ReleaseTypeSlots doesn't also release refcounts that otherwise won't be released.
That said: Do we have coverage on line 29? I'm not sure I really understand under what circumstance it happens.
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 put the expression into bool isLastReference
(don't miss the negations) or at least add a comment.
src/runtime/typemanager.cs
Outdated
IntPtr ret1 = NativeCodePage + native.Return1; | ||
IntPtr ret0 = NativeCodePage + native.Return0; | ||
InitializeSlot(type, ret0, "tp_traverse", false); | ||
InitializeSlot(type, ret0, "tp_clear", false); |
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: can you add parameter name to false
here? (e.g. paramName: false
) Otherwise it is unclear what it means.
src/runtime/typemanager.cs
Outdated
/// <param name="name">Name of the method.</param> | ||
static void InitializeSlot(IntPtr type, IntPtr slot, string name) | ||
/// <param name="canOverride">Can override the slot when it existed</param> | ||
static void InitializeSlot(IntPtr type, IntPtr slot, string name, bool canOverride = true) |
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.
Can you explain why is it OK sometimes to not overwrite a slot?
I see this is used for tp_traverse
and tp_clear
. What are consequences of not overwriting this slot? Would not this cause our cleanup code to simply be skipped?
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 it relates to other parts of #958 where some classes define their own tp_traverse
and tp_clear
. This could be removed for now
src/runtime/typemanager.cs
Outdated
} | ||
_alredyReset = true; | ||
IntPtr tp_name = Marshal.ReadIntPtr(_type, TypeOffset.tp_name); | ||
string typeName = Marshal.PtrToStringAnsi(tp_name); |
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.
Judging by the code around typemanager.cs:430, the value of tp_name
can well be a Unicode string. In Python3 they clearly use UTF8.
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 variable isn't used. We should remove it (and tp_name), and also remove the commented-out use at line 1055.
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.
Just some debug code I left...
src/runtime/typemanager.cs
Outdated
IntPtr tp_base = Marshal.ReadIntPtr(_type, TypeOffset.tp_base); | ||
Runtime.XDecref(tp_base); | ||
Marshal.WriteIntPtr(_type, TypeOffset.tp_base, IntPtr.Zero); | ||
|
||
IntPtr tp_bases = Marshal.ReadIntPtr(_type, TypeOffset.tp_bases); | ||
Runtime.XDecref(tp_bases); | ||
Marshal.WriteIntPtr(_type, TypeOffset.tp_bases, 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.
Is overwriting base types in a type object a defined behavior in Python?
Why do we need this?
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.
Is overwriting base types in a type object a defined behavior in Python?
It should, both of these values are still valid if NULL
.
As to why, this is the reason of this PR. On domain reload (and unload) we clobber the Python objects that points to C#, leaving empty, but still valid, Python objects that no longer refer to code that is now gone.
src/runtime/typemanager.cs
Outdated
Marshal.WriteIntPtr(_type, TypeOffset.mp_length, IntPtr.Zero); | ||
} | ||
|
||
private static void OnDestruct(IntPtr ob) |
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.
Is ob
a Python type here? Perhaps then name as such.
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.
Here and below, ob
should be tp
if (offset == TypeOffset.tp_clear | ||
|| offset == TypeOffset.tp_traverse) | ||
{ | ||
return TypeManager.NativeCodePage + TypeManager.NativeCode.Active.Return0; |
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.
Maybe I missed a conversation. What is the story for ARM in this code?
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 not sure if this is what you're looking for, but it seems that it was removed in #913
FYI: Felix is back from holiday on Monday. |
@@ -311,6 +337,24 @@ internal static IntPtr WriteMethodDefSentinel(IntPtr mdef) | |||
return WriteMethodDef(mdef, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero); | |||
} | |||
|
|||
internal static void FreeMethodDef(IntPtr mdef) | |||
{ | |||
unsafe |
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.
Have we verified this still works in Unity with default options? Default Unity settings block unsafe code. I think it's just at compile-time, so unsafe compiled code is fine, but I want to make sure.
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.
Yes, it does, there are other uses of unsafe code in the project. (XDecref for instance)
src/runtime/typemanager.cs
Outdated
{ | ||
Runtime.PyErr_Print(); | ||
} | ||
FreeMethodDef(mdefAddr); |
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 we open a scope and create a new local variable that just copies a local variable that already exists?
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, I get it -- so that the lambda grabs the right value.
Feels like we could refactor these two scopes into a function, so it's a bit less mysterious.
src/runtime/typemanager.cs
Outdated
|
||
InitializeSlot(type, ret0, "tp_traverse"); | ||
InitializeSlot(type, ret0, "tp_clear"); | ||
InitializeSlot(type, ret1, "tp_is_gc"); |
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 does this work without tp_is_gc
? It used to be required (I didn't really fully understand it, but leaving it out led to crashes).
If we actually don't need it then we can remove everything about Return1 from the code.
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 was probably an oversight, and I added it back.
src/runtime/typemanager.cs
Outdated
if (slotsHolder != null) | ||
{ | ||
slotsHolder.Add(offset, thunkRet0); | ||
} |
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 inline InitializeSlot 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.
Can't find a good reason, will uninline
src/runtime/typemanager.cs
Outdated
return; | ||
} | ||
Marshal.WriteIntPtr(type, offset, thunk.Address); | ||
slotsHolder.Add(offset, thunk); |
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.
won't this throw with the default slotsHolder?
Seems like we should remove the function with one less arg, and handle null here.
src/runtime/typemanager.cs
Outdated
public const string HolderKeyName = "_slots_holder"; | ||
public delegate void Resetor(IntPtr type, int offset); | ||
|
||
private GCHandle _handle; |
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.
The C# world tends to either use m_
or nothing at all to mark instance variables (I think that's the python.net tradition) -- but not just a leading underscore.
src/runtime/typemanager.cs
Outdated
private IntPtr _capsule; | ||
private IntPtr _type; | ||
private Dictionary<int, ThunkInfo> _slots = new Dictionary<int, ThunkInfo>(); | ||
private List<Delegate> _keepalive = new List<Delegate>(); |
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.
Does this actually get used anywhere? I see Add and Clear called on it but nothing else.
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.
If it's just a place to park a pointer to avoid getting GC'd then the KeeapAlive (!) function should document it.
src/runtime/typemanager.cs
Outdated
_deallocators.Add(deallocate); | ||
} | ||
|
||
public void KeeapAlive(Delegate d) |
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.
The spelling is a bit odd?
A comment about what this does would be good.
src/runtime/typemanager.cs
Outdated
|
||
private void ResetSlots() | ||
{ | ||
if (_alredyReset) |
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.
typo: alreadyReset
src/runtime/typemanager.cs
Outdated
Marshal.WriteIntPtr(_type, TypeOffset.tp_bases, IntPtr.Zero); | ||
|
||
// Because __len__ may have a "default" implementation, not registered to us | ||
// always explicitly clobber it. See mp_lenth_slot class |
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.
Is there a typo in mp_length_slot?
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.
No need to handle mp_length manually here, it already included in common case.
else if (offset == TypeOffset.tp_dealloc) | ||
{ | ||
// tp_free of PyTypeType is point to PyObejct_GC_Del. | ||
return Marshal.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_free); |
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.
PyObejct_GC_Del has a typo
return (SlotsHolder)handle.Target; | ||
} | ||
|
||
private static IntPtr GetDefaultSlot(int offset) |
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 the doc here should say that we're looking for the C function to call when resetting the slot at the given offset within the type.
We might want to eventually consider having those functions throw exceptions e.g. "the underlying .NET type has been deleted"
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.
Problem is: with domain gone, who will provide those functions?
…onnet into reset-type-slots
Manipulate SlotsHolder manually instead of Capsule mechanism
4ea1a6f
to
8baad6a
Compare
@@ -11,17 +11,30 @@ namespace Python.Runtime | |||
internal class MetaType : ManagedType | |||
{ | |||
private static IntPtr PyCLRMetaType; | |||
|
|||
private static SlotsHolder _metaSlotsHodler; |
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 just noticed the typo here. This is holding slots, not bitcoin.
@@ -13,7 +13,6 @@ internal class MethodWrapper | |||
public IntPtr mdef; | |||
public IntPtr ptr; | |||
private bool _disposed = false; | |||
|
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.
if you add this empty line back in you remove an entire file from the squash-commit.
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.
Please, update to master
@@ -537,4 +537,13 @@ public ThunkInfo(Delegate target) | |||
Address = Marshal.GetFunctionPointerForDelegate(target); | |||
} | |||
} | |||
|
|||
[StructLayout(LayoutKind.Sequential)] | |||
struct PyMethodDef |
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.
Please, add the following link as a comment: https://docs.python.org/3/c-api/structures.html#c.PyMethodDef
@@ -22,6 +22,15 @@ public static IntPtr Initialize() | |||
return PyCLRMetaType; | |||
} | |||
|
|||
public static void Release() | |||
{ | |||
if (Runtime.Refcount(PyCLRMetaType) > 1) |
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 put the expression into bool isLastReference
(don't miss the negations) or at least add a comment.
private static Dictionary<Type, IntPtr> cache; | ||
private const BindingFlags tbFlags = BindingFlags.Public | BindingFlags.Static; | ||
private static readonly Dictionary<Type, IntPtr> cache = new Dictionary<Type, IntPtr>(); | ||
private static readonly Dictionary<IntPtr, SlotsHolder> _slotsHolders = new Dictionary<IntPtr, SlotsHolder>(); | ||
|
||
static TypeManager() |
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.
Remove empty static constructor
mdef = WriteMethodDefSentinel(mdef); | ||
|
||
Marshal.WriteIntPtr(type, TypeOffset.tp_methods, mdefStart); | ||
slotsHolder.Set(TypeOffset.tp_methods, (t, offset) => |
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: name lambda parameter for clarity
return offset; | ||
} | ||
|
||
static bool IsSlotSet(IntPtr type, string name) |
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: make an overload, taking offset
, and use it here, and in InitializeSlot
above
} | ||
} | ||
|
||
class SlotsHolder |
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.
Put this class into a separate file
return (SlotsHolder)handle.Target; | ||
} | ||
|
||
private static IntPtr GetDefaultSlot(int offset) |
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.
Problem is: with domain gone, who will provide those functions?
} | ||
|
||
|
||
static class SlotHelper |
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.
Does not seem to be used anywhere
Work has stopped on our side on this, so I'm closing the PR. We might get back to it eventually. |
On shutdown, clear the managed Types' slots.
What does this implement/fix? Explain your changes.
This implements part of #958, where the managed type slots are reset on shutdown.
Does this close any currently open issues?
Not yet.
Any other comments?
Most of the code was originally written by @amos402
Checklist
Check all those that are applicable and complete.
Not really testable at the moment. Once the soft shutdown is implemented, tests can be written.
AUTHORS
CHANGELOG