use std::cmp;
use std::sync::Arc;
use anyhow::Result;
use tokio::sync::mpsc;
use crate::config::StreamConfig;
use crate::types::QueryResponse;
use crate::Client;
use hypersync_solana_net_types::query::SolanaQuery;
pub fn stream_arrow(
client: Arc<Client>,
query: SolanaQuery,
config: StreamConfig,
) -> mpsc::Receiver<Result<QueryResponse>> {
let (tx, rx) = mpsc::channel(config.concurrency * 2);
tokio::task::spawn(async move {
let from = query.from_slot;
let to = query.to_slot.unwrap_or(u64::MAX);
let mut current = from;
let mut batch_size = config.batch_size;
while current < to {
let mut chunks = Vec::with_capacity(config.concurrency);
let mut chunk_end = current;
for _ in 0..config.concurrency {
if chunk_end >= to {
break;
}
let end = cmp::min(to, chunk_end + batch_size);
let mut q = query.clone();
q.from_slot = chunk_end;
q.to_slot = Some(end);
let (chunk_tx, chunk_rx) = mpsc::channel::<Result<QueryResponse>>(2);
let client = client.clone();
let handle = tokio::spawn(async move {
let mut cur = q.from_slot;
let chunk_to = q.to_slot.expect("chunk to_slot is always set");
while cur < chunk_to {
q.from_slot = cur;
match client.get_arrow(&q).await {
Ok(resp) => {
if resp.next_slot <= cur {
let _ = chunk_tx
.send(Err(anyhow::anyhow!(
"server made no progress at slot {cur}"
)))
.await;
return;
}
cur = resp.next_slot;
if chunk_tx.send(Ok(resp)).await.is_err() {
return;
}
}
Err(e) => {
let _ = chunk_tx.send(Err(e)).await;
return;
}
}
}
});
chunks.push((chunk_rx, handle));
chunk_end = end;
}
if chunks.is_empty() {
break;
}
for (mut chunk_rx, handle) in chunks {
while let Some(result) = chunk_rx.recv().await {
if let Ok(resp) = &result {
let bytes = resp.response_bytes as u64;
if bytes > config.response_bytes_ceiling
&& batch_size > config.min_batch_size
{
batch_size = cmp::max(config.min_batch_size, batch_size / 2);
tracing::debug!(
batch_size,
response_bytes = bytes,
"Shrunk batch size (response too large)"
);
} else if bytes < config.response_bytes_floor
&& batch_size < config.max_batch_size
{
batch_size = cmp::min(config.max_batch_size, batch_size * 2);
tracing::debug!(
batch_size,
response_bytes = bytes,
"Grew batch size (response too small)"
);
}
current = resp.next_slot;
}
let is_err = result.is_err();
if tx.send(result).await.is_err() {
tracing::warn!("Stream receiver dropped");
return;
}
if is_err {
return;
}
}
if let Err(e) = handle.await {
let _ = tx.send(Err(anyhow::anyhow!("chunk task failed: {}", e))).await;
return;
}
}
}
});
rx
}