This is an automated email from the ASF dual-hosted git repository.
raulcd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new 826ac37440 GH-50591: [Python] Fix reference in
ConvertToSequenceAndInferSize when an iterator raises on array conversion
(#50594)
826ac37440 is described below
commit 826ac37440f0247769de1977e06956d49e710e05
Author: Raúl Cumplido <[email protected]>
AuthorDate: Wed Jul 22 13:36:59 2026 +0200
GH-50591: [Python] Fix reference in ConvertToSequenceAndInferSize when an
iterator raises on array conversion (#50594)
### Rationale for this change
We currently leak the list and items in the case of an iterator raising an
Exception and size is present.
### What changes are included in this PR?
Use `OwnedRef` instead of relying on manual `Py_DECREF` in order to avoid
exception scenario returning early in `RETURN_IF_PYERROR`.
### Are these changes tested?
Yes, I've created a new test that fails previously to the fix and passes
after the fix
### Are there any user-facing changes?
No
* GitHub Issue: #50591
Authored-by: Raúl Cumplido <[email protected]>
Signed-off-by: Raúl Cumplido <[email protected]>
---
python/pyarrow/src/arrow/python/python_to_arrow.cc | 4 ++--
python/pyarrow/tests/test_convert_builtin.py | 23 ++++++++++++++++++++++
2 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/python/pyarrow/src/arrow/python/python_to_arrow.cc
b/python/pyarrow/src/arrow/python/python_to_arrow.cc
index 47290a7e7f..5b83875813 100644
--- a/python/pyarrow/src/arrow/python/python_to_arrow.cc
+++ b/python/pyarrow/src/arrow/python/python_to_arrow.cc
@@ -1259,6 +1259,7 @@ Status ConvertToSequenceAndInferSize(PyObject* obj,
PyObject** seq, int64_t* siz
OwnedRef iter_ref(iter);
PyObject* lst = PyList_New(n);
RETURN_IF_PYERROR();
+ OwnedRef lst_ref(lst);
for (i = 0; i < n; i++) {
PyObject* item = PyIter_Next(iter);
if (!item) {
@@ -1270,10 +1271,9 @@ Status ConvertToSequenceAndInferSize(PyObject* obj,
PyObject** seq, int64_t* siz
}
// Shrink list if len(iterator) < size
if (i < n && PyList_SetSlice(lst, i, n, NULL)) {
- Py_DECREF(lst);
RETURN_IF_PYERROR();
}
- *seq = lst;
+ *seq = lst_ref.detach();
*size = std::min<int64_t>(i, *size);
}
return Status::OK();
diff --git a/python/pyarrow/tests/test_convert_builtin.py
b/python/pyarrow/tests/test_convert_builtin.py
index c10ae0f62b..bb2813f3b5 100644
--- a/python/pyarrow/tests/test_convert_builtin.py
+++ b/python/pyarrow/tests/test_convert_builtin.py
@@ -128,6 +128,29 @@ def test_failing_iterator():
pa.array((1 // 0 for x in range(10)), size=10)
+def test_failing_iterator_does_not_leak():
+ # GH-50591
+ import gc
+ import sys
+
+ # Create an arbitrary long int that is hopefully not cached by the
interpreter
+ value = 10**20
+
+ def raising_iter():
+ for _ in range(5):
+ yield value
+ raise ValueError("boom")
+
+ gc.collect()
+ original_refcount = sys.getrefcount(value)
+
+ with pytest.raises(ValueError, match="boom"):
+ pa.array(raising_iter(), size=100)
+
+ gc.collect()
+ assert sys.getrefcount(value) == original_refcount
+
+
class ObjectWithOnlyGetitem:
def __getitem__(self, key):
return 3