🌐 AI搜索 & 代理 主页
Skip to content

Commit ff1f874

Browse files
committed
Move tp_clear workaround to .NET
In Python 3.14, the objects __dict__ seems to already be half deconstructed, leading to crashes during garbage collection. Since gc in Python is single-threaded (I think :)), it should be fine to have a single static for this. If that is not true, we can always use a thread-local instead.
1 parent cb16d01 commit ff1f874

File tree

5 files changed

+11
-23
lines changed

5 files changed

+11
-23
lines changed

src/runtime/Native/PyIdentifier_.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ static class PyIdentifier
1313
public static BorrowedReference __doc__ => new(f__doc__);
1414
static IntPtr f__class__;
1515
public static BorrowedReference __class__ => new(f__class__);
16-
static IntPtr f__clear_reentry_guard__;
17-
public static BorrowedReference __clear_reentry_guard__ => new(f__clear_reentry_guard__);
1816
static IntPtr f__module__;
1917
public static BorrowedReference __module__ => new(f__module__);
2018
static IntPtr f__file__;
@@ -51,7 +49,6 @@ static partial class InternString
5149
"__dict__",
5250
"__doc__",
5351
"__class__",
54-
"__clear_reentry_guard__",
5552
"__module__",
5653
"__file__",
5754
"__slots__",

src/runtime/Native/PyIdentifier_.tt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"__dict__",
88
"__doc__",
99
"__class__",
10-
"__clear_reentry_guard__",
1110
"__module__",
1211
"__file__",
1312
"__slots__",

src/runtime/Types/ClassBase.cs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,8 @@ public static int tp_clear(BorrowedReference ob)
374374
return 0;
375375
}
376376

377+
static readonly HashSet<IntPtr> ClearVisited = new();
378+
377379
internal static unsafe int BaseUnmanagedClear(BorrowedReference ob)
378380
{
379381
var type = Runtime.PyObject_TYPE(ob);
@@ -385,26 +387,20 @@ internal static unsafe int BaseUnmanagedClear(BorrowedReference ob)
385387
}
386388
var clear = (delegate* unmanaged[Cdecl]<BorrowedReference, int>)clearPtr;
387389

388-
bool usesSubtypeClear = clearPtr == TypeManager.subtype_clear;
389-
if (usesSubtypeClear)
390+
if (clearPtr == TypeManager.subtype_clear)
390391
{
391-
// workaround for https://bugs.python.org/issue45266 (subtype_clear)
392-
using var dict = Runtime.PyObject_GenericGetDict(ob);
393-
if (Runtime.PyMapping_HasKey(dict.Borrow(), PyIdentifier.__clear_reentry_guard__) != 0)
392+
var addr = ob.DangerousGetAddress();
393+
if (!ClearVisited.Add(addr))
394394
return 0;
395395

396-
int res = Runtime.PyDict_SetItem(
397-
dict.Borrow(),
398-
PyIdentifier.__clear_reentry_guard__,
399-
Runtime.PyTrue
400-
);
401-
if (res != 0) return res;
402-
403-
res = clear(ob);
404-
Runtime.PyDict_DelItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__);
396+
int res = clear(ob);
397+
ClearVisited.Remove(addr);
405398
return res;
406399
}
407-
return clear(ob);
400+
else
401+
{
402+
return clear(ob);
403+
}
408404
}
409405

410406
protected override Dictionary<string, object?> OnSave(BorrowedReference ob)

tests/test_method.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,6 @@ def test_getting_generic_method_binding_does_not_leak_ref_count():
942942
refCount = sys.getrefcount(PlainOldClass().GenericMethod[str])
943943
assert refCount == 1
944944

945-
@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Test skipped on Python 3.14 and above")
946945
def test_getting_generic_method_binding_does_not_leak_memory():
947946
"""Test that managed object is freed after calling generic method. Issue #691"""
948947

@@ -984,7 +983,6 @@ def test_getting_overloaded_method_binding_does_not_leak_ref_count():
984983
refCount = sys.getrefcount(PlainOldClass().OverloadedMethod.Overloads[int])
985984
assert refCount == 1
986985

987-
@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Test skipped on Python 3.14 and above")
988986
def test_getting_overloaded_method_binding_does_not_leak_memory():
989987
"""Test that managed object is freed after calling overloaded method. Issue #691"""
990988

@@ -1027,7 +1025,6 @@ def test_getting_method_overloads_binding_does_not_leak_ref_count():
10271025
assert refCount == 1
10281026

10291027
@pytest.mark.xfail(reason="Fails locally, need to investigate later", strict=False)
1030-
@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Test skipped on Python 3.14 and above")
10311028
def test_getting_method_overloads_binding_does_not_leak_memory():
10321029
"""Test that managed object is freed after calling overloaded method. Issue #691"""
10331030

tests/test_subclass.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,6 @@ def __init__(self, i, s):
304304
assert calls[0][1] == "foo"
305305

306306
# regression test for https://github.com/pythonnet/pythonnet/issues/1565
307-
@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Test skipped on Python 3.14 and above")
308307
def test_can_be_collected_by_gc():
309308
from Python.Test import BaseClass
310309

0 commit comments

Comments
 (0)