use std::sync::{Arc, LazyLock};
use crossbeam_channel::{bounded, Receiver, Sender};
use vyre_driver::{BackendError, CompiledPipeline, DispatchConfig};
use crate::pipeline::WgpuPipeline;
use crate::thread_pool::{BoundedWorkerJob, BoundedWorkerPool};
pub mod async_copy;
pub struct HostIngressStream {
runner:
Arc<dyn Fn(Vec<u8>, DispatchConfig) -> Result<Vec<Vec<u8>>, BackendError> + Send + Sync>,
config: DispatchConfig,
in_flight: Option<Receiver<Result<Vec<Vec<u8>>, BackendError>>>,
}
type ChunkResult = Result<Vec<Vec<u8>>, BackendError>;
struct ChunkJob {
runner:
Arc<dyn Fn(Vec<u8>, DispatchConfig) -> Result<Vec<Vec<u8>>, BackendError> + Send + Sync>,
bytes: Vec<u8>,
config: DispatchConfig,
response: Sender<ChunkResult>,
}
struct StreamingPool {
pool: BoundedWorkerPool<ChunkJob>,
}
impl StreamingPool {
fn global() -> Result<&'static Self, BackendError> {
static POOL: LazyLock<Result<StreamingPool, BackendError>> =
LazyLock::new(StreamingPool::new);
POOL.as_ref().map_err(|e| BackendError::new(e.to_string()))
}
fn new() -> Result<Self, BackendError> {
const JOB_QUEUE: usize = 64;
Ok(Self {
pool: BoundedWorkerPool::new(
JOB_QUEUE,
"vyre-wgpu-streaming",
"inspect the chunk program and GPU driver logs.",
"reduce process thread count or increase system nproc limit.",
)?,
})
}
fn submit(
&self,
runner: Arc<
dyn Fn(Vec<u8>, DispatchConfig) -> Result<Vec<Vec<u8>>, BackendError> + Send + Sync,
>,
bytes: Vec<u8>,
config: DispatchConfig,
) -> Result<Receiver<ChunkResult>, BackendError> {
let (sender, receiver) = bounded(1);
let job = ChunkJob {
runner,
bytes,
config,
response: sender,
};
self.pool.submit_blocking(
job,
"recreate the process; the global stream pool only closes during shutdown.",
)?;
Ok(receiver)
}
}
impl BoundedWorkerJob for ChunkJob {
type Output = Vec<Vec<u8>>;
fn response(&self) -> &Sender<ChunkResult> {
&self.response
}
fn run(self) -> ChunkResult {
(self.runner)(self.bytes, self.config)
}
}
impl HostIngressStream {
#[must_use]
pub fn new(pipeline: WgpuPipeline, config: DispatchConfig) -> Self {
let runner = Arc::new(move |bytes: Vec<u8>, config: DispatchConfig| {
pipeline.dispatch(&[bytes], &config)
});
Self {
runner,
config,
in_flight: None,
}
}
#[must_use]
pub fn from_runner<F>(runner: F, config: DispatchConfig) -> Self
where
F: Fn(Vec<u8>, DispatchConfig) -> Result<Vec<Vec<u8>>, BackendError>
+ Send
+ Sync
+ 'static,
{
Self {
runner: Arc::new(runner),
config,
in_flight: None,
}
}
pub fn push_chunk(&mut self, bytes: Vec<u8>) -> Result<Option<Vec<Vec<u8>>>, BackendError> {
let previous = self.take_finished()?;
let runner = Arc::clone(&self.runner);
let config = self.config.clone();
self.in_flight = Some(StreamingPool::global()?.submit(runner, bytes, config)?);
Ok(previous)
}
pub fn finish(&mut self) -> Result<Option<Vec<Vec<u8>>>, BackendError> {
self.take_finished()
}
fn take_finished(&mut self) -> Result<Option<Vec<Vec<u8>>>, BackendError> {
let Some(handle) = self.in_flight.take() else {
return Ok(None);
};
handle.recv().map_err(|error| {
BackendError::new(
format!("host-ingress worker ended before sending a result: {error}. Fix: inspect worker-pool lifecycle and GPU driver logs."),
)
})?
.map(Some)
}
}