andygrove opened a new issue, #27069:
URL: https://github.com/apache/datafusion/issues/27069
### Describe the bug
On `branch-56` (`1635ed947`), `SortExec` panics when its input batches were
imported through the Arrow C Data Interface and are zero-copy slices of one
larger batch:
```
thread 'main' panicked at datafusion/physical-plan/src/sorts/sort.rs:1022:29:
assertion failed: reservation.size() + shared.size() >= reserved_bytes
```
The check is an `assert!`, so release builds panic too. 55.2.0 does not have
this code, so this is a regression in 56. The assertion and the shared-buffer
accounting behind it came from #25820, and #26242 changed the same code.
`RecordBatchMemoryCounter` identifies a buffer by `Buffer::data_ptr()` (the
start of the allocation) and sizes it with `Buffer::capacity()`. For a buffer
created by `Buffer::from_custom_allocation`, which is what
`arrow::ffi::from_ffi` uses, `capacity()` is the length declared at import, not
the size of the allocation. When sliced arrays go through FFI, every slice's
string values buffer starts at the same address, but each import declares a
different length (up to that slice's last offset). The counter keeps the first
length it sees. So `sort_run_sizes` computes a shared input size smaller than a
later run's `get_record_batch_memory_size`, and the assertion in `sort_run`
fails.
A run is only exposed when a batch passes through
`coalesce_in_mem_batches_into_runs` uncopied, i.e. when a single batch is about
`sort_in_place_threshold_bytes` (1 MB by default) or larger. Smaller batches
are concatenated into new buffers first.
DataFusion Comet hits this in TPC-DS q67a while testing 56
(apache/datafusion-comet#7249), where sort input arrives from the JVM over FFI.
I'd also expect any FFI consumer that sorts sliced input to be exposed, for
example datafusion-python with sliced pyarrow data, but I have not tested that.
### To Reproduce
Default configuration apart from one target partition. With `Cargo.toml`
dependencies `datafusion` (branch-56, `default-features = false`), `arrow = {
version = "60.1.0", features = ["ffi"] }` and `tokio`:
```rust
use std::sync::Arc;
use arrow::array::{Array, ArrayRef, Int32Array, StringArray, make_array};
use arrow::datatypes::{DataType, Field, Schema};
use arrow::ffi::{from_ffi, to_ffi};
use arrow::record_batch::RecordBatch;
use datafusion::datasource::MemTable;
use datafusion::prelude::*;
/// Round-trip an array through the Arrow C Data Interface.
fn ffi_roundtrip(a: &ArrayRef) -> ArrayRef {
let (array, schema) = to_ffi(&a.to_data()).unwrap();
make_array(unsafe { from_ffi(array, &schema) }.unwrap())
}
#[tokio::main]
async fn main() -> datafusion::error::Result<()> {
let n = 65_536;
let k: ArrayRef = Arc::new(Int32Array::from_iter_values((0..n).rev()));
let s: ArrayRef = Arc::new(StringArray::from_iter_values(
(0..n).map(|i| format!("{i:0>200}")),
));
let schema = Arc::new(Schema::new(vec![
Field::new("k", DataType::Int32, false),
Field::new("s", DataType::Utf8, false),
]));
let big = RecordBatch::try_new(Arc::clone(&schema), vec![k, s])?;
// Zero-copy slices of one batch (about 1.6 MB each), each imported over
FFI.
let batches: Vec<RecordBatch> = (0..n as usize)
.step_by(8192)
.map(|off| {
let b = big.slice(off, 8192);
let cols = b.columns().iter().map(ffi_roundtrip).collect();
RecordBatch::try_new(Arc::clone(&schema), cols).unwrap()
})
.collect();
let ctx =
SessionContext::new_with_config(SessionConfig::new().with_target_partitions(1));
ctx.register_table("t", Arc::new(MemTable::try_new(schema,
vec![batches])?))?;
let out = ctx.table("t").await?.sort(vec![col("k").sort(true,
false)])?.collect().await?;
println!("rows: {}", out.iter().map(|b| b.num_rows()).sum::<usize>());
Ok(())
}
```
`cargo run --release` panics with the assertion above.
A smaller unit test in `datafusion/physical-plan/src/sorts/sort.rs` shows
the same thing without FFI. It builds two batches whose `Int32` columns are
`Buffer::from_custom_allocation` buffers over the same pointer, declaring 16
and 1024 values, then sorts them with `sort_in_place_threshold_bytes = 0`.
### Expected behavior
The sort returns all 65,536 rows sorted, as it does on 55.2.0. Memory
accounting may be approximate for buffers whose real allocation size is
unknown, but it should not panic.
### Additional context
Possible directions: key the counter on `(data_ptr, capacity)`, or take the
larger capacity seen for an address; or turn the assertion into a resize that
covers any shortfall. Comet would need the fix backported to `branch-56`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]