use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use bytes::Bytes;
use futures_core::Stream;
use futures_util::StreamExt;
use http::Response;
use minarrow::{Table, Vec64};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::TcpStream;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use crate::enums::{BufferChunkSize, IPCMessageProtocol};
use crate::models::decoders::ipc::table_stream_decoder::TableStreamDecoder;
use crate::models::decoders::limits::DecodeLimits;
use crate::models::streams::http::{H2RecvRead, HttpByteStream};
use crate::traits::parallel_transport_reader::{ParallelTransportReader, SortBehaviour};
use crate::traits::parallel_transport_writer::SEQ_ID_META_KEY;
const STREAM_WINDOW_BYTES: u32 = 8 * 1024 * 1024;
const STREAM_CHANNEL_DEPTH: usize = 8;
type StreamItem = io::Result<(Table, Option<u64>)>;
pub struct HttpParallelTableReader {
streams: Vec<mpsc::Receiver<StreamItem>>,
tasks: Vec<JoinHandle<()>>,
driver: JoinHandle<()>,
stream_count: usize,
sort: SortBehaviour,
cursor: usize,
closed: Vec<bool>,
}
impl HttpParallelTableReader {
pub async fn accept<T>(
mut connection: h2::server::Connection<T, Bytes>,
stream_count: usize,
sort: SortBehaviour,
limits: Option<DecodeLimits>,
) -> io::Result<Self>
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
assert!(stream_count >= 1, "stream_count must be at least 1");
let mut streams = Vec::with_capacity(stream_count);
let mut tasks = Vec::with_capacity(stream_count);
for _ in 0..stream_count {
let (request, mut respond) = connection
.accept()
.await
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::UnexpectedEof,
"h2 connection closed before all streams were accepted",
)
})?
.map_err(io::Error::other)?;
let response = Response::builder()
.status(200)
.body(())
.map_err(io::Error::other)?;
respond
.send_response(response, true)
.map_err(io::Error::other)?;
let mut decoder = TableStreamDecoder::<Vec64<u8>>::new(
HttpByteStream::new(H2RecvRead::new(request.into_body()), BufferChunkSize::Http),
BufferChunkSize::Http.chunk_size(),
IPCMessageProtocol::Stream,
limits,
);
let (tx, rx) = mpsc::channel(STREAM_CHANNEL_DEPTH);
let task = tokio::spawn(async move {
loop {
match decoder.read_keyed().await {
Some(Ok((table, kv))) => {
let seq = if sort == SortBehaviour::None {
None
} else {
kv.and_then(|pairs| {
pairs
.into_iter()
.find(|k| k.key == SEQ_ID_META_KEY)
.and_then(|k| k.value.parse::<u64>().ok())
})
};
if tx.send(Ok((table, seq))).await.is_err() {
break;
}
}
Some(Err(e)) => {
let _ = tx.send(Err(e)).await;
break;
}
None => break,
}
}
});
streams.push(rx);
tasks.push(task);
}
let driver = tokio::spawn(async move { while connection.accept().await.is_some() {} });
Ok(Self {
streams,
tasks,
driver,
stream_count,
sort,
cursor: 0,
closed: vec![false; stream_count],
})
}
pub async fn from_tcp(
tcp: TcpStream,
stream_count: usize,
sort: SortBehaviour,
limits: Option<DecodeLimits>,
) -> io::Result<Self> {
let connection_window =
(stream_count as u64 * STREAM_WINDOW_BYTES as u64).min(u32::MAX as u64) as u32;
let connection = h2::server::Builder::new()
.initial_window_size(STREAM_WINDOW_BYTES)
.initial_connection_window_size(connection_window)
.handshake::<_, Bytes>(tcp)
.await
.map_err(io::Error::other)?;
Self::accept(connection, stream_count, sort, limits).await
}
}
impl Stream for HttpParallelTableReader {
type Item = StreamItem;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
if this.sort == SortBehaviour::Ordered {
let idx = this.cursor % this.stream_count;
return match this.streams[idx].poll_recv(cx) {
Poll::Ready(Some(item)) => {
this.cursor += 1;
Poll::Ready(Some(item))
}
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
};
}
let n = this.stream_count;
let mut any_pending = false;
for offset in 0..n {
let idx = (this.cursor + offset) % n;
if this.closed[idx] {
continue;
}
match this.streams[idx].poll_recv(cx) {
Poll::Ready(Some(item)) => {
this.cursor = (idx + 1) % n;
return Poll::Ready(Some(item));
}
Poll::Ready(None) => this.closed[idx] = true,
Poll::Pending => any_pending = true,
}
}
if any_pending {
Poll::Pending
} else {
Poll::Ready(None)
}
}
}
impl ParallelTransportReader for HttpParallelTableReader {
fn stream_count(&self) -> usize {
self.stream_count
}
async fn read_all_tables(mut self) -> io::Result<Vec<(Table, Option<u64>)>> {
let mut out = Vec::new();
while let Some(item) = self.next().await {
out.push(item?);
}
Ok(out)
}
}
impl Drop for HttpParallelTableReader {
fn drop(&mut self) {
self.driver.abort();
for task in &self.tasks {
task.abort();
}
}
}