use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use arrow_array::{Int64Array, RecordBatch, StringArray};
use arrow_schema::{DataType, Field, Schema};
use hudi_core::table::Table;
use tempfile::tempdir;
thread_local! {
static COUNT_ALLOCS: Cell<bool> = const { Cell::new(false) };
}
static MARKED_THREAD_ALLOCS: AtomicUsize = AtomicUsize::new(0);
struct CountingAllocator;
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if COUNT_ALLOCS.try_with(Cell::get).unwrap_or(false) {
MARKED_THREAD_ALLOCS.fetch_add(1, Ordering::Relaxed);
}
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static ALLOCATOR: CountingAllocator = CountingAllocator;
fn batch(ids: &[String], value: i64) -> RecordBatch {
let schema = Arc::new(Schema::new(vec![
Field::new("id", DataType::Utf8, false),
Field::new("value", DataType::Int64, false),
]));
RecordBatch::try_new(
schema,
vec![
Arc::new(StringArray::from_iter_values(ids)),
Arc::new(Int64Array::from(vec![value; ids.len()])),
],
)
.unwrap()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_cow_upsert_merge_runs_off_the_async_worker() {
const GROUPS: usize = 2;
const ROWS: usize = 20_000;
let dir = tempdir().unwrap();
let mut table = Table::create(dir.path().to_str().unwrap())
.with_table_name("trips")
.with_record_key_fields(["id"])
.with_metadata_table_enabled(false)
.with_option("hoodie.parquet.small.file.limit", "0")
.create()
.await
.unwrap();
let ids: Vec<String> = (0..GROUPS * ROWS).map(|i| format!("key{i:08}")).collect();
for group in ids.chunks(ROWS) {
table.append([batch(group, 1)]).await.unwrap();
}
tokio::spawn(async { COUNT_ALLOCS.with(|count| count.set(true)) })
.await
.unwrap();
MARKED_THREAD_ALLOCS.store(0, Ordering::SeqCst);
let result = table.upsert([batch(&ids, 2)]).await.unwrap();
let worker_allocs = MARKED_THREAD_ALLOCS.load(Ordering::SeqCst);
assert_eq!(result.num_updates, GROUPS * ROWS);
assert!(
worker_allocs < ROWS,
"the async worker made {worker_allocs} allocations during the upsert; the merge \
of {ROWS}-row file groups ran on it"
);
}