Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

Closed
wants to merge 9 commits into from

Conversation

BadSingleton
Copy link
Contributor

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.

  • Make sure to include one or more tests for your change
    Not really testable at the moment. Once the soft shutdown is implemented, tests can be written.
  • If an enhancement PR, please create docs and at best an example
  • Add yourself to AUTHORS
  • Updated the CHANGELOG

BadSingleton and others added 2 commits December 17, 2019 13:42
}
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
Copy link
Member

@lostmsu lostmsu Dec 20, 2019

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?

Copy link
Contributor Author

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.

SlotsHolder.ReleaseTypeSlots(PyCLRMetaType);
}
Runtime.XDecref(PyCLRMetaType);
PyCLRMetaType = IntPtr.Zero;
Copy link
Member

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)
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Member

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.

IntPtr ret1 = NativeCodePage + native.Return1;
IntPtr ret0 = NativeCodePage + native.Return0;
InitializeSlot(type, ret0, "tp_traverse", false);
InitializeSlot(type, ret0, "tp_clear", false);
Copy link
Member

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.

/// <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)
Copy link
Member

@lostmsu lostmsu Dec 20, 2019

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?

Copy link
Contributor Author

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

}
_alredyReset = true;
IntPtr tp_name = Marshal.ReadIntPtr(_type, TypeOffset.tp_name);
string typeName = Marshal.PtrToStringAnsi(tp_name);
Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Member

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...

Comment on lines 1077 to 1083
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);
Copy link
Member

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?

Copy link
Contributor Author

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.

Marshal.WriteIntPtr(_type, TypeOffset.mp_length, IntPtr.Zero);
}

private static void OnDestruct(IntPtr ob)
Copy link
Member

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.

Copy link
Contributor

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;
Copy link
Member

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?

Copy link
Contributor Author

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

@benoithudson
Copy link
Contributor

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
Copy link
Contributor

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.

Copy link
Contributor Author

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)

{
Runtime.PyErr_Print();
}
FreeMethodDef(mdefAddr);
Copy link
Contributor

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?

Copy link
Contributor

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.


InitializeSlot(type, ret0, "tp_traverse");
InitializeSlot(type, ret0, "tp_clear");
InitializeSlot(type, ret1, "tp_is_gc");
Copy link
Contributor

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.

Copy link
Contributor Author

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.

if (slotsHolder != null)
{
slotsHolder.Add(offset, thunkRet0);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why inline InitializeSlot here?

Copy link
Contributor Author

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

return;
}
Marshal.WriteIntPtr(type, offset, thunk.Address);
slotsHolder.Add(offset, thunk);
Copy link
Contributor

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.

public const string HolderKeyName = "_slots_holder";
public delegate void Resetor(IntPtr type, int offset);

private GCHandle _handle;
Copy link
Contributor

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.

private IntPtr _capsule;
private IntPtr _type;
private Dictionary<int, ThunkInfo> _slots = new Dictionary<int, ThunkInfo>();
private List<Delegate> _keepalive = new List<Delegate>();
Copy link
Contributor

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.

Copy link
Contributor

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.

_deallocators.Add(deallocate);
}

public void KeeapAlive(Delegate d)
Copy link
Contributor

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.


private void ResetSlots()
{
if (_alredyReset)
Copy link
Contributor

@benoithudson benoithudson Jan 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: alreadyReset

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
Copy link
Contributor

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?

Copy link
Member

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);
Copy link
Contributor

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)
Copy link
Contributor

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"

Copy link
Member

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?

@@ -11,17 +11,30 @@ namespace Python.Runtime
internal class MetaType : ManagedType
{
private static IntPtr PyCLRMetaType;

private static SlotsHolder _metaSlotsHodler;
Copy link
Contributor

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;

Copy link
Contributor

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.

Copy link
Member

@lostmsu lostmsu left a 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
Copy link
Member

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)
Copy link
Member

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()
Copy link
Member

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) =>
Copy link
Member

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)
Copy link
Member

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
Copy link
Member

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)
Copy link
Member

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
Copy link
Member

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

@BadSingleton
Copy link
Contributor Author

Work has stopped on our side on this, so I'm closing the PR. We might get back to it eventually.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants