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
Fix off-GIL AttributeError-hint callback: use a native __getattr__ thunk
The __getattr__ hook restored from #124 exposed the hint builder to
Python as a .NET delegate (Func<PyObject, string, string>). Python
invoked it through DelegateObject.tp_call -> MethodBinder.Invoke, and
MethodBinder releases the GIL around every reflected invocation
(allow_threads defaults to true). The callback therefore ran CPython
C-API calls (GetPythonType, GetManagedString, ...) without holding the
GIL on every attribute miss (hasattr, getattr with default, typos). It
usually survived by luck, but segfaulted whenever the pythonnet
Finalizer fired mid-callback: Finalizer.DisposeAll() starts with
PyErr_Fetch, which dereferences the current thread state - NULL when
the GIL is not held. This is what crashed Lean's CI test host on
2.0.55 after all 35k tests passed.
Replace the Python-function-plus-delegate pair with a native method
descriptor: a PyMethodDef (METH_VARARGS) around a managed thunk,
turned into __getattr__ via PyDescr_NewMethod (newly bound). CPython's
slot_tp_getattr_hook now calls the managed hook directly as a native
method call with the GIL held - MethodBinder is never involved. The
PyMethodDef and thunk are allocated once and kept for the process
lifetime, since descriptors reference them and can outlive engine
shutdown bookkeeping.
Verified with an instrumented build (PyGILState_Check inside
BuildMissingAttributeMessage): the delegate-based hook reports
"GIL held: False" on every miss; this version reports "GIL held: True".
Also survives a 20s stress run of concurrent attribute misses plus
finalizer churn across 6 threads, and behaves identically for hasattr/
getattr-with-default, dunder probes, Python subclasses with their own
__getattr__, and suggestion messages.
Add a regression test asserting the installed __getattr__ is a native
method_descriptor, which is the property that keeps the callback out of
MethodBinder's allow-threads path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 commit comments