Skip to content

Commit bdc0edf

Browse files
committed
always pydecref on the main thread
1 parent 5d227fc commit bdc0edf

File tree

3 files changed

+39
-2
lines changed

3 files changed

+39
-2
lines changed

src/PyCall.jl

+20-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,25 @@ Python executable used by PyCall in the current process.
4343
current_python() = _current_python[]
4444
const _current_python = Ref(pyprogramname)
4545

46+
47+
# PyCall isn't really thread-safe. We can ask the user to only use
48+
# PyCall from the main thread, but we can't prevent GC from running on
49+
# other threads. To prevent errors in such cases, we use a
50+
# synchronized queue to ensure all calls to pydecref during GC happen
51+
# on the main thread.
52+
const _pydecref_queue = []
53+
const _pydecref_queue_lock = Threads.SpinLock()
54+
function queue_pydecref(o)
55+
lock(_pydecref_queue_lock)
56+
try
57+
push!(_pydecref_queue, o)
58+
finally
59+
unlock(_pydecref_queue_lock)
60+
end
61+
return o
62+
end
63+
64+
4665
#########################################################################
4766

4867
# Mirror of C PyObject struct (for non-debugging Python builds).
@@ -76,7 +95,7 @@ mutable struct PyObject
7695
o::PyPtr # the actual PyObject*
7796
function PyObject(o::PyPtr)
7897
po = new(o)
79-
finalizer(pydecref, po)
98+
finalizer(queue_pydecref, po)
8099
return po
81100
end
82101
end

src/pybuffer.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ mutable struct PyBuffer
3232
b = new(Py_buffer(C_NULL, PyPtr_NULL, 0, 0,
3333
0, 0, C_NULL, C_NULL, C_NULL, C_NULL,
3434
C_NULL, C_NULL, C_NULL))
35-
finalizer(pydecref, b)
35+
finalizer(queue_pydecref, b)
3636
return b
3737
end
3838
end

src/pyinit.jl

+18
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,24 @@ function __init__()
190190
Py_SetPythonHome(libpy_handle, pyversion, pyhome)
191191
Py_SetProgramName(libpy_handle, pyversion, current_python())
192192
ccall((@pysym :Py_InitializeEx), Cvoid, (Cint,), 0)
193+
194+
# _pydecref_queue used to pydecref only on the main thread:
195+
@async begin
196+
while !_finalized[]
197+
if !isempty(_pydecref_queue)
198+
lock(_pydecref_queue_lock)
199+
o = try
200+
pop!(_pydecref_queue, o)
201+
finally
202+
unlock(_pydecref_queue_lock)
203+
end
204+
pydecref(o)
205+
else
206+
yield()
207+
end
208+
end
209+
end
210+
193211
end
194212

195213
# Will get reinitialized properly on first use

0 commit comments

Comments
 (0)