1use crate::batch::{FrameStream, batch_frames};
119use crate::{
120 BatchOptions, BatchStream, Executor, ExecutorError, Indexer, Input, InputCompletion, Lister,
121 Output, StreamExt, Writer,
122};
123use alloc::{boxed::Box, vec, vec::Vec};
124use core::{
125 fmt,
126 future::{Future, poll_fn},
127 marker::PhantomData,
128 pin::Pin,
129 task::Poll,
130};
131use std::{
132 ffi::OsString,
133 io::{self, Cursor},
134 process::Stdio,
135};
136use tokio::{process::Child, sync::watch};
137
138#[derive(Debug)]
140pub struct PipelineError {
141 pub stage: usize,
143 pub program: OsString,
145 pub error: ExecutorError,
147}
148
149impl fmt::Display for PipelineError {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 write!(
152 f,
153 "Pipeline stage {} ({}): {}",
154 self.stage,
155 self.program.to_string_lossy(),
156 self.error
157 )
158 }
159}
160
161impl core::error::Error for PipelineError {
162 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
163 Some(&self.error)
164 }
165}
166
167pub type PipelineStream = BatchStream<PipelineError>;
170
171mod sealed {
172 pub trait Sealed {}
173}
174
175pub trait PipelineProgram: sealed::Sealed + Into<PipelineStage> {}
178
179pub trait GraphProducer: PipelineProgram {}
181
182pub trait GraphConsumer: PipelineProgram {}
184
185macro_rules! programs {
186 (producer: $($producer:ty),*; consumer: $($consumer:ty),*; both: $($both:ty),*) => {
187 $(impl sealed::Sealed for $producer {}
188 impl PipelineProgram for $producer {}
189 impl GraphProducer for $producer {})*
190 $(impl sealed::Sealed for $consumer {}
191 impl PipelineProgram for $consumer {}
192 impl GraphConsumer for $consumer {})*
193 $(impl sealed::Sealed for $both {}
194 impl PipelineProgram for $both {}
195 impl GraphProducer for $both {}
196 impl GraphConsumer for $both {})*
197 };
198}
199programs! {
200 producer: crate::Adapter, crate::Emitter, crate::Fetcher, Lister, crate::Reader;
201 consumer: Writer, Indexer;
202 both: crate::Matcher, crate::Reasoner
203}
204
205#[derive(Debug)]
226pub struct Pipeline<P> {
227 stages: Vec<PipelineStage>,
228 batching: Option<BatchOptions>,
229 tail: PhantomData<fn() -> P>,
230}
231
232impl<P: PipelineProgram> Pipeline<P> {
233 pub fn new(program: P) -> Self {
236 Self {
237 stages: vec![program.into()],
238 batching: None,
239 tail: PhantomData,
240 }
241 }
242}
243
244impl<P: GraphProducer> Pipeline<P> {
245 pub fn pipe<N: GraphConsumer>(mut self, program: N) -> Pipeline<N> {
248 self.stages.push(program.into());
249 Pipeline {
250 stages: self.stages,
251 batching: self.batching,
252 tail: PhantomData,
253 }
254 }
255
256 #[must_use]
260 pub fn with_batching(mut self, options: BatchOptions) -> Self {
261 self.batching = Some(options);
262 self
263 }
264
265 pub async fn execute(self) -> Result<PipelineStream, PipelineError> {
270 let batching = self
271 .batching
272 .unwrap_or(self.stages.last().unwrap().batching);
273 Ok(batch_frames(self.execute_frames().await?, batching))
274 }
275
276 async fn execute_frames(self) -> Result<FrameStream<PipelineError>, PipelineError> {
277 let mut running = start(self.stages, true).await?;
278 let mut lines = running.lines.take();
279 let tail = running.tail.clone();
280 Ok(Box::pin(async_stream::try_stream! {
281 let mut completed = false;
282 if let Some(ref mut lines) = lines {
283 loop {
284 let event = tokio::select! {
285 result = running.wait(), if !completed => {
286 completed = true;
287 result.map(|_| None)
288 },
289 line = lines.next() => Ok(Some(line)),
290 }?;
291 let Some(line) = event else { continue };
292 match line {
293 Some(Ok(line)) => yield line,
294 Some(Err(error)) => {
295 running.cancel().await;
296 Err(tail.error(error))?;
297 },
298 None => break,
299 }
300 }
301 }
302 if !completed {
303 running.wait().await?;
304 }
305 }))
306 }
307}
308
309impl Pipeline<Writer> {
310 pub async fn execute(self) -> Result<Cursor<Vec<u8>>, PipelineError> {
314 let mut running = start(self.stages, false).await?;
315 Ok(Cursor::new(running.wait().await?))
316 }
317}
318
319impl Pipeline<Indexer> {
320 pub async fn execute(self) -> Result<(), PipelineError> {
322 start(self.stages, false).await?.wait().await?;
323 Ok(())
324 }
325}
326
327#[doc(hidden)]
329#[derive(Debug)]
330pub struct PipelineStage {
331 program: OsString,
332 kind: StageKind,
333 error: Option<ExecutorError>,
334 external_input: bool,
335 external_writer: bool,
336 batching: BatchOptions,
337}
338
339#[derive(Debug)]
340enum StageKind {
341 Native {
342 executor: Executor,
343 input: Input,
344 output: Output,
345 },
346 LimitedLister(Box<Lister>),
347}
348
349impl PipelineStage {
350 pub(crate) fn native(
351 mut executor: Executor,
352 input: Input,
353 output: Output,
354 error: Option<ExecutorError>,
355 ) -> Self {
356 Self {
357 program: executor.command().as_std().get_program().to_os_string(),
358 batching: executor.batch_options(),
359 external_input: !matches!(input, Input::Ignored),
360 external_writer: matches!(output, Output::AsyncWrite(_)),
361 kind: StageKind::Native {
362 executor,
363 input,
364 output,
365 },
366 error,
367 }
368 }
369
370 pub(crate) fn limited_lister(
371 lister: Lister,
372 program: OsString,
373 external_writer: bool,
374 error: Option<ExecutorError>,
375 batching: BatchOptions,
376 ) -> Self {
377 Self {
378 program,
379 kind: StageKind::LimitedLister(Box::new(lister)),
380 error,
381 external_input: false,
382 external_writer,
383 batching,
384 }
385 }
386}
387
388pub(crate) fn graph_formats(input: Option<&str>, output: Option<&str>) -> Option<ExecutorError> {
389 for (option, format) in [("input", input), ("output", output)] {
390 if let Some(format) = format {
391 if format != "jsonl" {
392 return Some(invalid(alloc::format!(
393 "pipeline graph {option} format must be jsonl, got {format}"
394 )));
395 }
396 }
397 }
398 None
399}
400
401fn invalid(message: impl Into<alloc::string::String>) -> ExecutorError {
402 io::Error::new(io::ErrorKind::InvalidInput, message.into()).into()
403}
404
405macro_rules! stage {
406 ($program:ty, $value:ident, $input:expr, $output:expr, $input_format:expr, $output_format:expr) => {
407 impl From<$program> for crate::pipeline::PipelineStage {
408 fn from($value: $program) -> Self {
409 let error = crate::pipeline::graph_formats($input_format, $output_format);
410 Self::native($value.executor, $input, $output, error)
411 }
412 }
413 };
414}
415pub(crate) use stage;
416
417#[derive(Clone)]
418struct StageInfo {
419 index: usize,
420 program: OsString,
421}
422
423impl StageInfo {
424 fn error(&self, error: impl Into<ExecutorError>) -> PipelineError {
425 PipelineError {
426 stage: self.index,
427 program: self.program.clone(),
428 error: error.into(),
429 }
430 }
431}
432
433type Job = Pin<Box<dyn Future<Output = Result<Vec<u8>, PipelineError>> + Send>>;
434
435struct Running {
436 jobs: Vec<Option<Job>>,
437 stop: watch::Sender<bool>,
438 lines: Option<FrameStream>,
439 tail: StageInfo,
440 failure: Option<PipelineError>,
441 output: Vec<u8>,
442}
443
444impl Running {
445 async fn next(&mut self) -> Option<(usize, Result<Vec<u8>, PipelineError>)> {
448 poll_fn(|cx| {
449 let mut pending = false;
450 for (index, job) in self.jobs.iter_mut().enumerate().rev() {
451 if let Some(future) = job {
452 match future.as_mut().poll(cx) {
453 Poll::Ready(result) => {
454 *job = None;
455 return Poll::Ready(Some((index, result)));
456 },
457 Poll::Pending => pending = true,
458 }
459 }
460 }
461 if pending {
462 Poll::Pending
463 } else {
464 Poll::Ready(None)
465 }
466 })
467 .await
468 }
469
470 async fn wait(&mut self) -> Result<Vec<u8>, PipelineError> {
471 while let Some((index, result)) = self.next().await {
472 match result {
473 Ok(bytes) if index + 1 == self.jobs.len() => self.output = bytes,
474 Ok(_) => {},
475 Err(error) if self.failure.is_none() => {
476 self.failure = Some(error);
477 self.stop.send_replace(true);
478 },
479 Err(_) => {},
480 }
481 }
482 match self.failure.take() {
483 Some(error) => Err(error),
484 None => Ok(core::mem::take(&mut self.output)),
485 }
486 }
487
488 async fn cancel(&mut self) {
489 self.stop.send_replace(true);
490 while self.next().await.is_some() {}
491 }
492}
493
494struct Spawned {
495 child: Child,
496 input: Input,
497 output: Output,
498 info: StageInfo,
499}
500
501async fn run_stage(
502 mut stage: Spawned,
503 mut stop: watch::Receiver<bool>,
504 source: Option<StageInfo>,
505) -> Result<Vec<u8>, PipelineError> {
506 let cancelled = *stop.borrow();
507 let completion = if cancelled {
508 None
509 } else {
510 tokio::select! {
511 biased;
512 result = crate::executor::communicate_child(&mut stage.child, &mut stage.input, &mut stage.output) => Some(result),
513 _ = stop.changed() => None,
514 }
515 };
516 match completion {
517 Some(result) => {
518 let completion = result.map_err(|error| stage.info.error(error))?;
519 let info = if matches!(completion.input, InputCompletion::SourceFailed(_)) {
520 source.as_ref().unwrap_or(&stage.info)
521 } else {
522 &stage.info
523 };
524 completion
525 .into_result()
526 .map(Cursor::into_inner)
527 .map_err(|error| info.error(error))
528 },
529 None => {
530 let _ = stage.child.start_kill();
531 let _ = stage.child.wait().await;
532 Ok(Vec::new())
533 },
534 }
535}
536
537async fn start(
538 mut stages: Vec<PipelineStage>,
539 capture_graph: bool,
540) -> Result<Running, PipelineError> {
541 let count = stages.len();
542 for (index, stage) in stages.iter_mut().enumerate() {
544 let info = StageInfo {
545 index,
546 program: stage.program.clone(),
547 };
548 if let Some(error) = stage.error.take() {
549 return Err(info.error(error));
550 }
551 if index != 0 && stage.external_input {
552 return Err(info.error(invalid(
553 "piped stages must use Input::Ignored; their input comes from the preceding stage",
554 )));
555 }
556 if index + 1 != count && stage.external_writer {
557 return Err(info.error(invalid("an intermediate pipeline stage cannot also forward stdout to an AsyncWrite destination")));
558 }
559 }
560 let tail = StageInfo {
561 index: count - 1,
562 program: stages.last().unwrap().program.clone(),
563 };
564 let (stop, receiver) = watch::channel(false);
565 let mut running = Running {
566 jobs: Vec::new(),
567 stop,
568 lines: None,
569 tail,
570 failure: None,
571 output: Vec::new(),
572 };
573 let mut source_info = None;
574 let mut limited_source = None;
575 if matches!(stages[0].kind, StageKind::LimitedLister(_)) {
576 let stage = stages.remove(0);
577 let info = StageInfo {
578 index: 0,
579 program: stage.program,
580 };
581 let StageKind::LimitedLister(mut lister) = stage.kind else {
582 unreachable!()
583 };
584 if stages.is_empty() {
585 running.lines = Some(
586 lister
587 .execute_frames()
588 .await
589 .map_err(|error| info.error(error))?,
590 );
591 return Ok(running);
592 }
593 limited_source = Some(lister);
594 source_info = Some(info);
595 }
596 let base = usize::from(source_info.is_some());
597 for index in 0..stages.len().saturating_sub(1) {
598 let (reader, writer) = io::pipe().map_err(|error| {
599 StageInfo {
600 index: index + base,
601 program: stages[index].program.clone(),
602 }
603 .error(error)
604 })?;
605 let StageKind::Native { executor, .. } = &mut stages[index].kind else {
606 unreachable!()
607 };
608 executor.command().stdout(Stdio::from(writer));
609 let StageKind::Native { executor, .. } = &mut stages[index + 1].kind else {
610 unreachable!()
611 };
612 executor.command().stdin(Stdio::from(reader));
613 }
614 if let Some(lister) = limited_source {
615 let source = lister
616 .into_pipeline_source()
617 .await
618 .map_err(|error| source_info.as_ref().unwrap().error(error))?;
619 let StageKind::Native {
620 executor, input, ..
621 } = &mut stages[0].kind
622 else {
623 unreachable!()
624 };
625 *input = Input::Jsonl(source);
626 executor.command().stdin(input.as_stdio());
627 }
628 let mut spawned: Vec<Spawned> = Vec::new();
629 let mut plans = stages.into_iter().enumerate();
630 while let Some((index, stage)) = plans.next() {
631 let info = StageInfo {
632 index: index + base,
633 program: stage.program,
634 };
635 let StageKind::Native {
636 mut executor,
637 input,
638 mut output,
639 } = stage.kind
640 else {
641 unreachable!()
642 };
643 if info.index + 1 != count {
644 output = Output::Ignored;
645 }
646 let result = executor.spawn().await;
647 drop(executor);
649 let mut child = match result {
650 Ok(child) => child,
651 Err(error) => {
652 drop(plans);
653 drop(input);
654 for stage in &mut spawned {
655 stage.input = Input::Ignored;
656 let _ = stage.child.start_kill();
657 }
658 for stage in &mut spawned {
659 let _ = stage.child.wait().await;
660 }
661 return Err(info.error(error));
662 },
663 };
664 if info.index + 1 == count && capture_graph && matches!(output, Output::Captured) {
665 running.lines = child.stdout.take().map(crate::jsonl::jsonl_frames);
666 }
667 spawned.push(Spawned {
668 child,
669 input,
670 output,
671 info,
672 });
673 }
674 for (index, stage) in spawned.into_iter().enumerate() {
675 running.jobs.push(Some(Box::pin(run_stage(
676 stage,
677 receiver.clone(),
678 if index == 0 {
679 source_info.clone()
680 } else {
681 None
682 },
683 ))));
684 }
685 Ok(running)
686}