Skip to content

Commit 104103c

Browse files
author
Dani Pinyol
committed
fix: crash in atexit
After jl_atexit_hook(0) returns, Python continues destroying objects, calling _pyjl_dealloc. But Julia's GC subsystem is already torn down, so Base.GC.enable() and lock() crash. Now we add a flag that's set by a Julia atexit hook (runs before finalizers inside jl_atexit_hook). When the flag is set, _pyjl_dealloc skips all Julia runtime operations — the process is exiting anyway.
1 parent 7d7bf7e commit 104103c

File tree

1 file changed

+7
-1
lines changed

1 file changed

+7
-1
lines changed

src/JlWrap/C.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ const PYJLVALUES = []
2727
const PYJLFREEVALUES = Int[]
2828
# lock protecting PYJLVALUES and PYJLFREEVALUES from concurrent modification
2929
const PYJL_LOCK = Threads.SpinLock()
30+
# set to true by an atexit hook so _pyjl_dealloc can skip Julia runtime calls
31+
# (Base.GC.enable, lock) after jl_atexit_hook has torn down the runtime
32+
const JL_EXITING = Ref(false)
3033

3134
function _pyjl_new(t::C.PyPtr, ::C.PyPtr, ::C.PyPtr)
3235
alloc = C.PyType_GetSlot(t, C.Py_tp_alloc)
@@ -40,7 +43,7 @@ end
4043

4144
function _pyjl_dealloc(o::C.PyPtr)
4245
idx = C.@ft UnsafePtr{PyJuliaValueObject}(o).value[]
43-
if idx != 0
46+
if idx != 0 && !JL_EXITING[]
4447
# Disable GC to prevent push! from triggering a GC that runs finalizers
4548
# re-entrantly (the finalizer chain: push! → alloc → GC → py_finalizer →
4649
# enqueue → Py_DecRef → _pyjl_dealloc → push! on same vector →
@@ -375,6 +378,9 @@ end
375378

376379
function __init__()
377380
init_c()
381+
atexit() do
382+
JL_EXITING[] = true
383+
end
378384
end
379385

380386
PyJuliaValue_IsNull(o) = Base.GC.@preserve o (C.@ft UnsafePtr{PyJuliaValueObject}(C.asptr(o)).value[]) == 0

0 commit comments

Comments
 (0)