blob: 4343e28bfb93994c7546d1fe9aad5b11378c70d9 [file]
// Copyright 2022 The Blaze Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::fmt::Debug;
use crate::io::read_one_batch;
use arrow::datatypes::SchemaRef;
use arrow::record_batch::RecordBatch;
use blaze_jni_bridge::{
jni_call, jni_get_object_class, jni_get_string, jni_new_direct_byte_buffer, jni_new_global_ref,
};
use datafusion::error::Result;
use datafusion::physical_plan::metrics::{BaselineMetrics, Count};
use datafusion::physical_plan::RecordBatchStream;
use futures::Stream;
use jni::objects::{GlobalRef, JObject};
use jni::sys::{jboolean, jint, jlong, JNI_TRUE};
use std::fs::File;
use std::io::{BufReader, Read, SeekFrom};
use std::io::{Error as IoError, Seek};
use std::pin::Pin;
use std::task::Context;
use std::task::Poll;
#[derive(Debug, Clone, Copy)]
pub enum IpcReadMode {
/// for ConvertToNative
ChannelUncompressed,
/// for BroadcastExchange reader
Channel,
/// for ShuffleExchange reader
ChannelAndFileSegment,
}
pub struct IpcReaderStream {
schema: SchemaRef,
mode: IpcReadMode,
segments: GlobalRef,
reader: Option<RecordBatchReader>,
baseline_metrics: BaselineMetrics,
size_counter: Count,
}
unsafe impl Send for IpcReaderStream {}
impl IpcReaderStream {
pub fn new(
schema: SchemaRef,
segments: GlobalRef,
mode: IpcReadMode,
baseline_metrics: BaselineMetrics,
size_counter: Count,
) -> IpcReaderStream {
IpcReaderStream {
schema,
mode,
segments,
reader: None,
baseline_metrics,
size_counter,
}
}
fn next_segment(&mut self) -> Result<bool> {
let has_next = jni_call!(
ScalaIterator(self.segments.as_obj()).hasNext() -> jboolean
)?;
if has_next != JNI_TRUE {
self.reader = None;
return Ok(false);
}
let segment = jni_call!(
ScalaIterator(self.segments.as_obj()).next() -> JObject
)?;
let schema = self.schema.clone();
self.reader = Some(match self.mode {
IpcReadMode::ChannelUncompressed => {
get_channel_reader(Some(schema), segment.as_obj(), false)?
}
IpcReadMode::Channel => get_channel_reader(Some(schema), segment.as_obj(), true)?,
IpcReadMode::ChannelAndFileSegment => {
let segment_class = jni_get_object_class!(segment.as_obj())?;
let segment_classname_obj =
jni_call!(Class(segment_class.as_obj()).getName() -> JObject)?;
let segment_classname = jni_get_string!(segment_classname_obj.as_obj().into())?;
if segment_classname == "org.apache.spark.storage.FileSegment" {
get_file_segment_reader(Some(schema), segment.as_obj())?
} else {
get_channel_reader(Some(schema), segment.as_obj(), true)?
}
}
});
Ok(true)
}
}
pub fn get_channel_reader(
schema: Option<SchemaRef>,
channel: JObject,
compressed: bool,
) -> Result<RecordBatchReader> {
let global_ref = jni_new_global_ref!(channel)?;
let channel_reader = ReadableByteChannelReader::new(global_ref);
Ok(RecordBatchReader::new(
Box::new(BufReader::with_capacity(65536, channel_reader)),
schema,
compressed,
))
}
pub fn get_file_segment_reader(
schema: Option<SchemaRef>,
file_segment: JObject,
) -> Result<RecordBatchReader> {
let file = jni_call!(SparkFileSegment(file_segment).file() -> JObject)?;
let path = jni_call!(JavaFile(file.as_obj()).getPath() -> JObject)?;
let path = jni_get_string!(path.as_obj().into())?;
let offset = jni_call!(SparkFileSegment(file_segment).offset() -> jlong)?;
let length = jni_call!(SparkFileSegment(file_segment).length() -> jlong)?;
let mut file = File::open(path)?;
file.seek(SeekFrom::Start(offset as u64))?;
Ok(RecordBatchReader::new(
Box::new(file.take(length as u64)),
schema,
true,
))
}
impl Stream for IpcReaderStream {
type Item = Result<RecordBatch>;
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let elapsed_compute = self.baseline_metrics.elapsed_compute().clone();
let _timer = elapsed_compute.timer();
if let Some(reader) = &mut self.reader {
if let Some(batch) = reader.next_batch()? {
self.size_counter.add(batch.get_array_memory_size());
return self
.baseline_metrics
.record_poll(Poll::Ready(Some(Ok(batch))));
}
}
// current arrow file reader reaches EOF, try next ipc
if self.next_segment()? {
return self.poll_next(_cx);
}
Poll::Ready(None)
}
}
impl RecordBatchStream for IpcReaderStream {
fn schema(&self) -> SchemaRef {
self.schema.clone()
}
}
pub struct ReadableByteChannelReader {
channel: GlobalRef,
closed: bool,
}
impl ReadableByteChannelReader {
pub fn new(channel: GlobalRef) -> Self {
Self {
channel,
closed: false,
}
}
pub fn close(&mut self) -> Result<()> {
if !self.closed {
jni_call!(JavaReadableByteChannel(self.channel.as_obj()).close() -> ())?;
self.closed = true;
}
Ok(())
}
fn read_impl(&mut self, buf: &mut [u8]) -> Result<usize> {
if self.closed {
return Ok(0);
}
let buf = jni_new_direct_byte_buffer!(buf)?;
while {
let has_remaining = jni_call!(JavaBuffer(buf.as_obj()).hasRemaining() -> jboolean)?;
has_remaining == JNI_TRUE
} {
let read_bytes = jni_call!(JavaReadableByteChannel(self.channel.as_obj())
.read(buf.as_obj()) -> jint
)?;
if read_bytes < 0 {
self.close()?;
break;
}
}
let position = jni_call!(JavaBuffer(buf.as_obj()).position() -> jint)?;
Ok(position as usize)
}
}
impl Read for ReadableByteChannelReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.read_impl(buf).map_err(IoError::other)
}
}
impl Drop for ReadableByteChannelReader {
fn drop(&mut self) {
// ensure the channel is closed
let _ = self.close();
}
}
pub struct RecordBatchReader {
input: Box<dyn Read>,
schema: Option<SchemaRef>,
compress: bool,
}
impl RecordBatchReader {
pub fn new(input: Box<dyn Read>, schema: Option<SchemaRef>, compress: bool) -> Self {
Self {
input,
schema,
compress,
}
}
pub fn next_batch(&mut self) -> Result<Option<RecordBatch>> {
read_one_batch(&mut self.input, self.schema.clone(), self.compress)
}
}