pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/pythonnet/pythonnet/commit/441954f7e589554c988ce1f7026115bcc88ad41b

" href="https://github.githubassets.com/assets/global-d18f184ea1a06a2c.css" /> Make extension/CLR-object registries thread-safe · pythonnet/pythonnet@441954f · GitHub
Skip to content

Commit 441954f

Browse files
greateggsgregfilmor
authored andcommitted
Make extension/CLR-object registries thread-safe
ExtensionType.loadedExtensions and CLRObject.reflectedObjects are "borrowed reference" registries written on every alloc and read or removed from finalizer-thread paths. Under free-threaded Python the plain HashSet<IntPtr> tears reliably; under the GIL the same tears were happening more rarely but still mostly observable as Debug.Assert firings during shutdown. Convert both to ConcurrentDictionary<IntPtr, byte> with the equivalent TryAdd/TryRemove operations, and update the few non-mutating callers (NullGCHandles, RuntimeData snapshot LINQ) to enumerate Keys.
1 parent cf8372a commit 441954f

4 files changed

Lines changed: 13 additions & 11 deletions

File tree

src/runtime/StateSerialization/RuntimeData.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ private static SharedObjectsState SaveRuntimeDataObjects()
178178
var contexts = new Dictionary<PyObject, Dictionary<string, object?>>(PythonReferenceComparer.Instance);
179179
var extensionObjs = new Dictionary<PyObject, ExtensionType>(PythonReferenceComparer.Instance);
180180
// make a copy with strongly typed references to avoid concurrent modification
181-
var extensions = ExtensionType.loadedExtensions
181+
var extensions = ExtensionType.loadedExtensions.Keys
182182
.Select(addr => new PyObject(
183183
new BorrowedReference(addr),
184184
// if we don't skip collect, finalizer might modify loadedExtensions
@@ -199,7 +199,7 @@ private static SharedObjectsState SaveRuntimeDataObjects()
199199
var wrappers = new Dictionary<object, List<CLRObject>>();
200200
var userObjects = new CLRWrapperCollection();
201201
// make a copy with strongly typed references to avoid concurrent modification
202-
var reflectedObjects = CLRObject.reflectedObjects
202+
var reflectedObjects = CLRObject.reflectedObjects.Keys
203203
.Select(addr => new PyObject(
204204
new BorrowedReference(addr),
205205
// if we don't skip collect, finalizer might modify reflectedObjects

src/runtime/Types/ClassBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ public static int tp_clear(BorrowedReference ob)
360360
if (TryFreeGCHandle(ob))
361361
{
362362
IntPtr addr = ob.DangerousGetAddress();
363-
bool deleted = CLRObject.reflectedObjects.Remove(addr);
363+
bool deleted = CLRObject.reflectedObjects.TryRemove(addr, out _);
364364
Debug.Assert(deleted);
365365
}
366366

src/runtime/Types/ClrObject.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
34
using System.Diagnostics;
45
using System.Runtime.InteropServices;
@@ -13,8 +14,8 @@ internal sealed class CLRObject : ManagedType
1314

1415
internal static bool creationBlocked = false;
1516

16-
// "borrowed" references
17-
internal static readonly HashSet<IntPtr> reflectedObjects = new();
17+
// "borrowed" references; thread-safe (see ExtensionType.loadedExtensions).
18+
internal static readonly ConcurrentDictionary<IntPtr, byte> reflectedObjects = new();
1819
static NewReference Create(object ob, BorrowedReference tp)
1920
{
2021
if (creationBlocked)
@@ -28,7 +29,7 @@ static NewReference Create(object ob, BorrowedReference tp)
2829
GCHandle gc = GCHandle.Alloc(self);
2930
InitGCHandle(py.Borrow(), type: tp, gc);
3031

31-
bool isNew = reflectedObjects.Add(py.DangerousGetAddress());
32+
bool isNew = reflectedObjects.TryAdd(py.DangerousGetAddress(), 0);
3233
Debug.Assert(isNew);
3334

3435
// Fix the BaseException args (and __cause__ in case of Python 3)
@@ -73,7 +74,7 @@ protected override void OnLoad(BorrowedReference ob, Dictionary<string, object?>
7374
GCHandle gc = GCHandle.Alloc(this);
7475
SetGCHandle(ob, gc);
7576

76-
bool isNew = reflectedObjects.Add(ob.DangerousGetAddress());
77+
bool isNew = reflectedObjects.TryAdd(ob.DangerousGetAddress(), 0);
7778
Debug.Assert(isNew);
7879
}
7980
}

src/runtime/Types/ExtensionType.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,15 @@ public virtual NewReference Alloc()
4242

4343
public PyObject AllocObject() => new(Alloc().Steal());
4444

45-
// "borrowed" references
46-
internal static readonly HashSet<IntPtr> loadedExtensions = new();
45+
// "borrowed" references; thread-safe to survive free-threaded Python and
46+
// .NET finalizer-thread races.
47+
internal static readonly System.Collections.Concurrent.ConcurrentDictionary<IntPtr, byte> loadedExtensions = new();
4748
void SetupGc (BorrowedReference ob, BorrowedReference tp)
4849
{
4950
GCHandle gc = GCHandle.Alloc(this);
5051
InitGCHandle(ob, tp, gc);
5152

52-
bool isNew = loadedExtensions.Add(ob.DangerousGetAddress());
53+
bool isNew = loadedExtensions.TryAdd(ob.DangerousGetAddress(), 0);
5354
Debug.Assert(isNew);
5455

5556
// We have to support gc because the type machinery makes it very
@@ -104,7 +105,7 @@ public static int tp_clear(BorrowedReference ob)
104105

105106
if (TryFreeGCHandle(ob))
106107
{
107-
bool deleted = loadedExtensions.Remove(ob.DangerousGetAddress());
108+
bool deleted = loadedExtensions.TryRemove(ob.DangerousGetAddress(), out _);
108109
Debug.Assert(deleted);
109110
}
110111

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy