Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/JlWrap/C.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -375,13 +391,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
# 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)
else
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
Expand Down
Loading