Skip to content

Commit 1cf616d

Browse files
authored
Merge pull request pythonnet#1712 from losttech/bugs/ShutdownSegFault
On shutdown from Python release all slot holders
2 parents 0e57cdd + 58bd58c commit 1cf616d

File tree

7 files changed

+37
-24
lines changed

7 files changed

+37
-24
lines changed

pythonnet.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F
2525
EndProject
2626
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}"
2727
ProjectSection(SolutionItems) = preProject
28+
.github\workflows\ARM.yml = .github\workflows\ARM.yml
2829
.github\workflows\main.yml = .github\workflows\main.yml
2930
.github\workflows\nuget-preview.yml = .github\workflows\nuget-preview.yml
3031
EndProjectSection

pythonnet/__init__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def set_runtime(runtime):
1616

1717

1818
def set_default_runtime() -> None:
19-
if sys.platform == 'win32':
19+
if sys.platform == "win32":
2020
set_runtime(clr_loader.get_netfx())
2121
else:
2222
set_runtime(clr_loader.get_mono())
@@ -36,22 +36,23 @@ def load():
3636
set_default_runtime()
3737

3838
dll_path = join(dirname(__file__), "runtime", "Python.Runtime.dll")
39-
39+
4040
_LOADER_ASSEMBLY = _RUNTIME.get_assembly(dll_path)
4141

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

4646
import atexit
47+
4748
atexit.register(unload)
4849

4950

5051
def unload():
5152
global _RUNTIME
5253
if _LOADER_ASSEMBLY is not None:
5354
func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Shutdown"]
54-
if func(b"") != 0:
55+
if func(b"full_shutdown") != 0:
5556
raise RuntimeError("Failed to call Python.NET shutdown")
5657

5758
if _RUNTIME is not None:

src/runtime/Runtime.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ private static string GetDefaultDllName(Version version)
5454
}
5555

5656
private static bool _isInitialized = false;
57-
5857
internal static bool IsInitialized => _isInitialized;
58+
private static bool _typesInitialized = false;
59+
internal static bool TypeManagerInitialized => _typesInitialized;
5960
internal static readonly bool Is32Bit = IntPtr.Size == 4;
6061

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

155157
// Initialize modules that depend on the runtime class.
156158
AssemblyManager.Initialize();
@@ -273,6 +275,7 @@ internal static void Shutdown()
273275
NullGCHandles(ExtensionType.loadedExtensions);
274276
ClassManager.RemoveClasses();
275277
TypeManager.RemoveTypes();
278+
_typesInitialized = false;
276279

277280
MetaType.Release();
278281
PyCLRMetaType.Dispose();
@@ -293,9 +296,10 @@ internal static void Shutdown()
293296
Finalizer.Shutdown();
294297
InternString.Shutdown();
295298

299+
ResetPyMembers();
300+
296301
if (!HostedInPython)
297302
{
298-
ResetPyMembers();
299303
GC.Collect();
300304
GC.WaitForPendingFinalizers();
301305
PyGILState_Release(state);
@@ -312,7 +316,6 @@ internal static void Shutdown()
312316
}
313317
else
314318
{
315-
ResetPyMembers();
316319
PyGILState_Release(state);
317320
}
318321
}

src/runtime/TypeManager.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,21 @@ internal static void Initialize()
5151

5252
internal static void RemoveTypes()
5353
{
54-
foreach (var type in cache.Values)
54+
if (Runtime.HostedInPython)
5555
{
56-
if (Runtime.HostedInPython
57-
&& _slotsHolders.TryGetValue(type, out var holder))
56+
foreach (var holder in _slotsHolders)
5857
{
5958
// If refcount > 1, it needs to reset the managed slot,
6059
// otherwise it can dealloc without any trick.
61-
if (Runtime.Refcount(type) > 1)
60+
if (holder.Key.Refcount > 1)
6261
{
63-
holder.ResetSlots();
62+
holder.Value.ResetSlots();
6463
}
6564
}
65+
}
66+
67+
foreach (var type in cache.Values)
68+
{
6669
type.Dispose();
6770
}
6871
cache.Clear();
@@ -507,7 +510,7 @@ internal static PyType CreateMetaType(Type impl, out SlotsHolder slotsHolder)
507510
{
508511
throw PythonException.ThrowLastAsClrException();
509512
}
510-
513+
511514
BorrowedReference dict = Util.ReadRef(type, TypeOffset.tp_dict);
512515
using (var mod = Runtime.PyString_FromString("clr._internal"))
513516
Runtime.PyDict_SetItemString(dict, "__module__", mod.Borrow());
@@ -726,6 +729,7 @@ internal static void CopySlot(BorrowedReference from, BorrowedReference to, int
726729

727730
internal static SlotsHolder CreateSlotsHolder(PyType type)
728731
{
732+
type = new PyType(type);
729733
var holder = new SlotsHolder(type);
730734
_slotsHolders.Add(type, holder);
731735
return holder;
@@ -828,6 +832,7 @@ public void ResetSlots()
828832
var metatype = Runtime.PyObject_TYPE(Type);
829833
ManagedType.TryFreeGCHandle(Type, metatype);
830834
}
835+
Runtime.PyType_Modified(Type);
831836
}
832837

833838
public static IntPtr GetDefaultSlot(int offset)

src/runtime/Types/ClassDerived.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,7 @@ protected override NewReference NewObjectToPython(object obj, BorrowedReference
5959
// Decrement the python object's reference count.
6060
// This doesn't actually destroy the object, it just sets the reference to this object
6161
// to be a weak reference and it will be destroyed when the C# object is destroyed.
62-
if (!self.IsNull())
63-
{
64-
Runtime.XDecref(self.Steal());
65-
}
62+
Runtime.XDecref(self.Steal());
6663

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

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

945-
// rare case when it's needed
946-
// matches correspdonging PyObject_GC_UnTrack
947-
// in ClassDerivedObject.tp_dealloc
948-
Runtime.PyObject_GC_Del(@ref.Steal());
942+
if (!Runtime.HostedInPython || Runtime.TypeManagerInitialized)
943+
{
944+
// rare case when it's needed
945+
// matches correspdonging PyObject_GC_UnTrack
946+
// in ClassDerivedObject.tp_dealloc
947+
Runtime.PyObject_GC_Del(@ref.Steal());
949948

950-
// must decref our type
951-
Runtime.XDecref(StolenReference.DangerousFromPointer(type.DangerousGetAddress()));
949+
// must decref our type
950+
Runtime.XDecref(StolenReference.DangerousFromPointer(type.DangerousGetAddress()));
951+
}
952952
}
953953

954954
internal static FieldInfo? GetPyObjField(Type type) => type.GetField(PyObjName, PyObjFlags);

src/runtime/Types/ModuleObject.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,6 @@ public static Assembly AddReference(string name)
542542
/// <returns>The Type object</returns>
543543

544544
[ModuleFunction]
545-
[ForbidPythonThreads]
546545
public static Type GetClrType(Type type)
547546
{
548547
return type;

tests/test_engine.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,7 @@ def test_run_string():
4141
assert sys.multiline_worked == 1
4242

4343
PythonEngine.ReleaseLock()
44+
45+
def test_leak_type():
46+
import clr
47+
sys._leaked_intptr = clr.GetClrType(System.IntPtr)

0 commit comments

Comments
 (0)