Skip to content

Commit

Permalink
Avoid segfault during exit process
Browse files Browse the repository at this point in the history
  • Loading branch information
tkf committed Oct 28, 2018
1 parent b9cca81 commit c875178
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 2 deletions.
6 changes: 4 additions & 2 deletions src/PyCall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,11 @@ it is equivalent to a `PyNULL()` object.
"""
ispynull(o::PyObject) = o.o == PyPtr_NULL

function pydecref_(o::Union{PyPtr,PyObject})
function pydecref_(o::Union{PyPtr,PyObject}) :: Nothing
if _finalized[]
return
end
ccall(@pysym(:Py_DecRef), Cvoid, (PyPtr,), o)
return o
end

function pydecref(o::PyObject)
Expand Down
36 changes: 36 additions & 0 deletions src/pyinit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,40 @@ function __init__()
end
end
end

# Configure finalization steps.
#
# * In julia/PyCall, `julia` needs to call `Py_Finalize` to
# finalize Python runtime to invoke Python functions registered
# in Python's exit hook. This is done by Julia's `atexit` exit
# hook.
#
# * In PyJulia, `python` needs to call `jl_atexit_hook` in its
# exit hook instead.
#
# In both cases, it is important to not invoke GC of the finalized
# runtime. This is ensured by:
@pycheckz ccall((@pysym :Py_AtExit), Cint, (Ptr{Cvoid},),
@cfunction($_set_finalized, Cvoid, ()))
if !already_inited
# Once `_set_finalized` is successfully registered to
# `Py_AtExit`, it is safe to call `Py_Finalize` during
# finalization of this Julia process.
atexit(Py_Finalize)
end
end

const _finalized = Ref(false)
# This flag is set via `Py_AtExit` to avoid calling `pydecref_` after
# Python is finalized.

function _set_finalized()
# This function MUST NOT invoke any Python APIs.
# https://docs.python.org/3/c-api/sys.html#c.Py_AtExit
_finalized[] = true
return nothing
end

function Py_Finalize()
ccall(@pysym(:Py_Finalize), Cvoid, ())
end
14 changes: 14 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -666,4 +666,18 @@ def try_call(f):
pybuiltin("Exception"))
end

@testset "atexit" begin
script = """
$(Base.load_path_setup_code())
using PyCall
pyimport("atexit")[:register]() do
println("atexit called")
end
"""
out = read(`$(Base.julia_cmd()) -e $script`, String)
@test occursin("atexit called", out)
end

include("test_pyfncall.jl")

0 comments on commit c875178

Please sign in to comment.