Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ebd0a95
Thread-safety prep for free-threading builds
greateggsgreg May 9, 2026
cf8372a
Initialise pythonnet on free-threaded Python (#2720)
greateggsgreg May 9, 2026
441954f
Make extension/CLR-object registries thread-safe
greateggsgreg May 9, 2026
19c7f59
Atomic type creation in ReflectedClrType.GetOrCreate / TypeManager.Ge…
greateggsgreg May 9, 2026
1029c62
Add free-threaded thread-stress tests and 3.14t to CI matrix
greateggsgreg May 9, 2026
3158ca5
Atomic GCHandle ownership and finalizer-thread shutdown guards
greateggsgreg May 10, 2026
1149295
Make additional internal registries thread-safe
greateggsgreg May 10, 2026
e8afa85
test_thread: join worker threads before returning
greateggsgreg May 10, 2026
3954df3
test_thread: cover ModuleObject thread-safe registries
greateggsgreg May 10, 2026
28013af
Wider thread-safety audit fixes for free-threaded Python
greateggsgreg May 10, 2026
9a3dd59
Document lock acquisition sites and strong->weak GCHandle swap
greateggsgreg May 10, 2026
cb1dcde
Preserve InternString single-write invariant under DEBUG
greateggsgreg May 10, 2026
74d12f1
test_thread: cover real-world consumer patterns
greateggsgreg May 10, 2026
a30c84e
Auto-detect free-threaded libpython in venv home
greateggsgreg May 12, 2026
be5ba34
Snapshot pypath, use ConcurrentDictionary for thunks and slot holders
greateggsgreg May 13, 2026
946785e
Fix handling of python runtime suffixes m/t
greateggsgreg May 13, 2026
77ca497
Fix threadtest race
greateggsgreg May 13, 2026
af518bb
Fix double-free in chained ClassDerived Finalize
greateggsgreg May 13, 2026
ed735ac
Enable Mono CI jobs on free-threaded Python 3.14
greateggsgreg May 13, 2026
bd3f0bf
Inline freethreaded_only as pytest.mark.skipif at call sites
greateggsgreg May 13, 2026
c4688b3
Fix InterruptTest assertion on free-threaded Python 3.14
greateggsgreg May 13, 2026
84cd07c
Add concurrent stress tests for PyBuffer.Dispose and CLR-cycle gc.col…
greateggsgreg May 15, 2026
d9b658d
Trim concurrent overhead on hot paths from free-threading prep
greateggsgreg May 15, 2026
20c51ad
Pre-warm ctor binder in concurrent-gc test to avoid first-call race
greateggsgreg May 15, 2026
f5f8ab0
Make MethodBinder.GetMethods lazy init thread-safe under free-threading
greateggsgreg May 15, 2026
c5097eb
Precompute method precedence to avoid quadratic GetParameters allocat…
greateggsgreg May 15, 2026
18e4901
Zero the slot in ClassDerived.tp_dealloc when tp_clear already ran to…
greateggsgreg May 15, 2026
e0569d0
Keep ClassDerived wrapper alive across the NewObjectToPython slot dem…
greateggsgreg May 15, 2026
2c9f861
Document private helpers added during free-threading prep
greateggsgreg May 16, 2026
ee3add4
Add debug echoes and a 6-minute step timeout to the Mono test job
greateggsgreg May 16, 2026
414ce70
Drop the per-loop CLR GC.Collect from concurrent-gc test to avoid Mon…
greateggsgreg May 16, 2026
99a99db
Add temporary Mono-step diagnostics on Linux/macOS to locate the x64-…
greateggsgreg May 16, 2026
8e7c2ba
Revert temporary Mono-step diagnostics now that the underlying race i…
greateggsgreg May 16, 2026
f8b69a2
Merge branch 'master' into freethreading-prep
greateggsgreg May 16, 2026
8f2804c
Add user-facing threading guide covering GIL, free-threading, and com…
greateggsgreg May 18, 2026
9c369ed
Harden CollectBasicObject against .NET-GC timing differences
greateggsgreg May 18, 2026
d1b4daa
Merge branch 'master' into freethreading-prep
greateggsgreg May 25, 2026
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
Prev Previous commit
Next Next commit
Fix double-free in chained ClassDerived Finalize
  • Loading branch information
greateggsgreg authored and filmor committed May 15, 2026
commit af518bb57bfc5f3eaf0529a20b1f7d716112e8ff
10 changes: 8 additions & 2 deletions src/runtime/Types/ClassDerived.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,14 @@ private static Type CreateDerivedTypeImpl(string name,
#pragma warning disable CS0618 // PythonDerivedType is for internal use only
il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(PyFinalize)));
#pragma warning restore CS0618 // PythonDerivedType is for internal use only
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, baseClass.GetMethod("Finalize", BindingFlags.NonPublic | BindingFlags.Instance));
// Only chain to the base Finalize if it's not another pythonnet-emitted
// type: those already call PyFinalize themselves, which would double-queue
// the same __pyobj__ and trigger PyObject_GC_Del on freed memory.
if (!typeof(IPythonDerivedType).IsAssignableFrom(baseClass))
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, baseClass.GetMethod("Finalize", BindingFlags.NonPublic | BindingFlags.Instance));
}
il.Emit(OpCodes.Ret);

Type type = typeBuilder.CreateType();
Expand Down
28 changes: 28 additions & 0 deletions tests/test_subclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,34 @@ class OverloadingSubclass2(OverloadingSubclass):
obj = OverloadingSubclass2()
assert obj.VirtMethod[int](5) == 5


def test_nested_namespaced_subclass_finalize_no_double_queue():
"""A Python subclass derived from a Python subclass with __namespace__ on
both must not double-queue PyFinalize when GC'd.

Each level emits a CLR type with a Finalize() that called PyFinalize(this)
and chained to the base's Finalize(). Chaining through another emitted
base ran PyFinalize twice for the same __pyobj__, so PythonDerivedType.Finalize
saw the same handle twice in the queue and called PyObject_GC_Del on freed
memory. Reliably reproduced under Mono in CI during #2721 development
CoreCLR happened to mask it via GC timing.
"""
from System import Object
class Base(Object):
__namespace__ = "test_nested_namespaced_subclass_finalize"
class Derived(Base):
__namespace__ = "test_nested_namespaced_subclass_finalize"

Derived() # only the deepest level triggers the double-queue

import gc
gc.collect()

# Touch CLR namespace state — would crash inside PyType_GenericAlloc /
# PyObject_GC_Del if the previous finalize double-freed.
import System
list(System.__all__)

def test_implement_interface_and_class():
class DualSubClass0(ISayHello1, SimpleClass):
__namespace__ = "Test"
Expand Down