1use super::{
21 instruments::Instruments,
22 incremental_averages::AtomicIncrementalAverage64,
23};
24use std::{
25 sync::{
26 Arc,
27 atomic::{
28 AtomicU64,
29 Ordering::Relaxed,
30 },
31 },
32 future::Future,
33 fmt::Debug,
34 time::Duration,
35 error::Error,
36 future,
37};
38use atomic_enum::atomic_enum;
39use futures::stream::{Stream,StreamExt};
40use tokio::time::timeout;
41use minstant::Instant;
43use log::{trace,info,warn,error};
44
45
46pub trait StreamExecutorStats: Debug {
48 fn executor_name(&self) -> &String;
49 fn futures_timeout(&self) -> &Duration;
50 fn creation_time(&self) -> &Instant;
51 fn executor_status(&self) -> &AtomicExecutorStatus;
52 fn execution_start_delta_nanos(&self) -> u64;
53 fn execution_finish_delta_nanos(&self) -> u64;
54 fn ok_events_avg_future_duration(&self) -> &AtomicIncrementalAverage64;
55 fn timed_out_events_avg_future_duration(&self) -> &AtomicIncrementalAverage64;
56 fn failed_events_avg_future_duration(&self) -> &AtomicIncrementalAverage64;
57 fn report_scheduled_to_finish(&self);
59}
60
61
62#[derive(Debug)]
64pub struct StreamExecutor<const INSTRUMENTS_USIZE: usize> {
65 executor_name: String,
66 futures_timeout: Duration,
67 creation_time: Instant,
68 executor_status: AtomicExecutorStatus,
69 execution_start_delta_nanos: AtomicU64,
70 execution_finish_delta_nanos: AtomicU64,
71
72 pub ok_events_avg_future_duration: AtomicIncrementalAverage64,
76
77 pub timed_out_events_avg_future_duration: AtomicIncrementalAverage64,
80
81 pub failed_events_avg_future_duration: AtomicIncrementalAverage64,
84
85 }
92
93macro_rules! on_executor_start {
105 ($self: expr, $future: expr, $fallible: expr, $futures_timeout: expr, $INSTRUMENTS: expr) => {
106 $self.register_execution_start();
107 if $INSTRUMENTS.logging() {
108 info!("✓✓✓✓ Stream Executor '{}' started: {}Futures{} / {}Fallible Items & {}Metrics",
109 $self.executor_name,
110 if $future {""} else {"Non-"},
111 if $future {
112 if $futures_timeout != Duration::ZERO {format!(" (with timeouts of {:?})", $futures_timeout)} else {" (NO timeouts)".to_string()}
113 } else {
114 "".to_string()
115 },
116 if $fallible {""} else {"Non-"},
117 if !$INSTRUMENTS.metrics() {"NO "} else {""});
118 }
119 }
120}
121
122macro_rules! on_non_timed_ok_item {
126 ($self: expr, $item: expr, $INSTRUMENTS: expr) => {
127 {
128 if $INSTRUMENTS.cheap_profiling() {
129 $self.ok_events_avg_future_duration.inc(-1.0); }
131 if $INSTRUMENTS.tracing() {
132 trace!("✓✓✓✓ Executor '{}' yielded '{:?}'", $self.executor_name, $item);
133 }
134 }
135 }
136}
137
138macro_rules! on_timed_ok_item {
143 ($self: expr, $item: expr, $elapsed: expr, $INSTRUMENTS: expr) => {
144 {
145 if $INSTRUMENTS.cheap_profiling() {
146 $self.ok_events_avg_future_duration.inc($elapsed.as_secs_f32());
147 } else {
148 panic!("\nThis macro can only be used if at least one of the Instruments' PROFILING are enabled -- otherwise you should use `on_non_timed_ok_item!(...)` instead");
149 }
150 if $INSTRUMENTS.tracing() {
151 trace!("✓✓✓✓ Executor '{}' yielded '{:?}' in {:?}", $self.executor_name, $item, $elapsed);
152 }
153 }
154 }
155}
156
157macro_rules! on_non_timed_err_item {
161 ($self: expr, $err: expr, $INSTRUMENTS: expr) => {
162 {
163 if $INSTRUMENTS.cheap_profiling() {
164 $self.failed_events_avg_future_duration.inc(-1.0); }
166 if $INSTRUMENTS.logging() {
167 error!("✗✗✗✗ Executor '{}' yielded ERROR '{:?}'", $self.executor_name, $err);
168 }
169 }
170 }
171}
172
173macro_rules! on_timed_err_item {
178 ($self: expr, $err: expr, $elapsed: expr, $INSTRUMENTS: expr) => {
179 {
180 if $INSTRUMENTS.cheap_profiling() {
181 $self.failed_events_avg_future_duration.inc($elapsed.as_secs_f32());
182 } else {
183 panic!("This macro can only be used if at least one of the Instruments' PROFILING are enabled -- otherwise you should use `on_non_timed_err_item!(...)` instead");
184 }
185 if $INSTRUMENTS.logging() {
186 error!("✗✗✗✗ Executor '{}' yielded ERROR '{:?}' in {:?}", $self.executor_name, $err, $elapsed);
187 }
188 }
189 }
190}
191
192macro_rules! on_executor_end {
198 ($self: expr, $future: expr, $fallible: expr, $futures_timeout: expr, $INSTRUMENTS: expr) => {
199 $self.register_execution_finish();
200 let stream_ended = $self.executor_status.load(Relaxed) == ExecutorStatus::StreamEnded;
201 let execution_nanos = $self.execution_finish_delta_nanos.load(Relaxed) - $self.execution_start_delta_nanos.load(Relaxed);
202 if $INSTRUMENTS.logging() && $INSTRUMENTS.cheap_profiling() {
203 let (ok_counter, ok_avg_seconds) = $self.ok_events_avg_future_duration.probe();
204 let (timed_out_counter, timed_out_avg_seconds) = $self.timed_out_events_avg_future_duration.probe();
205 let (failed_counter, failed_avg_seconds) = $self.failed_events_avg_future_duration.probe();
206 let execution_secs: f64 = Duration::from_nanos(execution_nanos).as_secs_f64() + f64::MIN_POSITIVE ;
207 let ok_stats = if $future {
208 format!("ok: {} events; avg {:?} - {:.5}/sec", ok_counter, Duration::from_secs_f32(ok_avg_seconds), ok_counter as f64 / execution_secs)
209 } else {
210 format!("ok: {} events", ok_counter)
211 };
212 let timed_out_stats = if $future && $futures_timeout != Duration::ZERO {
213 format!(" | time out: {} events; avg {:?} - {:.5}/sec", timed_out_counter, Duration::from_secs_f32(timed_out_avg_seconds), timed_out_counter as f64 / execution_secs)
214 } else {
215 format!("")
216 };
217 let failed_stats = if $future && $fallible {
218 format!(" | failed: {} events; avg {:?} - {:.5}/sec", failed_counter, Duration::from_secs_f32(failed_avg_seconds), failed_counter as f64 / execution_secs)
219 } else if $fallible {
220 format!(" | failed: {} events", failed_counter)
221 } else {
222 format!("")
223 };
224 warn!("✓✓✓✓ {} '{}' ended after running for {:?} -- stats: | {}{}{}",
225 if stream_ended {"Stream"} else {"Executor"},
226 $self.executor_name,
227 Duration::from_nanos(execution_nanos),
228 ok_stats,
229 timed_out_stats,
230 failed_stats);
231 } else if $INSTRUMENTS.logging() {
232 warn!("✓✓✓✓ {} '{}' ended after running for {:?} -- metrics were disabled",
233 if stream_ended {"Stream"} else {"Executor"},
234 $self.executor_name,
235 Duration::from_nanos(execution_nanos));
236 }
237 }
238}
239
240
241impl<const INSTRUMENTS_USIZE: usize>
242StreamExecutor<INSTRUMENTS_USIZE> {
243
244 const INSTRUMENTS: Instruments = {Instruments::from(INSTRUMENTS_USIZE)};
245
246 pub fn new<IntoString: Into<String>>(executor_name: IntoString) -> Arc<Self> {
248 Self::with_futures_timeout(executor_name, Duration::ZERO)
249 }
250
251 pub fn with_futures_timeout<IntoString: Into<String>>(executor_name: IntoString, futures_timeout: Duration) -> Arc<Self> {
253 Arc::new(Self {
254 executor_name: executor_name.into(),
255 futures_timeout,
256 creation_time: Instant::now(),
257 executor_status: AtomicExecutorStatus::new(ExecutorStatus::NotStarted),
258 execution_start_delta_nanos: AtomicU64::new(u64::MAX),
259 execution_finish_delta_nanos: AtomicU64::new(u64::MAX),
260 ok_events_avg_future_duration: AtomicIncrementalAverage64::new(),
261 failed_events_avg_future_duration: AtomicIncrementalAverage64::new(),
262 timed_out_events_avg_future_duration: AtomicIncrementalAverage64::new(),
263 })
264 }
265
266
267 fn register_execution_start(&self) {
269 self.executor_status.store(ExecutorStatus::Running, Relaxed);
270 self.execution_start_delta_nanos.store(self.creation_time.elapsed().as_nanos() as u64, Relaxed);
271 }
272
273 fn register_execution_finish(&self) {
275 loop {
276 if self.executor_status.compare_exchange(ExecutorStatus::Running, ExecutorStatus::StreamEnded, Relaxed, Relaxed).is_ok() ||
277 self.executor_status.compare_exchange(ExecutorStatus::ScheduledToFinish, ExecutorStatus::ProgrammaticallyEnded, Relaxed, Relaxed).is_ok() {
278 break
279 }
280 }
281 self.execution_finish_delta_nanos.store(self.creation_time.elapsed().as_nanos() as u64, Relaxed);
282 }
283
284 pub fn spawn_executor<OutItemType: Send + Debug,
291 FutureItemType: Future<Output=Result<OutItemType, Box<dyn std::error::Error + Send + Sync>>> + Send,
292 CloseVoidAsyncType: Future<Output=()> + Send + 'static,
293 ErrVoidAsyncType: Future<Output=()> + Send + 'static>
294 (self: Arc<Self>,
295 concurrency_limit: u32,
296 on_err_callback: impl Fn(Box<dyn Error + Send + Sync>) -> ErrVoidAsyncType + Send + Sync + 'static,
297 stream_ended_callback: impl FnOnce(Arc<dyn StreamExecutorStats + Send + Sync>) -> CloseVoidAsyncType + Send + Sync + 'static,
298 stream: impl Stream<Item=FutureItemType> + Send + 'static) {
299
300 match self.futures_timeout {
301
302 Duration::ZERO => {
304 tokio::spawn(async move {
305 let self_ref: &Self = &self;
306 let on_err_callback_ref = &on_err_callback;
307 on_executor_start!(self_ref, true, true, self_ref.futures_timeout, Self::INSTRUMENTS);
308 let mut start = Instant::now(); let item_processor = |future_element| {
310 async move {
311 if Self::INSTRUMENTS.cheap_profiling() {
312 start = Instant::now();
313 }
314 match future_element.await {
315 Ok(yielded_item) => {
316 if Self::INSTRUMENTS.cheap_profiling() {
317 let elapsed = start.elapsed();
318 on_timed_ok_item!(self_ref, yielded_item, elapsed, Self::INSTRUMENTS);
319 } else {
320 on_non_timed_ok_item!(self_ref, yielded_item, Self::INSTRUMENTS);
321 }
322 },
323 Err(err) => {
324 if Self::INSTRUMENTS.cheap_profiling() {
325 let elapsed = start.elapsed();
326 on_timed_err_item!(self_ref, err, elapsed, Self::INSTRUMENTS);
327 } else {
328 on_non_timed_err_item!(self_ref, err, Self::INSTRUMENTS);
329 }
330 on_err_callback_ref(err).await;
331 },
332 }
333 }
334 };
335 match concurrency_limit {
336 1 => stream.for_each(item_processor).await, _ => stream.for_each_concurrent(concurrency_limit as usize, item_processor).await,
338 }
339 on_executor_end!(self_ref, true, true, Duration::ZERO, Self::INSTRUMENTS);
340 stream_ended_callback(self).await;
341 });
342 },
343
344 _ => {
346 tokio::spawn(async move {
347 let self_ref: &Self = &self;
348 let on_err_callback_ref = &on_err_callback;
349 on_executor_start!(self_ref, true, true, self_ref.futures_timeout, Self::INSTRUMENTS);
350 let mut start = Instant::now(); let item_processor = |future_element| {
352 async move {
353 if Self::INSTRUMENTS.cheap_profiling() {
354 start = Instant::now();
355 }
356 match timeout(self_ref.futures_timeout, future_element).await {
357 Ok(non_timed_out_result) => match non_timed_out_result {
358 Ok(yielded_item) => {
359 if Self::INSTRUMENTS.cheap_profiling() {
360 let elapsed = start.elapsed();
361 on_timed_ok_item!(self_ref, yielded_item, elapsed, Self::INSTRUMENTS);
362 } else {
363 on_non_timed_ok_item!(self_ref, yielded_item, Self::INSTRUMENTS);
364 }
365 },
366 Err(err) => {
367 if Self::INSTRUMENTS.cheap_profiling() {
368 let elapsed = start.elapsed();
369 on_timed_err_item!(self_ref, err, elapsed, Self::INSTRUMENTS);
370 } else {
371 on_non_timed_err_item!(self_ref, err, Self::INSTRUMENTS);
372 }
373 on_err_callback_ref(err).await;
374 },
375 },
376 Err(_time_out_err) => {
377 if Self::INSTRUMENTS.cheap_profiling() {
378 let elapsed = start.elapsed();
379 self_ref.timed_out_events_avg_future_duration.inc(elapsed.as_secs_f32());
380 if Self::INSTRUMENTS.logging() {
381 error!("🕝🕝🕝🕝 Executor '{}' TIMED OUT after {:?}", self_ref.executor_name, elapsed);
382 }
383 } else if Self::INSTRUMENTS.logging() {
384 error!("🕝🕝🕝🕝 Executor '{}' TIMED OUT", self_ref.executor_name);
385 }
386 }
387 }
388 }
389 };
390 match concurrency_limit {
391 1 => stream.for_each(item_processor).await, _ => stream.for_each_concurrent(concurrency_limit as usize, item_processor).await,
393 }
394 on_executor_end!(self_ref, true, true, self_ref.futures_timeout, Self::INSTRUMENTS);
395 stream_ended_callback(self).await;
396 });
397 },
398
399 }
400 }
401
402 pub fn spawn_futures_executor<OutItemType: Send + Debug,
406 FutureItemType: Future<Output=OutItemType> + Send,
407 CloseVoidAsyncType: Future<Output=()> + Send + 'static>
408 (self: Arc<Self>,
409 concurrency_limit: u32,
410 stream_ended_callback: impl FnOnce(Arc<dyn StreamExecutorStats + Send + Sync>) -> CloseVoidAsyncType + Send + Sync + 'static,
411 stream: impl Stream<Item=FutureItemType> + Send + 'static) {
412
413 match self.futures_timeout {
414
415 Duration::ZERO => {
417 tokio::spawn(async move {
418 let self_ref: &Self = &self;
419 on_executor_start!(self_ref, true, true, self_ref.futures_timeout, Self::INSTRUMENTS);
420 let mut start = Instant::now(); let item_processor = |future_element| {
422 async move {
423 if Self::INSTRUMENTS.cheap_profiling() {
424 start = Instant::now();
425 }
426 let yielded_item = future_element.await;
427 if Self::INSTRUMENTS.cheap_profiling() {
428 let elapsed = start.elapsed();
429 on_timed_ok_item!(self_ref, yielded_item, elapsed, Self::INSTRUMENTS);
430 } else {
431 on_non_timed_ok_item!(self_ref, yielded_item, Self::INSTRUMENTS);
432 }
433 }
434 };
435 match concurrency_limit {
436 1 => stream.for_each(item_processor).await, _ => stream.for_each_concurrent(concurrency_limit as usize, item_processor).await,
438 }
439 on_executor_end!(self_ref, true, true, Duration::ZERO, Self::INSTRUMENTS);
440 stream_ended_callback(self).await;
441 });
442 },
443
444 _ => {
446 tokio::spawn(async move {
447 let self_ref: &Self = &self;
448 on_executor_start!(self_ref, true, true, self_ref.futures_timeout, Self::INSTRUMENTS);
449 let mut start = Instant::now(); let item_processor = |future_element| {
451 async move {
452 if Self::INSTRUMENTS.cheap_profiling() {
453 start = Instant::now();
454 }
455 match timeout(self_ref.futures_timeout, future_element).await {
456 Ok(non_timed_out_result) => {
457 if Self::INSTRUMENTS.cheap_profiling() {
458 let elapsed = start.elapsed();
459 on_timed_ok_item!(self_ref, non_timed_out_result, elapsed, Self::INSTRUMENTS);
460 } else {
461 on_non_timed_ok_item!(self_ref, non_timed_out_result, Self::INSTRUMENTS);
462 }
463 }
464 Err(_time_out_err) => {
465 if Self::INSTRUMENTS.cheap_profiling() {
466 let elapsed = start.elapsed();
467 self_ref.timed_out_events_avg_future_duration.inc(elapsed.as_secs_f32());
468 if Self::INSTRUMENTS.logging() {
469 error!("🕝🕝🕝🕝 Executor '{}' TIMED OUT after {:?}", self_ref.executor_name, elapsed);
470 }
471 } else if Self::INSTRUMENTS.logging() {
472 error!("🕝🕝🕝🕝 Executor '{}' TIMED OUT", self_ref.executor_name);
473 }
474 }
475 }
476 }
477 };
478 match concurrency_limit {
479 1 => stream.for_each(item_processor).await, _ => stream.for_each_concurrent(concurrency_limit as usize, item_processor).await,
481 }
482 on_executor_end!(self_ref, true, true, self_ref.futures_timeout, Self::INSTRUMENTS); stream_ended_callback(self).await;
484 });
485 },
486
487 }
488 }
489
490 pub fn spawn_fallibles_executor<OutItemType: Send + Debug,
495 CloseVoidAsyncType: Future<Output=()> + Send + 'static>
496 (self: Arc<Self>,
497 concurrency_limit: u32,
498 on_err_callback: impl Fn(Box<dyn Error + Send + Sync>) + Send + Sync + 'static,
499 stream_ended_callback: impl FnOnce(Arc<dyn StreamExecutorStats + Send + Sync>) -> CloseVoidAsyncType + Send + Sync + 'static,
500 stream: impl Stream<Item=Result<OutItemType, Box<dyn std::error::Error + Send + Sync>>> + Send + 'static) {
501
502 tokio::spawn(async move {
503 on_executor_start!(self, true, true, Duration::ZERO, Self::INSTRUMENTS);
504 let item_processor = |element| {
505 match element {
506 Ok(yielded_item) => {
507 on_non_timed_ok_item!(self, yielded_item, Self::INSTRUMENTS);
508 },
509 Err(err) => {
510 on_non_timed_err_item!(self, err, Self::INSTRUMENTS);
511 on_err_callback(err);
512 }
513 }
514 };
515 match concurrency_limit {
516 1 => stream.for_each(|item| {
518 item_processor(item);
519 future::ready(())
520 }).await,
521 _ => stream.for_each_concurrent(concurrency_limit as usize, |item| {
522 item_processor(item);
523 future::ready(())
524 }).await,
525 }
526 on_executor_end!(self, false, true, Duration::ZERO, Self::INSTRUMENTS);
527 stream_ended_callback(self).await;
528 });
529 }
530
531 pub fn spawn_non_futures_executor<ItemType: Send + Debug,
536 VoidAsyncType: Future<Output=()> + Send + 'static>
537 (self: Arc<Self>,
538 concurrency_limit: u32,
539 stream_ended_callback: impl FnOnce(Arc<dyn StreamExecutorStats + Send + Sync>) -> VoidAsyncType + Send + Sync + 'static,
540 stream: impl Stream<Item=Result<ItemType, Box<dyn std::error::Error + Send + Sync>>> + Send + 'static) {
541 tokio::spawn(async move {
542 on_executor_start!(self, false, true, Duration::ZERO, Self::INSTRUMENTS);
543 let item_processor = |fallible_element| {
544 match fallible_element {
545 Ok(yielded_item) => on_non_timed_ok_item!(self, yielded_item, Self::INSTRUMENTS),
546 Err(err) => on_non_timed_err_item!(self, err, Self::INSTRUMENTS),
547 }
548 };
549 match concurrency_limit {
550 1 => stream.for_each(|fallible_item| {
552 item_processor(fallible_item);
553 future::ready(())
554 }).await,
555 _ => stream.for_each_concurrent(concurrency_limit as usize, |fallible_item| {
556 item_processor(fallible_item);
557 future::ready(())
558 }).await,
559 }
560 on_executor_end!(self, false, true, Duration::ZERO, Self::INSTRUMENTS);
561 stream_ended_callback(self).await;
562 });
563 }
564
565 pub fn spawn_non_futures_non_fallibles_executor<OutItemType: Send + Debug,
568 VoidAsyncType: Future<Output=()> + Send + 'static>
569 (self: Arc<Self>,
570 concurrency_limit: u32,
571 stream_ended_callback: impl FnOnce(Arc<dyn StreamExecutorStats + Send + Sync>) -> VoidAsyncType + Send + Sync + 'static,
572 stream: impl Stream<Item=OutItemType> + Send + 'static) {
573
574 tokio::spawn(async move {
575 on_executor_start!(self, false, false, Duration::ZERO, Self::INSTRUMENTS);
576 let item_processor = |yielded_item| on_non_timed_ok_item!(self, yielded_item, Self::INSTRUMENTS);
577 match concurrency_limit {
578 1 => stream.for_each(|item| {
580 item_processor(item);
581 future::ready(())
582 }).await,
583 _ => stream.for_each_concurrent(concurrency_limit as usize, |item| {
584 item_processor(item);
585 future::ready(())
586 }).await,
587 }
588 on_executor_end!(self, false, false, Duration::ZERO, Self::INSTRUMENTS);
589 stream_ended_callback(self).await;
590 });
591 }
592
593}
594
595
596impl<const INSTRUMENTS_USIZE: usize> StreamExecutorStats for
597StreamExecutor<INSTRUMENTS_USIZE> {
598 fn executor_name(&self) -> &String {
600 &self.executor_name
601 }
602 fn futures_timeout(&self) -> &Duration {
603 &self.futures_timeout
604 }
605 fn creation_time(&self) -> &Instant {
606 &self.creation_time
607 }
608 fn executor_status(&self) -> &AtomicExecutorStatus {
609 &self.executor_status
610 }
611 fn execution_start_delta_nanos(&self) -> u64 {
612 self.execution_start_delta_nanos.load(Relaxed)
613 }
614 fn execution_finish_delta_nanos(&self) -> u64 {
615 self.execution_finish_delta_nanos.load(Relaxed)
616 }
617 fn ok_events_avg_future_duration(&self) -> &AtomicIncrementalAverage64 {
618 &self.ok_events_avg_future_duration
619 }
620 fn timed_out_events_avg_future_duration(&self) -> &AtomicIncrementalAverage64 {
621 &self.timed_out_events_avg_future_duration
622 }
623 fn failed_events_avg_future_duration(&self) -> &AtomicIncrementalAverage64 {
624 &self.failed_events_avg_future_duration
625 }
626 fn report_scheduled_to_finish(&self) {
627 self.executor_status.store(ExecutorStatus::ScheduledToFinish, Relaxed);
628 }
629}
630
631#[cfg(any(test,doc))]
634mod tests {
635 use super::*;
636 use std::sync::atomic::AtomicU32;
637 use futures::{
638 stream::{self, StreamExt},
639 channel::mpsc,
640 SinkExt,
641 };
642
643
644 #[ctor::ctor]
645 fn suite_setup() {
646 simple_logger::SimpleLogger::new().with_utc_timestamps().init().unwrap_or_else(|_| eprintln!("--> LOGGER WAS ALREADY STARTED"));
647 info!("minstant: is TSC / RDTSC instruction available for time measurement? {}", minstant::is_tsc_available());
648 }
649
650 #[cfg_attr(not(doc),tokio::test)]
654 async fn spawn_non_timeout_futures_fallible_executor_with_logs_and_metrics() {
655 assert_spawn_futures_fallible_executor::<{Instruments::LogsWithMetrics.into()}>(StreamExecutor::new("executor with logs & metrics")).await;
656 }
657
658 #[cfg_attr(not(doc),tokio::test)]
659 async fn spawn_non_timeout_futures_fallible_executor_with_metrics_and_no_logs() {
660 assert_spawn_futures_fallible_executor::<{Instruments::MetricsWithoutLogs.into()}>(StreamExecutor::new("executor with metrics & NO logs")).await;
661 }
662
663 #[cfg_attr(not(doc),tokio::test)]
664 async fn spawn_non_timeout_futures_fallible_executor_with_logs_and_no_metrics() {
665 assert_spawn_futures_fallible_executor::<{Instruments::LogsWithoutMetrics.into()}>(StreamExecutor::new("executor with logs & NO metrics")).await;
666 }
667
668 #[cfg_attr(not(doc),tokio::test)]
669 async fn spawn_non_timeout_futures_fallible_executor_with_no_logs_and_no_metrics() {
670 assert_spawn_futures_fallible_executor::<{Instruments::NoInstruments.into()}>(StreamExecutor::new("executor with NO logs & NO metrics")).await;
671 }
672
673 #[cfg_attr(not(doc),tokio::test)]
674 async fn spawn_timeout_futures_fallible_executor_with_logs_and_metrics() {
675 assert_spawn_futures_fallible_executor::<{Instruments::LogsWithMetrics.into()}>(StreamExecutor::with_futures_timeout("executor with logs & metrics", Duration::from_millis(100))).await;
676 }
677
678 #[cfg_attr(not(doc),tokio::test)]
679 async fn spawn_timeout_futures_fallible_executor_with_metrics_and_no_logs() {
680 assert_spawn_futures_fallible_executor::<{Instruments::MetricsWithoutLogs.into()}>(StreamExecutor::with_futures_timeout("executor with metrics & NO logs", Duration::from_millis(100))).await;
681 }
682
683 #[cfg_attr(not(doc),tokio::test)]
684 async fn spawn_timeout_futures_fallible_executor_with_logs_and_no_metrics() {
685 assert_spawn_futures_fallible_executor::<{Instruments::LogsWithoutMetrics.into()}>(StreamExecutor::with_futures_timeout("executor with logs & NO metrics", Duration::from_millis(100))).await;
686 }
687
688 #[cfg_attr(not(doc),tokio::test)]
689 async fn spawn_timeout_futures_fallible_executor_with_no_logs_and_no_metrics() {
690 assert_spawn_futures_fallible_executor::<{Instruments::NoInstruments.into()}>(StreamExecutor::with_futures_timeout("executor with NO logs & NO metrics", Duration::from_millis(100))).await;
691 }
692
693 async fn assert_spawn_futures_fallible_executor<const INSTRUMENTS: usize>
695 (executor: Arc<StreamExecutor<INSTRUMENTS>>) {
696
697 async fn to_future(item: Result<u32, Box<dyn Error + Send + Sync>>) -> Result<u32, Box<dyn Error + Send + Sync>> {
698 if item.is_ok() && item.as_ref().unwrap() > &100 {
700 tokio::time::sleep(Duration::from_millis(150)).await;
701 }
702 item
703 }
704
705 let (tx, mut rx) = mpsc::channel::<bool>(10);
706 let error_counter = Arc::new(AtomicU32::new(0));
707 let error_counter_ref = Arc::clone(&error_counter);
708 let cloned_executor = Arc::clone(&executor);
709 let timeout_enabled = *executor.futures_timeout() > Duration::ZERO;
710 let expected_timeout_count = if timeout_enabled {2} else {0};
711 executor.spawn_executor::<_, _, _, _>
712 (1,
713 move |_| { let error_counter = Arc::clone(&error_counter_ref); async move {error_counter.fetch_add(1, Relaxed);} },
714 move |_| { let mut tx = tx.clone(); async move {tx.send(true).await.unwrap()} },
715 stream::iter(vec![to_future(Ok(17)),
716 to_future(Err(Box::from("17"))),
717 to_future(Ok(170)), to_future(Ok(19)),
719 to_future(Err(Box::from("19"))),
720 to_future(Ok(190))] ));
722 assert!(rx.next().await.expect("consumption_done_reporter() wasn't called"), "consumption_done_reporter yielded the wrong value");
723 assert_eq!(error_counter.load(Relaxed), 2, "Error callback wasn't called the right number of times");
724 assert_metrics::<INSTRUMENTS>
725 (cloned_executor, 4 - expected_timeout_count, expected_timeout_count, 2);
726
727 }
728
729 #[cfg_attr(not(doc),tokio::test)]
733 async fn spawn_non_timeout_futures_executor_with_logs_and_metrics() {
734 assert_spawn_futures_executor::<{Instruments::LogsWithMetrics.into()}>(StreamExecutor::new("executor with logs & metrics")).await;
735 }
736
737 #[cfg_attr(not(doc),tokio::test)]
738 async fn spawn_non_timeout_futures_executor_with_metrics_and_no_logs() {
739 assert_spawn_futures_executor::<{Instruments::MetricsWithoutLogs.into()}>(StreamExecutor::new("executor with metrics & NO logs")).await;
740 }
741
742 #[cfg_attr(not(doc),tokio::test)]
743 async fn spawn_non_timeout_futures_executor_with_logs_and_no_metrics() {
744 assert_spawn_futures_executor::<{Instruments::LogsWithoutMetrics.into()}>(StreamExecutor::new("executor with logs & NO metrics")).await;
745 }
746
747 #[cfg_attr(not(doc),tokio::test)]
748 async fn spawn_non_timeout_futures_executor_with_no_logs_and_no_metrics() {
749 assert_spawn_futures_executor::<{Instruments::NoInstruments.into()}>(StreamExecutor::new("executor with NO logs & NO metrics")).await;
750 }
751
752 #[cfg_attr(not(doc),tokio::test)]
753 async fn spawn_timeout_futures_executor_with_logs_and_metrics() {
754 assert_spawn_futures_executor::<{Instruments::LogsWithMetrics.into()}>(StreamExecutor::with_futures_timeout("executor with logs & metrics", Duration::from_millis(100))).await;
755 }
756
757 #[cfg_attr(not(doc),tokio::test)]
758 async fn spawn_timeout_futures_executor_with_metrics_and_no_logs() {
759 assert_spawn_futures_executor::<{Instruments::MetricsWithoutLogs.into()}>(StreamExecutor::with_futures_timeout("executor with metrics & NO logs", Duration::from_millis(100))).await;
760 }
761
762 #[cfg_attr(not(doc),tokio::test)]
763 async fn spawn_timeout_futures_executor_with_logs_and_no_metrics() {
764 assert_spawn_futures_executor::<{Instruments::LogsWithoutMetrics.into()}>(StreamExecutor::with_futures_timeout("executor with logs & NO metrics", Duration::from_millis(100))).await;
765 }
766
767 #[cfg_attr(not(doc),tokio::test)]
768 async fn spawn_timeout_futures_executor_with_no_logs_and_no_metrics() {
769 assert_spawn_futures_executor::<{Instruments::NoInstruments.into()}>(StreamExecutor::with_futures_timeout("executor with NO logs & NO metrics", Duration::from_millis(100))).await;
770 }
771
772 async fn assert_spawn_futures_executor<const INSTRUMENTS: usize>
774 (executor: Arc<StreamExecutor<INSTRUMENTS>>) {
775
776 async fn to_future(item: u32) -> u32 {
777 if item > 100 {
779 tokio::time::sleep(Duration::from_millis(150)).await;
780 }
781 item
782 }
783
784 let (tx, mut rx) = mpsc::channel::<bool>(10);
785 let cloned_executor = Arc::clone(&executor);
786 let timeout_enabled = executor.futures_timeout > Duration::ZERO;
787 let expected_timeout_count = if timeout_enabled {2} else {0};
788 executor.spawn_futures_executor::<_, _, _>
789 (1,
790 move |_| { let mut tx = tx.clone(); async move {tx.send(true).await.unwrap()} },
791 stream::iter(vec![to_future(17),
792 to_future(170), to_future(19),
794 to_future(190)] ));
796 assert!(rx.next().await.expect("consumption_done_reporter() wasn't called"), "consumption_done_reporter yielded the wrong value");
797 assert_metrics::<INSTRUMENTS>
798 (cloned_executor, 4 - expected_timeout_count, expected_timeout_count, 0);
799
800 }
801
802 #[cfg_attr(not(doc),tokio::test)]
806 async fn spawn_fallibles_executor_with_logs_and_metrics() {
807 assert_spawn_fallibles_executor::<{Instruments::LogsWithMetrics.into()}>(StreamExecutor::new("executor with logs & metrics")).await;
808 }
809
810 #[cfg_attr(not(doc),tokio::test)]
811 async fn spawn_fallibles_executor_with_metrics_and_no_logs() {
812 assert_spawn_fallibles_executor::<{Instruments::MetricsWithoutLogs.into()}>(StreamExecutor::new("executor with metrics & NO logs")).await;
813 }
814
815 #[cfg_attr(not(doc),tokio::test)]
816 async fn spawn_fallibles_executor_with_logs_and_no_metrics() {
817 assert_spawn_fallibles_executor::<{Instruments::LogsWithoutMetrics.into()}>(StreamExecutor::new("executor with logs & NO metrics")).await;
818 }
819
820 #[cfg_attr(not(doc),tokio::test)]
821 async fn spawn_fallibles_executor_with_no_logs_and_no_metrics() {
822 assert_spawn_fallibles_executor::<{Instruments::NoInstruments.into()}>(StreamExecutor::new("executor with NO logs & NO metrics")).await;
823 }
824
825 async fn assert_spawn_fallibles_executor<const INSTRUMENTS: usize>
827 (executor: Arc<StreamExecutor<INSTRUMENTS>>) {
828 let error_count = Arc::new(AtomicU32::new(0));
829 let error_count_ref = Arc::clone(&error_count);
830 let (mut tx, mut rx) = mpsc::channel::<bool>(10);
831 let cloned_executor = Arc::clone(&executor);
832 executor.spawn_fallibles_executor::<_, _>
833 (1,
834 move |_err| {
835 error_count_ref.fetch_add(1, Relaxed);
836 },
837 move |_| async move {
838 tx.send(true).await.unwrap()
839 },
840 stream::iter(vec![Ok(17), Ok(19), Err(Box::from(String::from("Error on 20th")))]) );
841 assert!(rx.next().await.expect("consumption_done_reporter() wasn't called"), "consumption_done_reporter yielded the wrong value");
842 assert_eq!(error_count.load(Relaxed), 1, "Error count is wrong, as computed by the error callback");
843 assert_metrics::<INSTRUMENTS>
844 (cloned_executor, 2, 0, 1);
845
846 }
847
848 #[cfg_attr(not(doc),tokio::test)]
852 async fn spawn_non_futures_non_fallibles_executor_with_logs_and_metrics() {
853 assert_spawn_non_futures_non_fallibles_executor::<{Instruments::LogsWithMetrics.into()}>(StreamExecutor::new("executor with logs & metrics")).await;
854 }
855
856 #[cfg_attr(not(doc),tokio::test)]
857 async fn spawn_non_futures_non_fallibles_executor_with_metrics_and_no_logs() {
858 assert_spawn_non_futures_non_fallibles_executor::<{Instruments::MetricsWithoutLogs.into()}>(StreamExecutor::new("executor with metrics & NO logs")).await;
859 }
860
861 #[cfg_attr(not(doc),tokio::test)]
862 async fn spawn_non_futures_non_fallibles_executor_with_logs_and_no_metrics() {
863 assert_spawn_non_futures_non_fallibles_executor::<{Instruments::LogsWithoutMetrics.into()}>(StreamExecutor::new("executor with logs & NO metrics")).await;
864 }
865
866 #[cfg_attr(not(doc),tokio::test)]
867 async fn spawn_non_futures_non_fallibles_executor_with_no_logs_and_no_metrics() {
868 assert_spawn_non_futures_non_fallibles_executor::<{Instruments::NoInstruments.into()}>(StreamExecutor::new("executor with NO logs & NO metrics")).await;
869 }
870
871 async fn assert_spawn_non_futures_non_fallibles_executor<const INSTRUMENTS: usize>
873 (executor: Arc<StreamExecutor<INSTRUMENTS>>) {
874 let (mut tx, mut rx) = mpsc::channel::<bool>(10);
875 let cloned_executor = Arc::clone(&executor);
876 executor.spawn_non_futures_non_fallibles_executor::<_, _>
877 (1,
878 move |_| async move {tx.send(true).await.unwrap()},
879 stream::iter(vec![17, 19]) );
880 assert!(rx.next().await.expect("consumption_done_reporter() wasn't called"), "consumption_done_reporter yielded the wrong value");
881 assert_metrics::<INSTRUMENTS>
882 (cloned_executor, 2, 0, 0);
883
884 }
885
886 fn assert_metrics<const INSTRUMENTS: usize>
891 (executor: Arc<StreamExecutor<INSTRUMENTS>>,
892 expected_ok_counter: u32,
893 expected_timed_out_counter: u32,
894 expected_failed_counter: u32) {
895
896 println!("### Stats assertions for Stream pipeline executor named '{}' (Logs? {}; Metrics? {}) ####",
897 executor.executor_name, Instruments::from(INSTRUMENTS).logging(), Instruments::from(INSTRUMENTS).metrics());
898 let creation_duration = executor.creation_time.elapsed();
899 let execution_start_delta_nanos = executor.execution_start_delta_nanos.load(Relaxed);
900 let execution_finish_delta_nanos = executor.execution_finish_delta_nanos.load(Relaxed);
901 let (ok_counter, ok_average) = executor.ok_events_avg_future_duration.lightweight_probe();
902 let (timed_out_counter, timed_out_average) = executor.timed_out_events_avg_future_duration.lightweight_probe();
903 let (failed_counter, failed_average) = executor.failed_events_avg_future_duration.lightweight_probe();
904 println!("Creation time: {:?} ago", creation_duration);
905 println!("Execution Start: {:?} after creation", Duration::from_nanos(execution_start_delta_nanos));
906 println!("Execution Finish: {:?} after creation", Duration::from_nanos(execution_finish_delta_nanos));
907 println!("OK elements count: {ok_counter}; OK elements average Future resolution time: {ok_average}s{}{}",
908 if Instruments::from(INSTRUMENTS).metrics() {""} else {" -- metrics are DISABLED"},
909 if Instruments::from(INSTRUMENTS).logging() {" -- verify these values against the \"executor closed\" message"} else {" -- logs are DISABLED"});
910 println!("TIMED OUT elements count: {timed_out_counter}; TIMED OUT elements average Future resolution time: {timed_out_average}s{}{}",
911 if Instruments::from(INSTRUMENTS).metrics() {""} else {" -- metrics are DISABLED"},
912 if Instruments::from(INSTRUMENTS).logging() {" -- verify these values against the \"executor closed\" message"} else {" -- logs are DISABLED"});
913 println!("FAILED elements count: {failed_counter}; FAILED elements average Future resolution time: {failed_average}s{}{}",
914 if Instruments::from(INSTRUMENTS).metrics() {""} else {" -- metrics are DISABLED"},
915 if Instruments::from(INSTRUMENTS).logging() {" -- verify these values against the \"executor closed\" message"} else {" -- logs are DISABLED"});
916
917 assert_ne!(execution_start_delta_nanos, u64::MAX, "'execution_start_delta_nanos' wasn't set");
918 assert_ne!(execution_finish_delta_nanos, u64::MAX, "'execution_finish_delta_nanos' wasn't set");
919 assert!(execution_finish_delta_nanos >= execution_start_delta_nanos, "INSTRUMENTATION ERROR: 'execution_start_delta_nanos' was set after 'execution_finish_delta_nanos'");
920
921 if Instruments::from(INSTRUMENTS).metrics() {
922 assert_eq!(ok_counter, expected_ok_counter, "OK elements counter doesn't match -- Metrics are ENABLED");
923 assert_eq!(timed_out_counter, expected_timed_out_counter, "TIMED OUT elements counter doesn't match -- Metrics are ENABLED");
924 assert_eq!(failed_counter, expected_failed_counter, "FAILED elements counter doesn't match -- Metrics are ENABLED");
925 } else {
926 assert_eq!(ok_counter, 0, "Metrics are DISABLED, so the reported OK elements should be ZERO");
927 assert_eq!(timed_out_counter, 0, "Metrics are DISABLED, so the reported TIMED OUT elements should be ZERO");
928 assert_eq!(failed_counter, 0, "Metrics are DISABLED, so the reported FAILED elements should be ZERO");
929 }
930
931
932 }
935}
936
937#[atomic_enum]
939#[derive(PartialEq)]
940pub enum ExecutorStatus {
941 NotStarted,
942 Running,
943 ScheduledToFinish,
944 ProgrammaticallyEnded,
945 StreamEnded,
946}