Skip to content

fix: Dispose record batches exported through the C stream interface - #483

Merged
adamreeve merged 2 commits into
apache:mainfrom
ump45nose:fix/stream-export-dispose-batches
Oct 22, 2026
Merged

adamreeve merged 2 commits into
apache:mainfrom
ump45nose:fix/stream-export-dispose-batches

Conversation

@ump45nose

Copy link
Copy Markdown
Contributor

What's Changed

CArrowArrayStreamExporter.GetNext and CArrowDeviceArrayStreamExporter.GetNext read a batch from the managed stream, export it and never dispose it. The exported array takes its own references to the buffers, so releasing it on the consumer side doesn't free the batch's references, and with NativeMemoryAllocator the memory is only reclaimed by the finalizer.

This follows the third option from the issue: the exported array takes ownership of the batch.

  • ExportedAllocationOwner gets TakeOwnership(IDisposable). Owned objects are disposed in Dispose, after the exported buffers have been unpinned and their shared handles released, so memory managers that throw when disposed while pinned are not affected. This also covers buffers exported by pinning rather than through a shared handle.
  • CArrowArrayExporter and CArrowDeviceArrayExporter get internal ExportRecordBatch overloads with a transferOwnership flag. The stream exporters pass true, so a batch is disposed when the consumer releases the exported array (or right away if the export fails). The public ExportRecordBatch methods still leave the batch to the caller.

Batches returned by an IArrowArrayStream are owned by the caller, so streams that keep using a batch after handing it out would now see it disposed once the consumer releases it.

Tests: ExportedArrayStreamDisposesBatchesOnRelease and ExportedDeviceStreamDisposesBatchesOnRelease export a stream whose batch is built with TestMemoryAllocator, import it again, and check that disposing the imported batch returns every rented buffer. Both fail on main (buffers stay rented). The full Apache.Arrow.Tests suite passes on net8.0 locally (the pythonnet tests were skipped since Python isn't set up here).

This PR was prepared with AI assistance.

Closes #464.

CArrowArrayStreamExporter and CArrowDeviceArrayStreamExporter read each
batch from the managed stream, export it and then drop it without
disposing it. The exported array holds its own references to the
buffers, so when the consumer releases it only those references go
away; the batch's references are left for the finalizer, which in a
NativeAOT library may never run.

The exported array now takes ownership of the batch: the
ExportedAllocationOwner disposes it when the array is released, after
it has unpinned the exported buffers. Exporting a batch directly with
ExportRecordBatch is unchanged and still leaves the batch to the
caller.

Closes apache#464.

@adamreeve adamreeve left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me thanks @ump45nose, I just have one testing suggestion.

Comment thread test/Apache.Arrow.Tests/CDataInterfaceDataTests.cs

@adamreeve adamreeve left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me thanks @ump45nose

@adamreeve
adamreeve merged commit c964702 into apache:main Oct 22, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CArrowArrayStreamExporter never disposes exported record batches, leaving native memory to the finalizer

2 participants