use std::collections::HashMap;
use std::io;
use std::net::SocketAddr;
use minarrow::{Field, TableV, Vec64};
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use crate::models::frames::lightstream_message::LightstreamMessage;
use crate::models::writers::lightstream::LightstreamWriter;
const STREAM_CHANNEL_DEPTH: usize = 8;
pub struct LightstreamParallelWriter {
senders: Vec<mpsc::Sender<LightstreamMessage>>,
tasks: Vec<JoinHandle<io::Result<()>>>,
registry: HashMap<String, u8>,
next: usize,
}
impl LightstreamParallelWriter {
pub async fn connect(
addr: SocketAddr,
stream_count: usize,
messages: &[&str],
tables: &[(&str, Vec<Field>)],
) -> io::Result<Self> {
assert!(stream_count >= 1, "stream_count must be at least 1");
assert!(stream_count <= 256, "stream_count must be at most 256");
let mut senders = Vec::with_capacity(stream_count);
let mut tasks = Vec::with_capacity(stream_count);
let mut registry = HashMap::new();
for index in 0..stream_count {
let stream = TcpStream::connect(addr).await?;
let (_read, mut write) = stream.into_split();
write.write_all(&[index as u8]).await?;
let mut writer = LightstreamWriter::<_, Vec64<u8>>::new(write);
for name in messages {
let tag = writer.register_message(*name);
if index == 0 {
registry.insert(name.to_string(), tag);
}
}
for (name, schema) in tables {
let tag = writer.register_table(*name, schema.clone());
if index == 0 {
registry.insert(name.to_string(), tag);
}
}
let (tx, mut rx) = mpsc::channel::<LightstreamMessage>(STREAM_CHANNEL_DEPTH);
let task = tokio::spawn(async move {
while let Some(frame) = rx.recv().await {
writer.send_frame(&frame).await?;
}
writer.flush().await?;
writer.shutdown().await
});
senders.push(tx);
tasks.push(task);
}
Ok(Self { senders, tasks, registry, next: 0 })
}
pub fn stream_count(&self) -> usize {
self.senders.len()
}
pub async fn send_message(&mut self, name: &str, payload: Vec<u8>) -> io::Result<()> {
let tag = self.tag_for(name)?;
self.route(LightstreamMessage::Message { tag, payload }).await
}
pub async fn send_table(&mut self, name: &str, table: impl Into<TableV>) -> io::Result<()> {
let tag = self.tag_for(name)?;
self.route(LightstreamMessage::Table { tag, table: table.into() }).await
}
#[cfg(feature = "protobuf")]
pub async fn send_proto<M: prost::Message>(&mut self, name: &str, msg: &M) -> io::Result<()> {
self.send_message(name, msg.encode_to_vec()).await
}
fn tag_for(&self, name: &str) -> io::Result<u8> {
self.registry.get(name).copied().ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, format!("unknown type name '{name}'"))
})
}
async fn route(&mut self, frame: LightstreamMessage) -> io::Result<()> {
let idx = self.next % self.senders.len();
self.next = self.next.wrapping_add(1);
self.senders[idx].send(frame).await.map_err(|_| {
io::Error::new(io::ErrorKind::BrokenPipe, "Lightstream protocol connection task closed")
})
}
pub async fn finish(mut self) -> io::Result<()> {
self.senders.clear();
let mut first_err: Option<io::Error> = None;
for task in self.tasks.drain(..) {
match task.await {
Ok(Ok(())) => {}
Ok(Err(e)) => {
if first_err.is_none() {
first_err = Some(e);
}
}
Err(join_err) => {
if first_err.is_none() {
first_err = Some(io::Error::other(join_err));
}
}
}
}
match first_err {
Some(e) => Err(e),
None => Ok(()),
}
}
}