You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Wider thread-safety audit fixes for free-threaded Python
Audit found several more shared-state hazards beyond the registries
already covered. The following are reachable on every interpreter
under sufficient contention; FT exposes them reliably.
src/runtime/DelegateManager.cs
Lock cache lookup + Reflection.Emit. TypeBuilder/ModuleBuilder are
not thread-safe; concurrent Python->CLR delegate construction (e.g.
PythonEngine.ShutdownHandler(lambda: None) from multiple threads)
threw "Duplicate type name within an assembly" on 3.14t. Same shape
as the CreateDerivedType fix from a18e872.
src/runtime/Finalizer.cs
- `_throttled` becomes Interlocked.Increment / Interlocked.Exchange.
Lost increments would either grow the queue unbounded or burn CPU
on unnecessary drains.
- `started` is now volatile so the ThrottledCollect check on every
PyObject ctor cannot observe a stale "false" after Initialize.
src/runtime/PythonTypes/PyBuffer.cs
`disposedValue` is now an int gated by Interlocked.Exchange (write)
and Volatile.Read (hot reads). The .NET finalizer racing with an
explicit Dispose() could otherwise both pass the `if (!disposedValue)`
check and call PyBuffer_Release twice -> double-free of _view.obj.
Repeated check at every public method extracted to ThrowIfDisposed().
src/runtime/Util/GenericUtil.cs
`mapping` (nested Dictionary<string, Dictionary<string, List<string>>>)
guarded by a lock; nested mutations cannot be expressed atomically
with ConcurrentDictionary alone. GenericByName snapshots candidate
names under the lock then calls AssemblyManager.LookupTypes outside
it, since LookupTypes can re-enter Register.
src/runtime/PythonEngine.cs
- `ShutdownHandlers` (List<>) wrapped in a lock. ConcurrentStack
would change semantics (no remove-by-equality). ExecuteShutdownHandlers
pops under the lock and invokes unlocked so handlers can re-enter
Add/Remove without deadlock.
- `initialized` flag is now volatile (read from worker threads,
written from Initialize/Shutdown).
src/runtime/Runtime.cs
- `run` epoch now Interlocked.Increment + Volatile.Read; lost
increments across re-init would let stale finalizer queue entries
slip past the RuntimeRun guard.
- `_pyRefs` mutations wrapped in a lock; ResetPyMembers snapshots
then disposes outside the lock so a Dispose callback cannot
reenter and deadlock.
- `_isInitialized`, `_typesInitialized` now volatile.
tests/test_thread.py
- test_concurrent_delegate_creation (FT-only): reproduces the
DelegateManager Reflection.Emit race - aborts with the lock
removed, passes with it.
- test_concurrent_shutdown_handler_register (FT-only): drives
AddShutdownHandler/RemoveShutdownHandler from 8 threads on
pre-built handlers.
- Removed test_freethreaded_concurrent_attribute_access_no_tear;
its workload duplicates test_concurrent_attribute_access at a
different intensity without exercising additional code paths.
Comment cleanup
Trimmed multi-line thread-safety comments across the branch's
earlier commits to single lines that capture only the non-obvious
"why" (Concurrent: / Lock: / volatile: / Atomic claim:). Removed
comments where the type signature already documents the choice.
/// <returns>On success, set view->obj to a new reference to exporter and return 0. Otherwise, raise PyExc_BufferError, set view->obj to NULL and return -1;</returns>
0 commit comments