diff --git a/python/pyarrow/src/arrow/python/python_to_arrow.cc b/python/pyarrow/src/arrow/python/python_to_arrow.cc index 47290a7e7f10..5b83875813a1 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(i, *size); } return Status::OK(); diff --git a/python/pyarrow/tests/test_convert_builtin.py b/python/pyarrow/tests/test_convert_builtin.py index c10ae0f62b41..bb2813f3b5df 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