diff --git a/src/JlWrap/C.jl b/src/JlWrap/C.jl index 19ff2904..0866b6b3 100644 --- a/src/JlWrap/C.jl +++ b/src/JlWrap/C.jl @@ -25,6 +25,11 @@ const PyJuliaBase_Type = Ref(C.PyNULL) const PYJLVALUES = [] # unused indices in PYJLVALUES const PYJLFREEVALUES = Int[] +# lock protecting PYJLVALUES and PYJLFREEVALUES from concurrent modification +#const PYJL_LOCK = Threads.SpinLock() +# set to true by an atexit hook so _pyjl_dealloc can skip Julia runtime calls +# (Base.GC.enable, lock) after jl_atexit_hook has torn down the runtime +const JL_EXITING = Ref(false) function _pyjl_new(t::C.PyPtr, ::C.PyPtr, ::C.PyPtr) alloc = C.PyType_GetSlot(t, C.Py_tp_alloc) @@ -38,9 +43,17 @@ end function _pyjl_dealloc(o::C.PyPtr) idx = C.@ft UnsafePtr{PyJuliaValueObject}(o).value[] - if idx != 0 + if idx != 0 && !JL_EXITING[] + # Disable GC to prevent push! from triggering a GC that runs finalizers + # re-entrantly (the finalizer chain: push! → alloc → GC → py_finalizer → + # enqueue → Py_DecRef → _pyjl_dealloc → push! on same vector → + # ConcurrencyViolationError). The lock guards against true multi-thread races. +# prev = Base.GC.enable(false) + # lock(PYJL_LOCK) PYJLVALUES[idx] = nothing push!(PYJLFREEVALUES, idx) + # unlock(PYJL_LOCK) + # Base.GC.enable(prev) end (C.@ft UnsafePtr{PyJuliaValueObject}(o).weaklist[!]) == C.PyNULL || C.PyObject_ClearWeakRefs(o) freeptr = C.PyType_GetSlot(C.Py_Type(o), C.Py_tp_free) @@ -365,6 +378,9 @@ end function __init__() init_c() + atexit() do + JL_EXITING[] = true + end end PyJuliaValue_IsNull(o) = Base.GC.@preserve o (C.@ft UnsafePtr{PyJuliaValueObject}(C.asptr(o)).value[]) == 0 @@ -375,6 +391,10 @@ PyJuliaValue_SetValue(_o, @nospecialize(v)) = Base.GC.@preserve _o begin o = C.asptr(_o) idx = C.@ft UnsafePtr{PyJuliaValueObject}(o).value[] if idx == 0 + # Disable GC to prevent push!/pop! from triggering a GC whose finalizers + # re-entrantly resize the same vectors (see _pyjl_dealloc comment). +# prev = Base.GC.enable(false) + #lock(PYJL_LOCK) if isempty(PYJLFREEVALUES) push!(PYJLVALUES, v) idx = length(PYJLVALUES) @@ -382,6 +402,8 @@ PyJuliaValue_SetValue(_o, @nospecialize(v)) = Base.GC.@preserve _o begin idx = pop!(PYJLFREEVALUES) PYJLVALUES[idx] = v end + #unlock(PYJL_LOCK) +# Base.GC.enable(prev) C.@ft UnsafePtr{PyJuliaValueObject}(o).value[] = idx else PYJLVALUES[idx] = v