Skip to content

On shutdown from Python release all slot holders #1712

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 3 commits into from
Apr 8, 2022
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
1 change: 1 addition & 0 deletions pythonnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}"
ProjectSection(SolutionItems) = preProject
.github\workflows\ARM.yml = .github\workflows\ARM.yml
.github\workflows\main.yml = .github\workflows\main.yml
.github\workflows\nuget-preview.yml = .github\workflows\nuget-preview.yml
EndProjectSection
Expand Down
9 changes: 5 additions & 4 deletions pythonnet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def set_runtime(runtime):


def set_default_runtime() -> None:
if sys.platform == 'win32':
if sys.platform == "win32":
set_runtime(clr_loader.get_netfx())
else:
set_runtime(clr_loader.get_mono())
Expand All @@ -36,22 +36,23 @@ def load():
set_default_runtime()

dll_path = join(dirname(__file__), "runtime", "Python.Runtime.dll")

_LOADER_ASSEMBLY = _RUNTIME.get_assembly(dll_path)

func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Initialize"]
if func(''.encode("utf8")) != 0:
if func(b"") != 0:
raise RuntimeError("Failed to initialize Python.Runtime.dll")

import atexit

atexit.register(unload)


def unload():
global _RUNTIME
if _LOADER_ASSEMBLY is not None:
func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Shutdown"]
if func(b"") != 0:
if func(b"full_shutdown") != 0:
raise RuntimeError("Failed to call Python.NET shutdown")

if _RUNTIME is not None:
Expand Down
9 changes: 6 additions & 3 deletions src/runtime/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ private static string GetDefaultDllName(Version version)
}

private static bool _isInitialized = false;

internal static bool IsInitialized => _isInitialized;
private static bool _typesInitialized = false;
internal static bool TypeManagerInitialized => _typesInitialized;
internal static readonly bool Is32Bit = IntPtr.Size == 4;

// .NET core: System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
Expand Down Expand Up @@ -151,6 +152,7 @@ internal static void Initialize(bool initSigs = false)
ClassManager.Reset();
ClassDerivedObject.Reset();
TypeManager.Initialize();
_typesInitialized = true;

// Initialize modules that depend on the runtime class.
AssemblyManager.Initialize();
Expand Down Expand Up @@ -272,6 +274,7 @@ internal static void Shutdown()
NullGCHandles(ExtensionType.loadedExtensions);
ClassManager.RemoveClasses();
TypeManager.RemoveTypes();
_typesInitialized = false;

MetaType.Release();
PyCLRMetaType.Dispose();
Expand All @@ -291,9 +294,10 @@ internal static void Shutdown()
Finalizer.Shutdown();
InternString.Shutdown();

ResetPyMembers();

if (!HostedInPython)
{
ResetPyMembers();
GC.Collect();
GC.WaitForPendingFinalizers();
PyGILState_Release(state);
Expand All @@ -310,7 +314,6 @@ internal static void Shutdown()
}
else
{
ResetPyMembers();
PyGILState_Release(state);
}
}
Expand Down
17 changes: 11 additions & 6 deletions src/runtime/TypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,21 @@ internal static void Initialize()

internal static void RemoveTypes()
{
foreach (var type in cache.Values)
if (Runtime.HostedInPython)
{
if (Runtime.HostedInPython
&& _slotsHolders.TryGetValue(type, out var holder))
foreach (var holder in _slotsHolders)
{
// If refcount > 1, it needs to reset the managed slot,
// otherwise it can dealloc without any trick.
if (Runtime.Refcount(type) > 1)
if (holder.Key.Refcount > 1)
{
holder.ResetSlots();
holder.Value.ResetSlots();
}
}
}

foreach (var type in cache.Values)
{
type.Dispose();
}
cache.Clear();
Expand Down Expand Up @@ -507,7 +510,7 @@ internal static PyType CreateMetaType(Type impl, out SlotsHolder slotsHolder)
{
throw PythonException.ThrowLastAsClrException();
}

BorrowedReference dict = Util.ReadRef(type, TypeOffset.tp_dict);
using (var mod = Runtime.PyString_FromString("clr._internal"))
Runtime.PyDict_SetItemString(dict, "__module__", mod.Borrow());
Expand Down Expand Up @@ -726,6 +729,7 @@ internal static void CopySlot(BorrowedReference from, BorrowedReference to, int

internal static SlotsHolder CreateSlotsHolder(PyType type)
{
type = new PyType(type);
var holder = new SlotsHolder(type);
_slotsHolders.Add(type, holder);
return holder;
Expand Down Expand Up @@ -828,6 +832,7 @@ public void ResetSlots()
var metatype = Runtime.PyObject_TYPE(Type);
ManagedType.TryFreeGCHandle(Type, metatype);
}
Runtime.PyType_Modified(Type);
}

public static IntPtr GetDefaultSlot(int offset)
Expand Down
20 changes: 10 additions & 10 deletions src/runtime/Types/ClassDerived.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,7 @@ protected override NewReference NewObjectToPython(object obj, BorrowedReference
// Decrement the python object's reference count.
// This doesn't actually destroy the object, it just sets the reference to this object
// to be a weak reference and it will be destroyed when the C# object is destroyed.
if (!self.IsNull())
{
Runtime.XDecref(self.Steal());
}
Runtime.XDecref(self.Steal());

return Converter.ToPython(obj, type.Value);
}
Expand Down Expand Up @@ -942,13 +939,16 @@ internal static void Finalize(IntPtr derived)

var type = Runtime.PyObject_TYPE(@ref.Borrow());

// rare case when it's needed
// matches correspdonging PyObject_GC_UnTrack
// in ClassDerivedObject.tp_dealloc
Runtime.PyObject_GC_Del(@ref.Steal());
if (!Runtime.HostedInPython || Runtime.TypeManagerInitialized)
{
// rare case when it's needed
// matches correspdonging PyObject_GC_UnTrack
// in ClassDerivedObject.tp_dealloc
Runtime.PyObject_GC_Del(@ref.Steal());

// must decref our type
Runtime.XDecref(StolenReference.DangerousFromPointer(type.DangerousGetAddress()));
// must decref our type
Runtime.XDecref(StolenReference.DangerousFromPointer(type.DangerousGetAddress()));
}
}

internal static FieldInfo? GetPyObjField(Type type) => type.GetField(PyObjName, PyObjFlags);
Expand Down
1 change: 0 additions & 1 deletion src/runtime/Types/ModuleObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,6 @@ public static Assembly AddReference(string name)
/// <returns>The Type object</returns>

[ModuleFunction]
[ForbidPythonThreads]
public static Type GetClrType(Type type)
{
return type;
Expand Down
4 changes: 4 additions & 0 deletions tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ def test_run_string():
assert sys.multiline_worked == 1

PythonEngine.ReleaseLock()

def test_leak_type():
import clr
sys._leaked_intptr = clr.GetClrType(System.IntPtr)