Skip to content

Commit f1a6357

Browse files
authored
fix non-reentrancy and lack of gc in recent pycall optimization (#534)
* fix non-reentrancy and lack of gc in recent pycall optimization * default range conversion uses PyInt
1 parent f41e5c7 commit f1a6357

File tree

2 files changed

+37
-18
lines changed

2 files changed

+37
-18
lines changed

src/pyfncall.jl

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,23 @@ function _pycall!(ret::PyObject, o::Union{PyObject,PyPtr}, args,
2929
end
3030
check_pyargsptr(nargs)
3131
pyargsptr = pyarg_tuples[nargs+1]
32-
return _pycall!(ret, pyargsptr, o, args, nargs, kw) #::PyObject
32+
# We temporarily set pyarg_tuples[nargs+1] to NULL to ensure that nested
33+
# calls to pycall don't try to use the same tuple object (issue #533).
34+
pyarg_tuples[nargs+1] = C_NULL
35+
try
36+
pysetargs!(pyargsptr, args, nargs)
37+
return __pycall!(ret, pyargsptr, o, kw) #::PyObject
38+
finally
39+
if nargs > 0 && unsafe_load(pyargsptr).ob_refcnt > 1
40+
# we are no longer the only reference to the tuple,
41+
# so PyTuple_SetItem will fail (see check_pyargsptr)
42+
pydecref_(pyargsptr)
43+
else
44+
pyunsetargs!(pyargsptr, nargs) # garbage-collect args
45+
pydecref_(pyarg_tuples[nargs+1]) # no-op if it's still NULL
46+
pyarg_tuples[nargs+1] = pyargsptr # restore tuple cache
47+
end
48+
end
3349
end
3450

3551
"""
@@ -43,25 +59,16 @@ OTOH this py"def foo(*args): global z; z=args" doesn't trigger this.
4359
Fortunately, this check for ob_refcnt is fast - only a few cpu clock cycles.
4460
"""
4561
function check_pyargsptr(nargs::Int)
46-
if nargs > 0 && unsafe_load(pyarg_tuples[nargs+1]).ob_refcnt > 1
47-
pydecref_(pyarg_tuples[nargs+1])
48-
pyarg_tuples[nargs+1] =
49-
@pycheckn ccall((@pysym :PyTuple_New), PyPtr, (Int,), nargs)
62+
if nargs > 0
63+
p = pyarg_tuples[nargs+1]
64+
if p == C_NULL || unsafe_load(p).ob_refcnt > 1
65+
pydecref_(p) # no-op for NULL p
66+
pyarg_tuples[nargs+1] =
67+
@pycheckn ccall((@pysym :PyTuple_New), PyPtr, (Int,), nargs)
68+
end
5069
end
5170
end
5271

53-
"""
54-
Low-level version of `pycall!(ret, o, ...)` for when `kw` is already in python
55-
friendly format and you have the python tuple to hold the arguments (`pyargsptr`).
56-
Sets the tuple's values to the python version of your arguments, and calls the
57-
function. Sets `ret.o` to the result of the call, and returns `ret::PyObject`.
58-
"""
59-
function _pycall!(ret::PyObject, pyargsptr::PyPtr, o::Union{PyObject,PyPtr},
60-
args, nargs::Int=length(args), kw::Union{Ptr{Cvoid}, PyObject}=C_NULL)
61-
pysetargs!(pyargsptr, args, nargs)
62-
return __pycall!(ret, pyargsptr, o, kw) #::PyObject
63-
end
64-
6572
"""
6673
```
6774
pysetargs!(pyargsptr::PyPtr, args...)
@@ -75,6 +82,16 @@ function pysetargs!(pyargsptr::PyPtr, args, N::Int)
7582
end
7683
end
7784

85+
"""
86+
Decref the `N`` elements of the Python tuple `pyargsptr` by setting the
87+
elements to NULL, to ensure that they are garbage-collected.
88+
"""
89+
function pyunsetargs!(pyargsptr::PyPtr, N::Int)
90+
for i = 1:N
91+
@pycheckz ccall((@pysym :PyTuple_SetItem), Cint, (PyPtr,Int,PyPtr), pyargsptr, i-1, C_NULL)
92+
end
93+
end
94+
7895
"""
7996
```
8097
pysetarg!(pyargsptr::PyPtr, arg, i::Integer=1)
@@ -88,7 +105,6 @@ function pysetarg!(pyargsptr::PyPtr, arg, i::Integer=1)
88105
@pycheckz ccall((@pysym :PyTuple_SetItem), Cint,
89106
(PyPtr,Int,PyPtr), pyargsptr, i-1, pyarg)
90107
end
91-
92108
"""
93109
Lowest level version of `pycall!(ret, o, ...)`, assumes `pyargsptr` and `kw`
94110
have all their args set to Python values, so we can just call the function `o`.

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,9 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
527527
@test o isa Vector{PyObject}
528528
@test o == [4,7]
529529
end
530+
531+
# issue #533
532+
@test py"lambda x,y,z: (x,y,z)"(3:6,4:10,5:11) === (PyInt(3):PyInt(6), PyInt(4):PyInt(10), PyInt(5):PyInt(11))
530533
end
531534

532535
######################################################################

0 commit comments

Comments
 (0)