diff --git a/src/JlWrap/C.jl b/src/JlWrap/C.jl index 19ff2904..60118ba6 100644 --- a/src/JlWrap/C.jl +++ b/src/JlWrap/C.jl @@ -23,8 +23,14 @@ const PyJuliaBase_Type = Ref(C.PyNULL) # we store the actual julia values here # the `value` field of `PyJuliaValueObject` indexes into here const PYJLVALUES = [] -# unused indices in PYJLVALUES +# unused indices in PYJLVALUES; kept the same length as PYJLVALUES (extra slots +# are pushed alongside PYJLVALUES so the dealloc/finalizer path never has to +# resize this vector). Only the first PYJLFREEVALUECOUNT[] entries are valid +# free indices; the rest are placeholders. const PYJLFREEVALUES = Int[] +const PYJLFREEVALUECOUNT = Ref(0) +# lock protecting PYJLVALUES, PYJLFREEVALUES and PYJLFREEVALUECOUNT +const PYJL_LOCK = Threads.SpinLock() function _pyjl_new(t::C.PyPtr, ::C.PyPtr, ::C.PyPtr) alloc = C.PyType_GetSlot(t, C.Py_tp_alloc) @@ -39,8 +45,14 @@ end function _pyjl_dealloc(o::C.PyPtr) idx = C.@ft UnsafePtr{PyJuliaValueObject}(o).value[] if idx != 0 + # No allocation in this critical section: PYJLFREEVALUES has been pre-sized + # to match PYJLVALUES by PyJuliaValue_SetValue, so recording a free index is + # just a write to an existing slot plus a counter bump. This avoids the GC-triggered + lock(PYJL_LOCK) PYJLVALUES[idx] = nothing - push!(PYJLFREEVALUES, idx) + PYJLFREEVALUECOUNT[] += 1 + PYJLFREEVALUES[PYJLFREEVALUECOUNT[]] = idx + unlock(PYJL_LOCK) 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) @@ -375,13 +387,19 @@ PyJuliaValue_SetValue(_o, @nospecialize(v)) = Base.GC.@preserve _o begin o = C.asptr(_o) idx = C.@ft UnsafePtr{PyJuliaValueObject}(o).value[] if idx == 0 - if isempty(PYJLFREEVALUES) + lock(PYJL_LOCK) + if PYJLFREEVALUECOUNT[] == 0 + # Grow both vectors together so length(PYJLFREEVALUES) == length(PYJLVALUES) + # is preserved. The 0 in PYJLFREEVALUES is just a placeholder push!(PYJLVALUES, v) + push!(PYJLFREEVALUES, 0) idx = length(PYJLVALUES) else - idx = pop!(PYJLFREEVALUES) + idx = PYJLFREEVALUES[PYJLFREEVALUECOUNT[]] + PYJLFREEVALUECOUNT[] -= 1 PYJLVALUES[idx] = v end + unlock(PYJL_LOCK) C.@ft UnsafePtr{PyJuliaValueObject}(o).value[] = idx else PYJLVALUES[idx] = v