@@ -67,13 +67,25 @@ unsafe extern "C" fn drop_stream(capsule: *mut ffi::PyObject) {
6767 if capsule. is_null ( ) {
6868 return ;
6969 }
70- let stream_ptr =
71- ffi:: PyCapsule_GetPointer ( capsule, ARROW_STREAM_NAME . as_ptr ( ) ) as * mut FFI_ArrowArrayStream ;
72- if stream_ptr. is_null ( ) {
73- ffi:: PyErr_Clear ( ) ;
74- return ;
70+
71+ // When PyArrow imports this capsule it steals the raw stream pointer and
72+ // sets the capsule's internal pointer to NULL. In that case
73+ // `PyCapsule_IsValid` returns 0 and this destructor must not drop the
74+ // stream as ownership has been transferred to PyArrow. If the capsule was
75+ // never imported, the pointer remains valid and we are responsible for
76+ // freeing the stream here.
77+ if ffi:: PyCapsule_IsValid ( capsule, ARROW_STREAM_NAME . as_ptr ( ) ) == 1 {
78+ let stream_ptr = ffi:: PyCapsule_GetPointer ( capsule, ARROW_STREAM_NAME . as_ptr ( ) )
79+ as * mut FFI_ArrowArrayStream ;
80+ if !stream_ptr. is_null ( ) {
81+ drop ( Box :: from_raw ( stream_ptr) ) ;
82+ }
7583 }
76- drop ( Box :: from_raw ( stream_ptr) ) ;
84+
85+ // `PyCapsule_GetPointer` sets a Python error on failure. Clear it only
86+ // after the stream has been released (or determined to be owned
87+ // elsewhere).
88+ ffi:: PyErr_Clear ( ) ;
7789}
7890
7991// https://github.com/apache/datafusion-python/pull/1016#discussion_r1983239116
@@ -976,6 +988,10 @@ impl PyDataFrame {
976988 !stream_ptr. is_null( ) ,
977989 "ArrowArrayStream pointer should never be null" ,
978990 ) ;
991+ // The returned capsule allows zero-copy hand-off to PyArrow. When
992+ // PyArrow imports the capsule it assumes ownership of the stream and
993+ // nulls out the capsule's internal pointer so `drop_stream` knows not to
994+ // free it.
979995 let capsule = unsafe {
980996 ffi:: PyCapsule_New (
981997 stream_ptr as * mut c_void ,
0 commit comments