1#![allow(unused)]
2use crate::common::status_tracker::ErrorDecision;
3use crate::engine::events::{
4 EventBus, EventEnvelope, EventPhase, EventType, ModuleGenerateEvent, ParserTaskModelEvent,
5 RequestEvent, TaskModelEvent,
6};
7use crate::engine::processors::event_processor::{EventAwareTypedChain, EventProcessorTrait};
8use log::info;
9
10use crate::common::context::PipelineContext;
11use crate::common::model::chain_key;
12use crate::common::model::message::{TaskErrorEvent, TaskEvent, TaskParserEvent, UnifiedTaskInput};
13use crate::common::model::{ModuleConfig, Request};
14use crate::errors::{Error, ModuleError, Result};
15use async_trait::async_trait;
16
17use crate::common::processors::processor::{
18 ProcessorContext, ProcessorResult, ProcessorTrait, RetryPolicy,
19};
20use crate::common::processors::processor_chain::ErrorStrategy;
21use crate::queue::{QueueManager, QueuedItem};
22use futures::stream::StreamExt;
23use log::{debug, error, warn};
24use metrics::counter;
25use serde_json::json;
26use std::marker::PhantomData;
27use std::sync::Arc;
28use std::time::{SystemTime, UNIX_EPOCH};
29use uuid::Uuid;
30
31use crate::engine::chain::backpressure::{BackpressureSendState, send_with_backpressure};
32
33#[derive(Debug, Clone)]
35pub enum ChainDecision {
36 Continue,
38 RetryAfter {
40 delay: std::time::Duration,
41 action_applied: bool,
42 },
43 TerminateModule {
45 reason: String,
46 action_applied: bool,
47 },
48 TerminateTask {
50 reason: String,
51 action_applied: bool,
52 },
53}
54
55impl ChainDecision {
56 fn action_applied(&self) -> bool {
57 match self {
58 ChainDecision::Continue => false,
59 ChainDecision::RetryAfter { action_applied, .. } => *action_applied,
60 ChainDecision::TerminateModule { action_applied, .. } => *action_applied,
61 ChainDecision::TerminateTask { action_applied, .. } => *action_applied,
62 }
63 }
64}
65
66#[cfg(test)]
67mod decision_tests {
68 use super::ChainDecision;
69
70 #[test]
71 fn chain_decision_action_applied_flags_are_stable() {
72 let continue_decision = ChainDecision::Continue;
73 assert!(!continue_decision.action_applied());
74
75 let retry_lua = ChainDecision::RetryAfter {
76 delay: std::time::Duration::from_millis(1000),
77 action_applied: true,
78 };
79 let retry_fallback = ChainDecision::RetryAfter {
80 delay: std::time::Duration::from_millis(1000),
81 action_applied: false,
82 };
83 assert!(retry_lua.action_applied());
84 assert!(!retry_fallback.action_applied());
85
86 let terminate_module_lua = ChainDecision::TerminateModule {
87 reason: "module limit".to_string(),
88 action_applied: true,
89 };
90 let terminate_module_fallback = ChainDecision::TerminateModule {
91 reason: "module limit".to_string(),
92 action_applied: false,
93 };
94 assert!(terminate_module_lua.action_applied());
95 assert!(!terminate_module_fallback.action_applied());
96
97 let terminate_task_lua = ChainDecision::TerminateTask {
98 reason: "task limit".to_string(),
99 action_applied: true,
100 };
101 let terminate_task_fallback = ChainDecision::TerminateTask {
102 reason: "task limit".to_string(),
103 action_applied: false,
104 };
105 assert!(terminate_task_lua.action_applied());
106 assert!(!terminate_task_fallback.action_applied());
107 }
108}
109
110#[async_trait]
111pub trait ThresholdDecisionService: Send + Sync {
113 async fn task_precheck(&self, task_id: &str) -> ChainDecision;
115 async fn error_task_decide(&self, input: &TaskErrorEvent) -> ChainDecision;
117}
118
119#[derive(Clone)]
121pub struct StatusTrackerThresholdDecisionService {
122 state: Arc<PipelineContext>,
123}
124
125impl StatusTrackerThresholdDecisionService {
126 pub fn new(state: Arc<PipelineContext>) -> Self {
128 Self { state }
129 }
130}
131
132#[async_trait]
133impl ThresholdDecisionService for StatusTrackerThresholdDecisionService {
134 async fn task_precheck(&self, task_id: &str) -> ChainDecision {
135 match self
136 .state
137 .status_tracker
138 .should_task_continue(task_id)
139 .await
140 {
141 Ok(ErrorDecision::Continue) => ChainDecision::Continue,
142 Ok(ErrorDecision::Terminate(reason)) => ChainDecision::TerminateTask {
143 reason,
144 action_applied: false,
145 },
146 Err(err) => {
147 warn!(
148 "[ThresholdDecisionService] task precheck failed: task_id={} error={}, fallback=continue",
149 task_id, err
150 );
151 ChainDecision::Continue
152 }
153 _ => ChainDecision::Continue,
154 }
155 }
156
157 async fn error_task_decide(&self, input: &TaskErrorEvent) -> ChainDecision {
158 let task_id = chain_key::task_runtime_id(
159 &input.account_task.platform,
160 &input.account_task.account,
161 input.run_id,
162 );
163
164 let parse_error: Error = ModuleError::ModuleNotFound(input.error_msg.clone().into()).into();
165 let mut retry_after: Option<std::time::Duration> = None;
166
167 if let Some(modules) = &input.account_task.module {
168 for module_name in modules {
169 let module_id = chain_key::module_runtime_id(
170 &input.account_task.account,
171 &input.account_task.platform,
172 module_name,
173 );
174 let module_id_scoped = format!("{}-{}", module_id, input.run_id);
176
177 if input.prefix_request != Uuid::nil() {
178 match self
179 .state
180 .status_tracker
181 .record_parse_error(
182 &task_id,
183 &module_id_scoped,
184 &input.prefix_request.to_string(),
185 &parse_error,
186 )
187 .await
188 {
189 Ok(ErrorDecision::RetryAfter(delay)) => {
190 retry_after = Some(delay);
191 }
192 Ok(_) => {}
193 Err(err) => {
194 warn!(
195 "[ThresholdDecisionService] record_parse_error failed: task_id={} module_id={} error={}",
196 task_id, module_id_scoped, err
197 );
198 }
199 }
200 }
201
202 match self
203 .state
204 .status_tracker
205 .should_module_continue(&module_id_scoped)
206 .await
207 {
208 Ok(ErrorDecision::Terminate(reason)) => {
209 return ChainDecision::TerminateModule {
210 reason,
211 action_applied: false,
212 };
213 }
214 Ok(_) => {}
215 Err(err) => {
216 warn!(
217 "[ThresholdDecisionService] module precheck failed: module_id={} error={}",
218 module_id, err
219 );
220 }
221 }
222 }
223 }
224
225 match self.task_precheck(&task_id).await {
226 ChainDecision::TerminateTask { reason, .. } => ChainDecision::TerminateTask {
227 reason,
228 action_applied: false,
229 },
230 _ => retry_after
231 .map(|delay| ChainDecision::RetryAfter {
232 delay,
233 action_applied: false,
234 })
235 .unwrap_or(ChainDecision::Continue),
236 }
237 }
238}
239
240pub struct TaskModelProcessor {
248 task_manager: Arc<TaskManager>,
249 state: Arc<PipelineContext>,
250 queue_manager: Arc<QueueManager>,
251 event_bus: Option<Arc<EventBus>>,
252 threshold_decision_service: Arc<dyn ThresholdDecisionService>,
253}
254
255impl TaskModelProcessor {
256 async fn task_precheck(&self, task_id: &str) -> ChainDecision {
257 self.threshold_decision_service.task_precheck(task_id).await
258 }
259
260 async fn error_task_decide(&self, input: &TaskErrorEvent) -> ChainDecision {
261 self.threshold_decision_service
262 .error_task_decide(input)
263 .await
264 }
265
266 async fn persist_error_retry_schedule(
267 &self,
268 input: &TaskErrorEvent,
269 delay: std::time::Duration,
270 ) {
271 let task_id = chain_key::task_runtime_id(
272 &input.account_task.platform,
273 &input.account_task.account,
274 input.run_id,
275 );
276 let module_id = input
277 .context
278 .module_id
279 .clone()
280 .or_else(|| {
281 input.account_task.module.as_ref().and_then(|m| {
282 m.first().map(|name| {
283 chain_key::module_runtime_id(
284 &input.account_task.account,
285 &input.account_task.platform,
286 name,
287 )
288 })
289 })
290 })
291 .unwrap_or_else(|| {
292 chain_key::module_runtime_id(
293 &input.account_task.account,
294 &input.account_task.platform,
295 "unknown",
296 )
297 });
298 let retry_key = chain_key::error_retry_schedule_key(&task_id);
299 let now_ms = SystemTime::now()
300 .duration_since(UNIX_EPOCH)
301 .unwrap_or_default()
302 .as_millis() as f64;
303 let score = now_ms + delay.as_millis() as f64;
304 let member = format!("{}:{}:{}", task_id, module_id, input.prefix_request);
305
306 if let Err(err) = self
307 .state
308 .cache_service
309 .zadd(&retry_key, score, member.as_bytes())
310 .await
311 {
312 warn!(
313 "[TaskModelProcessor<ErrorTaskModel>] failed to persist retry schedule: key={} error={}",
314 retry_key, err
315 );
316 }
317 }
318
319 async fn persist_terminate_mark(
320 &self,
321 input: &TaskErrorEvent,
322 terminate_module: Option<&str>,
323 reason: &str,
324 ) {
325 let task_id = chain_key::task_runtime_id(
326 &input.account_task.platform,
327 &input.account_task.account,
328 input.run_id,
329 );
330 let terminate_key = if let Some(module_id) = terminate_module {
331 chain_key::terminate_module_key(&task_id, module_id)
332 } else {
333 chain_key::terminate_task_key(&task_id)
334 };
335
336 let ttl_secs = {
337 let cfg = self.state.config.read().await;
338 cfg.cache.ttl
339 };
340 let payload = json!({
341 "reason": reason,
342 "task_id": task_id,
343 "module_id": terminate_module,
344 "prefix_request": input.prefix_request.to_string(),
345 })
346 .to_string();
347 if let Err(err) = self
348 .state
349 .cache_service
350 .set_nx(
351 &terminate_key,
352 payload.as_bytes(),
353 Some(std::time::Duration::from_secs(ttl_secs)),
354 )
355 .await
356 {
357 warn!(
358 "[TaskModelProcessor<ErrorTaskModel>] failed to persist terminate mark: key={} error={}",
359 terminate_key, err
360 );
361 }
362 }
363
364 async fn emit_threshold_terminated_event(
365 &self,
366 input: &TaskErrorEvent,
367 decision: &str,
368 reason: &str,
369 ) {
370 let Some(event_bus) = &self.event_bus else {
371 return;
372 };
373
374 let payload = json!({
375 "run_id": input.run_id.to_string(),
376 "account": input.account_task.account,
377 "platform": input.account_task.platform,
378 "module_id": input.context.module_id,
379 "step_idx": input.context.step_idx,
380 "prefix_request": input.prefix_request.to_string(),
381 "decision": decision,
382 "reason": reason,
383 });
384
385 if let Err(err) = event_bus
386 .publish(EventEnvelope::engine(
387 EventType::TaskTerminatedByThreshold,
388 EventPhase::Completed,
389 payload,
390 ))
391 .await
392 {
393 warn!(
394 "[TaskModelProcessor<ErrorTaskModel>] failed to publish threshold termination event: {}",
395 err
396 );
397 }
398 }
399}
400#[async_trait]
401impl ProcessorTrait<TaskEvent, Task> for TaskModelProcessor {
402 fn name(&self) -> &'static str {
403 "TaskModelProcessor"
404 }
405
406 async fn process(&self, input: TaskEvent, context: ProcessorContext) -> ProcessorResult<Task> {
407 debug!(
408 "[TaskModelProcessor] Processing Task: account={} platform={} modules={:?} retry={}",
409 input.account,
410 input.platform,
411 input.module,
412 context
413 .retry_policy
414 .as_ref()
415 .map(|r| r.current_retry)
416 .unwrap_or(0)
417 );
418
419 let task_id = chain_key::task_runtime_id(&input.platform, &input.account, input.run_id);
420 match self.task_precheck(&task_id).await {
421 ChainDecision::Continue => {}
422 ChainDecision::TerminateTask { reason, .. } => {
423 error!(
424 "[TaskModelProcessor<TaskModel>] task terminated (pre-check): task_id={} reason={}",
425 task_id, reason
426 );
427 if let Err(e) = self
428 .queue_manager
429 .send_to_dlq("task", &input, &reason)
430 .await
431 {
432 error!(
433 "[TaskModelProcessor<TaskModel>] failed to send to DLQ: {}",
434 e
435 );
436 }
437 return ProcessorResult::FatalFailure(
438 ModuleError::TaskMaxError(reason.into()).into(),
439 );
440 }
441 _ => {}
442 }
443
444 let task = self.task_manager.load_with_model(&input).await;
445 debug!(
446 "[TaskModelProcessor] load_with_model result: {:?}",
447 task.as_ref().map(|t| t.id())
448 );
449
450 match task {
451 Ok(task) => {
452 if task.is_empty() {
453 return ProcessorResult::FatalFailure(
454 ModuleError::ModuleNotFound(
455 format!(
456 "No modules found for the given TaskModel, task_model: {input:?}"
457 )
458 .into(),
459 )
460 .into(),
461 );
462 }
463 let default_locker_ttl = self.state.config.read().await.crawler.module_locker_ttl;
464 let mut all_locked = true;
465 for m in task.modules.iter() {
466 if !self
467 .state
468 .status_tracker
469 .is_module_locker(m.id().as_ref(), default_locker_ttl)
470 .await
471 {
472 all_locked = false;
473 break;
474 }
475 }
476 if all_locked {
477 warn!(
478 "[TaskModelProcessor<TaskModel>] all target modules locked, defer via retry policy: account={} platform={} run_id={}",
479 input.account, input.platform, input.run_id
480 );
481 return ProcessorResult::RetryableFailure(
486 context
487 .retry_policy
488 .unwrap_or_default()
489 .with_reason("all target modules locked".to_string()),
490 );
491 }
492
493 info!(
494 "[TaskModelProcessor] Successfully converted TaskModel to Task. Task ID: {}, Modules count: {}",
495 task.id(),
496 task.modules.len()
497 );
498
499 ProcessorResult::Success(task)
500 }
501 Err(e) => {
502 debug!(
503 "[TaskModelProcessor] load_with_model failed: account={} platform={} run_id={} error={e}",
504 input.account, input.platform, input.run_id
505 );
506 warn!(
507 "[TaskModelProcessor<TaskModel>] load_with_model failed, will retry: account={} platform={} run_id={} error={e}",
508 input.account, input.platform, input.run_id
509 );
510 ProcessorResult::RetryableFailure(
511 context
512 .retry_policy
513 .unwrap_or(RetryPolicy::default().with_reason(e.to_string())),
514 )
515 }
516 }
517 }
518 async fn pre_process(&self, _input: &TaskEvent, _context: &ProcessorContext) -> Result<()> {
519 Ok(())
520 }
521 async fn handle_error(
522 &self,
523 input: &TaskEvent,
524 error: Error,
525 _context: &ProcessorContext,
526 ) -> ProcessorResult<Task> {
527 let sender = self.queue_manager.get_error_push_channel();
528 let error_msg = TaskErrorEvent {
529 id: Default::default(),
530 account_task: input.clone(),
531 error_msg: error.to_string(),
532 timestamp: chrono::Utc::now().timestamp_millis() as u64,
533 metadata: Default::default(),
534 context: Default::default(),
535 run_id: input.run_id,
536 prefix_request: Uuid::nil(),
537 };
538 if let Err(e) = sender.send(QueuedItem::new(error_msg)).await {
539 error!("[TaskModelProcessor<TaskModel>] failed to enqueue ErrorTaskModel: {e}");
540 }
541 ProcessorResult::FatalFailure(
542 ModuleError::ModuleNotFound("Failed to load task, re-queued".into()).into(),
543 )
544 }
545}
546#[async_trait]
547impl EventProcessorTrait<TaskEvent, Task> for TaskModelProcessor {
548 fn pre_status(&self, input: &TaskEvent) -> Option<EventEnvelope> {
549 Some(EventEnvelope::engine(
550 EventType::TaskModel,
551 EventPhase::Started,
552 TaskModelEvent::from(input),
553 ))
554 }
555
556 fn finish_status(&self, input: &TaskEvent, output: &Task) -> Option<EventEnvelope> {
557 let mut task_model_event: TaskModelEvent = input.into();
558 task_model_event.modules = Some(output.get_module_names());
559 Some(EventEnvelope::engine(
560 EventType::TaskModel,
561 EventPhase::Completed,
562 task_model_event,
563 ))
564 }
565
566 fn working_status(&self, input: &TaskEvent) -> Option<EventEnvelope> {
567 Some(EventEnvelope::engine(
568 EventType::TaskModel,
569 EventPhase::Started,
570 TaskModelEvent::from(input),
571 ))
572 }
573
574 fn error_status(&self, input: &TaskEvent, error: &Error) -> Option<EventEnvelope> {
575 Some(EventEnvelope::engine_error(
576 EventType::TaskModel,
577 EventPhase::Failed,
578 TaskModelEvent::from(input),
579 error,
580 ))
581 }
582
583 fn retry_status(&self, input: &TaskEvent, retry_policy: &RetryPolicy) -> Option<EventEnvelope> {
584 Some(EventEnvelope::engine(
585 EventType::TaskModel,
586 EventPhase::Retry,
587 json!({
588 "data": TaskModelEvent::from(input),
589 "retry_count": retry_policy.current_retry,
590 "reason": retry_policy.reason.clone().unwrap_or_default(),
591 }),
592 ))
593 }
594}
595#[async_trait]
596impl ProcessorTrait<TaskParserEvent, Task> for TaskModelProcessor {
597 fn name(&self) -> &'static str {
598 "TaskModelProcessor"
599 }
600
601 async fn process(
602 &self,
603 input: TaskParserEvent,
604 context: ProcessorContext,
605 ) -> ProcessorResult<Task> {
606 debug!(
607 "[TaskModelProcessor<ParserTaskModel>] start: account={} platform={} crawler={:?} retry_count={}",
608 input.account_task.account,
609 input.account_task.platform,
610 input.account_task.module,
611 context
612 .retry_policy
613 .as_ref()
614 .map(|r| r.current_retry)
615 .unwrap_or(0)
616 );
617
618 let task_id = chain_key::task_runtime_id(
620 &input.account_task.platform,
621 &input.account_task.account,
622 input.run_id,
623 );
624 match self.task_precheck(&task_id).await {
625 ChainDecision::Continue => {
626 debug!(
627 "[TaskModelProcessor<ParserTaskModel>] task can continue: task_id={}",
628 task_id
629 );
630 }
631 ChainDecision::TerminateTask { reason, .. } => {
632 error!(
633 "[TaskModelProcessor<ParserTaskModel>] task terminated (pre-check): task_id={} reason={}",
634 task_id, reason
635 );
636 return ProcessorResult::FatalFailure(
637 ModuleError::TaskMaxError(reason.into()).into(),
638 );
639 }
640 _ => {}
641 }
642
643 let task = self.task_manager.load_parser(&input).await;
644 match task {
645 Ok(task) => ProcessorResult::Success(task),
646 Err(e) => {
647 warn!(
648 "[TaskModelProcessor<ParserTaskModel>] load_parser failed, will retry: account={} platform={} run_id={} error={e}",
649 input.account_task.account, input.account_task.platform, input.run_id
650 );
651 ProcessorResult::RetryableFailure(
652 context
653 .retry_policy
654 .unwrap_or(RetryPolicy::default().with_reason(e.to_string())),
655 )
656 }
657 }
658 }
659 async fn pre_process(
660 &self,
661 _input: &TaskParserEvent,
662 _context: &ProcessorContext,
663 ) -> Result<()> {
664 Ok(())
665 }
666 async fn handle_error(
667 &self,
668 input: &TaskParserEvent,
669 error: Error,
670 _context: &ProcessorContext,
671 ) -> ProcessorResult<Task> {
672 let sender = self.queue_manager.get_error_push_channel();
673 let error_msg = TaskErrorEvent {
674 id: Default::default(),
675 account_task: input.account_task.clone(),
676 error_msg: error.to_string(),
677 timestamp: chrono::Utc::now().timestamp_millis() as u64,
678 metadata: Default::default(),
679 context: Default::default(),
680 run_id: input.run_id,
681 prefix_request: input.prefix_request,
682 };
683 if let Err(e) = sender.send(QueuedItem::new(error_msg)).await {
684 error!("[TaskModelProcessor<ParserTaskModel>] failed to enqueue ErrorTaskModel: {e}");
685 }
686 ProcessorResult::FatalFailure(
687 ModuleError::ModuleNotFound("Failed to load task, re-queued".into()).into(),
688 )
689 }
690}
691#[async_trait]
692impl EventProcessorTrait<TaskParserEvent, Task> for TaskModelProcessor {
693 fn pre_status(&self, input: &TaskParserEvent) -> Option<EventEnvelope> {
694 Some(EventEnvelope::engine(
695 EventType::ParserTaskModel,
696 EventPhase::Started,
697 ParserTaskModelEvent::from(input),
698 ))
699 }
700
701 fn finish_status(&self, input: &TaskParserEvent, output: &Task) -> Option<EventEnvelope> {
702 let mut evt: ParserTaskModelEvent = input.into();
703 evt.modules = Some(output.get_module_names());
704 Some(EventEnvelope::engine(
705 EventType::ParserTaskModel,
706 EventPhase::Completed,
707 evt,
708 ))
709 }
710
711 fn working_status(&self, input: &TaskParserEvent) -> Option<EventEnvelope> {
712 Some(EventEnvelope::engine(
713 EventType::ParserTaskModel,
714 EventPhase::Started,
715 ParserTaskModelEvent::from(input),
716 ))
717 }
718
719 fn error_status(&self, input: &TaskParserEvent, err: &Error) -> Option<EventEnvelope> {
720 Some(EventEnvelope::engine_error(
721 EventType::ParserTaskModel,
722 EventPhase::Failed,
723 ParserTaskModelEvent::from(input),
724 err,
725 ))
726 }
727
728 fn retry_status(
729 &self,
730 input: &TaskParserEvent,
731 retry_policy: &RetryPolicy,
732 ) -> Option<EventEnvelope> {
733 Some(EventEnvelope::engine(
734 EventType::ParserTaskModel,
735 EventPhase::Retry,
736 json!({
737 "data": ParserTaskModelEvent::from(input),
738 "retry_count": retry_policy.current_retry,
739 "reason": retry_policy.reason.clone().unwrap_or_default(),
740 }),
741 ))
742 }
743}
744#[async_trait]
745impl ProcessorTrait<TaskErrorEvent, Task> for TaskModelProcessor {
746 fn name(&self) -> &'static str {
747 "TaskModelProcessor"
748 }
749
750 async fn process(
751 &self,
752 input: TaskErrorEvent,
753 context: ProcessorContext,
754 ) -> ProcessorResult<Task> {
755 debug!(
756 "[TaskModelProcessor<ErrorTaskModel>] start: account={} platform={} crawler={:?} retry_count={}",
757 input.account_task.account,
758 input.account_task.platform,
759 input.account_task.module,
760 context
761 .retry_policy
762 .as_ref()
763 .map(|r| r.current_retry)
764 .unwrap_or(0)
765 );
766
767 let task_id = chain_key::task_runtime_id(
768 &input.account_task.platform,
769 &input.account_task.account,
770 input.run_id,
771 );
772 match self.error_task_decide(&input).await {
773 ChainDecision::Continue => {
774 counter!("mocra_error_task_threshold_decision_total", "decision" => "continue")
775 .increment(1);
776 debug!(
777 "[TaskModelProcessor<ErrorTaskModel>] task can continue: task_id={}",
778 task_id
779 );
780 }
781 ChainDecision::RetryAfter {
782 delay,
783 action_applied,
784 } => {
785 counter!("mocra_error_task_threshold_decision_total", "decision" => "retry_after")
786 .increment(1);
787 if !(ChainDecision::RetryAfter {
788 delay,
789 action_applied,
790 })
791 .action_applied()
792 {
793 self.persist_error_retry_schedule(&input, delay).await;
794 }
795 let mut retry_policy = context.retry_policy.unwrap_or_default();
796 retry_policy.retry_delay = std::cmp::max(delay.as_millis() as u64, 1);
797 retry_policy.reason = Some(format!(
798 "error task retry after {}ms",
799 retry_policy.retry_delay
800 ));
801 return ProcessorResult::RetryableFailure(retry_policy);
802 }
803 ChainDecision::TerminateModule {
804 reason,
805 action_applied,
806 } => {
807 counter!("mocra_error_task_threshold_decision_total", "decision" => "terminate_module").increment(1);
808 let module_id = input
809 .context
810 .module_id
811 .clone()
812 .or_else(|| {
813 input.account_task.module.as_ref().and_then(|m| {
814 m.first().map(|name| {
815 chain_key::module_runtime_id(
816 &input.account_task.account,
817 &input.account_task.platform,
818 name,
819 )
820 })
821 })
822 })
823 .unwrap_or_else(|| {
824 chain_key::module_runtime_id(
825 &input.account_task.account,
826 &input.account_task.platform,
827 "unknown",
828 )
829 });
830 if !(ChainDecision::TerminateModule {
831 reason: reason.clone(),
832 action_applied,
833 })
834 .action_applied()
835 {
836 self.persist_terminate_mark(&input, Some(&module_id), &reason)
837 .await;
838 }
839 self.emit_threshold_terminated_event(&input, "terminate_module", &reason)
840 .await;
841 warn!(
842 "[TaskModelProcessor<ErrorTaskModel>] module terminated by threshold: task_id={} reason={}",
843 task_id, reason
844 );
845 return ProcessorResult::FatalFailure(
846 ModuleError::ModuleMaxError(reason.into()).into(),
847 );
848 }
849 ChainDecision::TerminateTask {
850 reason,
851 action_applied,
852 } => {
853 counter!("mocra_error_task_threshold_decision_total", "decision" => "terminate_task").increment(1);
854 if !(ChainDecision::TerminateTask {
855 reason: reason.clone(),
856 action_applied,
857 })
858 .action_applied()
859 {
860 self.persist_terminate_mark(&input, None, &reason).await;
861 }
862 self.emit_threshold_terminated_event(&input, "terminate_task", &reason)
863 .await;
864 error!(
865 "[TaskModelProcessor<ErrorTaskModel>] task terminated (pre-check): task_id={} reason={}",
866 task_id, reason
867 );
868 if let Err(e) = self
869 .queue_manager
870 .send_to_dlq("error_task", &input, &reason)
871 .await
872 {
873 error!(
874 "[TaskModelProcessor<ErrorTaskModel>] failed to send to DLQ: {}",
875 e
876 );
877 }
878 return ProcessorResult::FatalFailure(
879 ModuleError::TaskMaxError(reason.into()).into(),
880 );
881 }
882 }
883
884 let task = self.task_manager.load_error(&input).await;
885 match task {
886 Ok(task) => ProcessorResult::Success(task),
887 Err(e) => {
888 warn!(
889 "[TaskModelProcessor<ErrorTaskModel>] load_error failed, will retry: account={} platform={} run_id={} error={e}",
890 input.account_task.account, input.account_task.platform, input.run_id
891 );
892 ProcessorResult::RetryableFailure(
893 context
894 .retry_policy
895 .unwrap_or(RetryPolicy::default().with_reason(e.to_string())),
896 )
897 }
898 }
899 }
900 async fn pre_process(
901 &self,
902 _input: &TaskErrorEvent,
903 _context: &ProcessorContext,
904 ) -> Result<()> {
905 Ok(())
906 }
907}
908#[async_trait]
909impl EventProcessorTrait<TaskErrorEvent, Task> for TaskModelProcessor {
910 fn pre_status(&self, input: &TaskErrorEvent) -> Option<EventEnvelope> {
911 Some(EventEnvelope::engine(
912 EventType::ParserTaskModel,
913 EventPhase::Started,
914 ParserTaskModelEvent::from(input),
915 ))
916 }
917
918 fn finish_status(&self, input: &TaskErrorEvent, output: &Task) -> Option<EventEnvelope> {
919 let mut evt: ParserTaskModelEvent = input.into();
920 evt.modules = Some(output.get_module_names());
921 Some(EventEnvelope::engine(
922 EventType::ParserTaskModel,
923 EventPhase::Completed,
924 evt,
925 ))
926 }
927
928 fn working_status(&self, input: &TaskErrorEvent) -> Option<EventEnvelope> {
929 Some(EventEnvelope::engine(
930 EventType::ParserTaskModel,
931 EventPhase::Started,
932 ParserTaskModelEvent::from(input),
933 ))
934 }
935
936 fn error_status(&self, input: &TaskErrorEvent, err: &Error) -> Option<EventEnvelope> {
937 Some(EventEnvelope::engine_error(
938 EventType::ParserTaskModel,
939 EventPhase::Failed,
940 ParserTaskModelEvent::from(input),
941 err,
942 ))
943 }
944
945 fn retry_status(
946 &self,
947 input: &TaskErrorEvent,
948 retry_policy: &RetryPolicy,
949 ) -> Option<EventEnvelope> {
950 Some(EventEnvelope::engine(
951 EventType::ParserTaskModel,
952 EventPhase::Retry,
953 json!({
954 "data": ParserTaskModelEvent::from(input),
955 "retry_count": retry_policy.current_retry,
956 "reason": retry_policy.reason.clone().unwrap_or_default(),
957 }),
958 ))
959 }
960}
961
962pub struct TaskModuleProcessor {
963 state: Arc<PipelineContext>,
964}
965#[async_trait]
966impl ProcessorTrait<Task, Vec<Module>> for TaskModuleProcessor {
967 fn name(&self) -> &'static str {
968 "TaskModuleProcessor"
969 }
970
971 async fn process(
972 &self,
973 input: Task,
974 _context: ProcessorContext,
975 ) -> ProcessorResult<Vec<Module>> {
976 debug!(
977 "[TaskModuleProcessor] start: task_id={} module_count={}",
978 input.id(),
979 input.modules.len()
980 );
981 let metadata = input.metadata.clone();
982 let login_info = input.login_info.clone();
983 let mut modules: Vec<Module> = Vec::new();
984 let default_locker_ttl = self.state.config.read().await.crawler.module_locker_ttl;
985 for mut module in input.modules {
986 match self
988 .state
989 .status_tracker
990 .should_module_continue(&module.id())
991 .await
992 {
993 Ok(ErrorDecision::Continue) => {
994 debug!(
995 "[TaskModuleProcessor] module can continue: module_id={}",
996 module.id()
997 );
998 }
999 Ok(ErrorDecision::Terminate(reason)) => {
1000 error!(
1002 "[TaskModuleProcessor] skip terminated module: module_id={} reason={}",
1003 module.id(),
1004 reason
1005 );
1006 continue;
1008 }
1009 Err(e) => {
1010 warn!(
1011 "[TaskModuleProcessor] error tracker check failed for module: module_id={} error={}, continue anyway",
1012 module.id(),
1013 e
1014 );
1015 }
1016 _ => {}
1017 }
1018
1019 module.locker_ttl = module
1020 .config
1021 .get_config_value("module_locker_ttl")
1022 .and_then(|v| v.as_u64())
1023 .unwrap_or(default_locker_ttl);
1024 module.bind_task_context(metadata.clone(), login_info.clone());
1025 modules.push(module);
1026 }
1027
1028 let mut filtered_modules: Vec<Module> = Vec::new();
1029 for x in modules.into_iter() {
1030 filtered_modules.push(x);
1031 }
1032 let modules = filtered_modules;
1033 ProcessorResult::Success(modules)
1034 }
1035}
1036
1037#[async_trait]
1038impl EventProcessorTrait<Task, Vec<Module>> for TaskModuleProcessor {
1039 fn pre_status(&self, _input: &Task) -> Option<EventEnvelope> {
1040 None
1041 }
1042
1043 fn finish_status(&self, _input: &Task, _output: &Vec<Module>) -> Option<EventEnvelope> {
1044 None
1045 }
1046
1047 fn working_status(&self, _input: &Task) -> Option<EventEnvelope> {
1048 None
1049 }
1050
1051 fn error_status(&self, _input: &Task, _err: &Error) -> Option<EventEnvelope> {
1052 None
1053 }
1054
1055 fn retry_status(&self, _input: &Task, _retry_policy: &RetryPolicy) -> Option<EventEnvelope> {
1056 None
1057 }
1058}
1059
1060pub struct TaskProcessor {
1061 task_manager: Arc<TaskManager>,
1062}
1063#[async_trait]
1064impl ProcessorTrait<Module, SyncBoxStream<'static, Request>> for TaskProcessor {
1065 fn name(&self) -> &'static str {
1066 "TaskProcessor"
1067 }
1068
1069 async fn process(
1070 &self,
1071 input: Module,
1072 context: ProcessorContext,
1073 ) -> ProcessorResult<SyncBoxStream<'static, Request>> {
1074 debug!(
1075 "[TaskProcessor] start generate: module_id={} module_name={}",
1076 input.id(),
1077 input.module.name()
1078 );
1079
1080 let (meta, login_info) = input.runtime_task_context();
1081
1082 let requests: SyncBoxStream<'static, Request> = match input.generate(meta, login_info).await
1085 {
1086 Ok(stream) => stream,
1087 Err(e) => {
1088 warn!("[TaskProcessor] generate error, will retry: {e}");
1089 return ProcessorResult::RetryableFailure(
1090 context
1091 .retry_policy
1092 .unwrap_or(RetryPolicy::default().with_reason(e.to_string())),
1093 );
1094 }
1095 };
1096 ProcessorResult::Success(requests)
1097 }
1098 async fn post_process(
1099 &self,
1100 _input: &Module,
1101 _output: &SyncBoxStream<'static, Request>,
1102 _context: &ProcessorContext,
1103 ) -> Result<()> {
1104 Ok(())
1106 }
1107}
1108#[async_trait]
1109impl EventProcessorTrait<Module, SyncBoxStream<'static, Request>> for TaskProcessor {
1110 fn pre_status(&self, input: &Module) -> Option<EventEnvelope> {
1111 Some(EventEnvelope::engine(
1112 EventType::ModuleGenerate,
1113 EventPhase::Started,
1114 ModuleGenerateEvent::from(input),
1115 ))
1116 }
1117
1118 fn finish_status(
1119 &self,
1120 input: &Module,
1121 _output: &SyncBoxStream<'static, Request>,
1122 ) -> Option<EventEnvelope> {
1123 Some(EventEnvelope::engine(
1124 EventType::ModuleGenerate,
1125 EventPhase::Completed,
1126 ModuleGenerateEvent::from(input),
1127 ))
1128 }
1129
1130 fn working_status(&self, input: &Module) -> Option<EventEnvelope> {
1131 Some(EventEnvelope::engine(
1132 EventType::ModuleGenerate,
1133 EventPhase::Started,
1134 ModuleGenerateEvent::from(input),
1135 ))
1136 }
1137
1138 fn error_status(&self, input: &Module, err: &Error) -> Option<EventEnvelope> {
1139 Some(EventEnvelope::engine_error(
1140 EventType::ModuleGenerate,
1141 EventPhase::Failed,
1142 ModuleGenerateEvent::from(input),
1143 err,
1144 ))
1145 }
1146
1147 fn retry_status(&self, input: &Module, retry_policy: &RetryPolicy) -> Option<EventEnvelope> {
1148 Some(EventEnvelope::engine(
1149 EventType::ModuleGenerate,
1150 EventPhase::Retry,
1151 json!({
1152 "data": ModuleGenerateEvent::from(input),
1153 "retry_count": retry_policy.current_retry,
1154 "reason": retry_policy.reason.clone().unwrap_or_default(),
1155 }),
1156 ))
1157 }
1158}
1159pub struct RequestPublish {
1160 queue_manager: Arc<QueueManager>,
1161 state: Arc<PipelineContext>,
1162}
1163
1164#[async_trait]
1165impl ProcessorTrait<Request, ()> for RequestPublish {
1166 fn name(&self) -> &'static str {
1167 "RequestPublish"
1168 }
1169
1170 async fn process(&self, input: Request, context: ProcessorContext) -> ProcessorResult<()> {
1171 let request_id = input.id;
1172 let module_id = input.module_id();
1173 let backpressure_retry_delay_ms = {
1174 let cfg = self.state.config.read().await;
1175 cfg.crawler.backpressure_retry_delay_ms
1176 };
1177
1178 info!(
1179 "[RequestPublish] publish request: request_id={} module_id={}",
1180 request_id, module_id
1181 );
1182
1183 let id = input.id.to_string();
1185
1186 let cache_service = self.state.cache_service.clone();
1188 let request_clone = input.clone();
1189
1190 tokio::spawn(async move {
1191 if let Err(e) = request_clone.send(&id, &cache_service).await {
1192 debug!("[RequestPublish] persist failed (background): {e}");
1194 }
1195 });
1196
1197 let tx = self.queue_manager.get_request_push_channel();
1198 match send_with_backpressure(&tx, QueuedItem::new(input)).await {
1199 Ok(BackpressureSendState::Direct) => {}
1200 Ok(BackpressureSendState::RecoveredFromFull) => {
1201 counter!("mocra_request_publish_backpressure_total", "reason" => "queue_full")
1202 .increment(1);
1203 warn!(
1204 "[RequestPublish] queue full, falling back to awaited send: request_id={} module_id={} remaining_capacity={}",
1205 request_id,
1206 module_id,
1207 tx.capacity()
1208 );
1209 }
1210 Err(err) => {
1211 if err.after_full {
1212 counter!("mocra_request_publish_backpressure_total", "reason" => "queue_full")
1213 .increment(1);
1214 warn!(
1215 "[RequestPublish] queue full before close: request_id={} module_id={} remaining_capacity={}",
1216 request_id,
1217 module_id,
1218 tx.capacity()
1219 );
1220 }
1221 counter!("mocra_request_publish_backpressure_total", "reason" => "queue_closed")
1222 .increment(1);
1223 let retry_reason = format!(
1224 "request queue closed: request_id={} module_id={}",
1225 err.item.inner.id,
1226 err.item.inner.module_id()
1227 );
1228 error!("[RequestPublish] {retry_reason}");
1229 let mut retry_policy = context.retry_policy.unwrap_or_default();
1230 if let Some(delay_ms) = backpressure_retry_delay_ms {
1231 retry_policy.retry_delay = delay_ms.max(1);
1232 }
1233 retry_policy.reason = Some(retry_reason);
1234 return ProcessorResult::RetryableFailure(retry_policy);
1235 }
1236 }
1237 ProcessorResult::Success(())
1238 }
1239 async fn handle_error(
1240 &self,
1241 input: &Request,
1242 error: Error,
1243 _context: &ProcessorContext,
1244 ) -> ProcessorResult<()> {
1245 self.state
1248 .status_tracker
1249 .release_module_locker(&input.module_runtime_id())
1250 .await;
1251 ProcessorResult::FatalFailure(error)
1252 }
1253}
1254#[async_trait]
1255impl EventProcessorTrait<Request, ()> for RequestPublish {
1256 fn pre_status(&self, input: &Request) -> Option<EventEnvelope> {
1257 Some(EventEnvelope::engine(
1258 EventType::RequestPublish,
1259 EventPhase::Started,
1260 RequestEvent::from(input),
1261 ))
1262 }
1263
1264 fn finish_status(&self, input: &Request, _output: &()) -> Option<EventEnvelope> {
1265 Some(EventEnvelope::engine(
1266 EventType::RequestPublish,
1267 EventPhase::Completed,
1268 RequestEvent::from(input),
1269 ))
1270 }
1271
1272 fn working_status(&self, input: &Request) -> Option<EventEnvelope> {
1273 Some(EventEnvelope::engine(
1274 EventType::RequestPublish,
1275 EventPhase::Started,
1276 RequestEvent::from(input),
1277 ))
1278 }
1279
1280 fn error_status(&self, input: &Request, err: &Error) -> Option<EventEnvelope> {
1281 Some(EventEnvelope::engine_error(
1282 EventType::RequestPublish,
1283 EventPhase::Failed,
1284 RequestEvent::from(input),
1285 err,
1286 ))
1287 }
1288
1289 fn retry_status(&self, input: &Request, retry_policy: &RetryPolicy) -> Option<EventEnvelope> {
1290 Some(EventEnvelope::engine(
1291 EventType::RequestPublish,
1292 EventPhase::Retry,
1293 json!({
1294 "data": RequestEvent::from(input),
1295 "retry_count": retry_policy.current_retry,
1296 "reason": retry_policy.reason.clone().unwrap_or_default(),
1297 }),
1298 ))
1299 }
1300}
1301pub struct ConfigProcessor {
1302 pub state: Arc<PipelineContext>,
1303}
1304#[async_trait]
1305impl ProcessorTrait<Request, (Request, Option<ModuleConfig>)> for ConfigProcessor {
1306 fn name(&self) -> &'static str {
1307 "ConfigProcessor"
1308 }
1309
1310 async fn process(
1311 &self,
1312 input: Request,
1313 context: ProcessorContext,
1314 ) -> ProcessorResult<(Request, Option<ModuleConfig>)> {
1315 match ModuleConfig::sync(&input.module_id(), &self.state.cache_service).await {
1317 Ok(Some(config)) => ProcessorResult::Success((input, Some(config))),
1318 Ok(None) => ProcessorResult::Success((input, None)),
1319 Err(e) => {
1320 error!(
1321 "Failed to fetch config for module {}: {}",
1322 input.task_id(),
1323 e
1324 );
1325 ProcessorResult::RetryableFailure(
1326 context
1327 .retry_policy
1328 .unwrap_or(RetryPolicy::default().with_reason(e.to_string())),
1329 )
1330 }
1331 }
1332 }
1333 async fn pre_process(&self, _input: &Request, _context: &ProcessorContext) -> Result<()> {
1334 self.state
1335 .status_tracker
1336 .lock_module(&_input.module_runtime_id())
1337 .await;
1338 Ok(())
1339 }
1340 async fn handle_error(
1341 &self,
1342 _input: &Request,
1343 _error: Error,
1344 _context: &ProcessorContext,
1345 ) -> ProcessorResult<(Request, Option<ModuleConfig>)> {
1346 ProcessorResult::Success((_input.clone(), None))
1347 }
1348}
1349#[async_trait]
1350impl EventProcessorTrait<Request, (Request, Option<ModuleConfig>)> for ConfigProcessor {
1351 fn pre_status(&self, _input: &Request) -> Option<EventEnvelope> {
1352 None
1353 }
1354
1355 fn finish_status(
1356 &self,
1357 _input: &Request,
1358 _out: &(Request, Option<ModuleConfig>),
1359 ) -> Option<EventEnvelope> {
1360 None
1361 }
1362
1363 fn working_status(&self, _input: &Request) -> Option<EventEnvelope> {
1364 None
1365 }
1366
1367 fn error_status(&self, _input: &Request, _err: &Error) -> Option<EventEnvelope> {
1368 None
1369 }
1370
1371 fn retry_status(&self, _input: &Request, _retry_policy: &RetryPolicy) -> Option<EventEnvelope> {
1372 None
1373 }
1374}
1375
1376use crate::cacheable::CacheAble;
1377use crate::engine::task::module::Module;
1378use crate::engine::task::{Task, TaskManager};
1379use futures::stream::Stream;
1380use std::pin::Pin;
1381
1382pub type SyncBoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + Send + Sync + 'a>>;
1383
1384pub struct VecToStreamProcessor<T> {
1386 _marker: PhantomData<T>,
1387}
1388
1389impl<T> Default for VecToStreamProcessor<T> {
1390 fn default() -> Self {
1391 Self::new()
1392 }
1393}
1394
1395impl<T> VecToStreamProcessor<T> {
1396 pub fn new() -> Self {
1397 Self {
1398 _marker: PhantomData,
1399 }
1400 }
1401}
1402
1403#[async_trait]
1404impl<T: Send + Sync + 'static> ProcessorTrait<Vec<T>, SyncBoxStream<'static, T>>
1405 for VecToStreamProcessor<T>
1406{
1407 fn name(&self) -> &'static str {
1408 "VecToStreamProcessor"
1409 }
1410
1411 async fn process(
1412 &self,
1413 input: Vec<T>,
1414 _context: ProcessorContext,
1415 ) -> ProcessorResult<SyncBoxStream<'static, T>> {
1416 let stream = futures::stream::iter(input);
1417 let boxed: SyncBoxStream<'static, T> = Box::pin(stream);
1418 ProcessorResult::Success(boxed)
1419 }
1420}
1421
1422#[async_trait]
1423impl<T: Send + Sync + 'static> EventProcessorTrait<Vec<T>, SyncBoxStream<'static, T>>
1424 for VecToStreamProcessor<T>
1425{
1426 fn pre_status(&self, _input: &Vec<T>) -> Option<EventEnvelope> {
1427 None
1428 }
1429 fn finish_status(
1430 &self,
1431 _input: &Vec<T>,
1432 _output: &SyncBoxStream<'static, T>,
1433 ) -> Option<EventEnvelope> {
1434 None
1435 }
1436 fn working_status(&self, _input: &Vec<T>) -> Option<EventEnvelope> {
1437 None
1438 }
1439 fn error_status(&self, _input: &Vec<T>, _err: &Error) -> Option<EventEnvelope> {
1440 None
1441 }
1442 fn retry_status(&self, _input: &Vec<T>, _retry_policy: &RetryPolicy) -> Option<EventEnvelope> {
1443 None
1444 }
1445}
1446
1447pub struct FlattenStreamVecProcessor<T> {
1448 _marker: PhantomData<T>,
1449 #[allow(clippy::type_complexity)]
1450 logger: Option<Arc<dyn Fn(&T) + Send + Sync>>,
1451}
1452
1453impl<T> Default for FlattenStreamVecProcessor<T> {
1454 fn default() -> Self {
1455 Self::new()
1456 }
1457}
1458
1459impl<T> FlattenStreamVecProcessor<T> {
1460 pub fn new() -> Self {
1461 Self {
1462 _marker: PhantomData,
1463 logger: None,
1464 }
1465 }
1466 pub fn with_logger<F>(mut self, logger: F) -> Self
1467 where
1468 F: Fn(&T) + Send + Sync + 'static,
1469 {
1470 self.logger = Some(Arc::new(logger));
1471 self
1472 }
1473}
1474
1475#[async_trait]
1476impl<T: Send + Sync + 'static>
1477 ProcessorTrait<SyncBoxStream<'static, Vec<T>>, SyncBoxStream<'static, T>>
1478 for FlattenStreamVecProcessor<T>
1479{
1480 fn name(&self) -> &'static str {
1481 "FlattenStreamVecProcessor"
1482 }
1483
1484 async fn process(
1485 &self,
1486 input: SyncBoxStream<'static, Vec<T>>,
1487 _context: ProcessorContext,
1488 ) -> ProcessorResult<SyncBoxStream<'static, T>> {
1489 let logger = self.logger.clone();
1490 let stream = input.flat_map(move |v| {
1491 let logger = logger.clone();
1492 let iter = v.into_iter().inspect(move |item| {
1493 if let Some(l) = &logger {
1494 l(item);
1495 }
1496 });
1497 futures::stream::iter(iter)
1498 });
1499 let boxed: SyncBoxStream<'static, T> = Box::pin(stream);
1500 ProcessorResult::Success(boxed)
1501 }
1502}
1503
1504#[async_trait]
1505impl<T: Send + Sync + 'static>
1506 EventProcessorTrait<SyncBoxStream<'static, Vec<T>>, SyncBoxStream<'static, T>>
1507 for FlattenStreamVecProcessor<T>
1508{
1509 fn pre_status(&self, _input: &SyncBoxStream<'static, Vec<T>>) -> Option<EventEnvelope> {
1510 None
1511 }
1512 fn finish_status(
1513 &self,
1514 _input: &SyncBoxStream<'static, Vec<T>>,
1515 _output: &SyncBoxStream<'static, T>,
1516 ) -> Option<EventEnvelope> {
1517 None
1518 }
1519 fn working_status(&self, _input: &SyncBoxStream<'static, Vec<T>>) -> Option<EventEnvelope> {
1520 None
1521 }
1522 fn error_status(
1523 &self,
1524 _input: &SyncBoxStream<'static, Vec<T>>,
1525 _err: &Error,
1526 ) -> Option<EventEnvelope> {
1527 None
1528 }
1529 fn retry_status(
1530 &self,
1531 _input: &SyncBoxStream<'static, Vec<T>>,
1532 _retry_policy: &RetryPolicy,
1533 ) -> Option<EventEnvelope> {
1534 None
1535 }
1536}
1537
1538pub struct StreamLoggerProcessor<T> {
1539 _marker: PhantomData<T>,
1540 name: String,
1541 #[allow(clippy::type_complexity)]
1542 logger: Option<Arc<dyn Fn(&T) + Send + Sync>>,
1543}
1544
1545impl<T> StreamLoggerProcessor<T> {
1546 pub fn new(name: &str) -> Self {
1547 Self {
1548 _marker: PhantomData,
1549 name: name.to_string(),
1550 logger: None,
1551 }
1552 }
1553 pub fn with_logger<F>(mut self, logger: F) -> Self
1554 where
1555 F: Fn(&T) + Send + Sync + 'static,
1556 {
1557 self.logger = Some(Arc::new(logger));
1558 self
1559 }
1560}
1561
1562#[async_trait]
1563impl<T: Send + Sync + 'static> ProcessorTrait<SyncBoxStream<'static, T>, SyncBoxStream<'static, T>>
1564 for StreamLoggerProcessor<T>
1565{
1566 fn name(&self) -> &'static str {
1567 "StreamLoggerProcessor"
1568 }
1569
1570 async fn process(
1571 &self,
1572 input: SyncBoxStream<'static, T>,
1573 _context: ProcessorContext,
1574 ) -> ProcessorResult<SyncBoxStream<'static, T>> {
1575 let logger = self.logger.clone();
1576 let stream = input.map(move |item| {
1577 if let Some(l) = &logger {
1578 l(&item);
1579 }
1580 item
1581 });
1582 let boxed: SyncBoxStream<'static, T> = Box::pin(stream);
1583 ProcessorResult::Success(boxed)
1584 }
1585}
1586
1587#[async_trait]
1588impl<T: Send + Sync + 'static>
1589 EventProcessorTrait<SyncBoxStream<'static, T>, SyncBoxStream<'static, T>>
1590 for StreamLoggerProcessor<T>
1591{
1592 fn pre_status(&self, _input: &SyncBoxStream<'static, T>) -> Option<EventEnvelope> {
1593 None
1594 }
1595 fn finish_status(
1596 &self,
1597 _input: &SyncBoxStream<'static, T>,
1598 _output: &SyncBoxStream<'static, T>,
1599 ) -> Option<EventEnvelope> {
1600 None
1601 }
1602 fn working_status(&self, _input: &SyncBoxStream<'static, T>) -> Option<EventEnvelope> {
1603 None
1604 }
1605 fn error_status(
1606 &self,
1607 _input: &SyncBoxStream<'static, T>,
1608 _err: &Error,
1609 ) -> Option<EventEnvelope> {
1610 None
1611 }
1612 fn retry_status(
1613 &self,
1614 _input: &SyncBoxStream<'static, T>,
1615 _retry_policy: &RetryPolicy,
1616 ) -> Option<EventEnvelope> {
1617 None
1618 }
1619}
1620
1621pub struct FlattenStreamProcessor<T> {
1622 _marker: PhantomData<T>,
1623}
1624
1625impl<T> Default for FlattenStreamProcessor<T> {
1626 fn default() -> Self {
1627 Self::new()
1628 }
1629}
1630
1631impl<T> FlattenStreamProcessor<T> {
1632 pub fn new() -> Self {
1633 Self {
1634 _marker: PhantomData,
1635 }
1636 }
1637}
1638
1639#[async_trait]
1640impl<T: Send + Sync + 'static>
1641 ProcessorTrait<SyncBoxStream<'static, SyncBoxStream<'static, T>>, SyncBoxStream<'static, T>>
1642 for FlattenStreamProcessor<T>
1643{
1644 fn name(&self) -> &'static str {
1645 "FlattenStreamProcessor"
1646 }
1647
1648 async fn process(
1649 &self,
1650 input: SyncBoxStream<'static, SyncBoxStream<'static, T>>,
1651 _context: ProcessorContext,
1652 ) -> ProcessorResult<SyncBoxStream<'static, T>> {
1653 let stream = input.flatten();
1654 let boxed: SyncBoxStream<'static, T> = Box::pin(stream);
1655 ProcessorResult::Success(boxed)
1656 }
1657}
1658
1659#[async_trait]
1660impl<T: Send + Sync + 'static>
1661 EventProcessorTrait<
1662 SyncBoxStream<'static, SyncBoxStream<'static, T>>,
1663 SyncBoxStream<'static, T>,
1664 > for FlattenStreamProcessor<T>
1665{
1666 fn pre_status(
1667 &self,
1668 _input: &SyncBoxStream<'static, SyncBoxStream<'static, T>>,
1669 ) -> Option<EventEnvelope> {
1670 None
1671 }
1672 fn finish_status(
1673 &self,
1674 _input: &SyncBoxStream<'static, SyncBoxStream<'static, T>>,
1675 _output: &SyncBoxStream<'static, T>,
1676 ) -> Option<EventEnvelope> {
1677 None
1678 }
1679 fn working_status(
1680 &self,
1681 _input: &SyncBoxStream<'static, SyncBoxStream<'static, T>>,
1682 ) -> Option<EventEnvelope> {
1683 None
1684 }
1685 fn error_status(
1686 &self,
1687 _input: &SyncBoxStream<'static, SyncBoxStream<'static, T>>,
1688 _err: &Error,
1689 ) -> Option<EventEnvelope> {
1690 None
1691 }
1692 fn retry_status(
1693 &self,
1694 _input: &SyncBoxStream<'static, SyncBoxStream<'static, T>>,
1695 _retry_policy: &RetryPolicy,
1696 ) -> Option<EventEnvelope> {
1697 None
1698 }
1699}
1700
1701pub struct UnifiedTaskIngressChain {
1702 task_chain: Arc<EventAwareTypedChain<TaskEvent, SyncBoxStream<'static, ()>>>,
1704 parser_chain: Arc<EventAwareTypedChain<TaskParserEvent, SyncBoxStream<'static, ()>>>,
1706 error_chain: Arc<EventAwareTypedChain<TaskErrorEvent, SyncBoxStream<'static, ()>>>,
1708}
1709
1710impl UnifiedTaskIngressChain {
1711 pub fn new(
1713 task_chain: Arc<EventAwareTypedChain<TaskEvent, SyncBoxStream<'static, ()>>>,
1714 parser_chain: Arc<EventAwareTypedChain<TaskParserEvent, SyncBoxStream<'static, ()>>>,
1715 error_chain: Arc<EventAwareTypedChain<TaskErrorEvent, SyncBoxStream<'static, ()>>>,
1716 ) -> Self {
1717 Self {
1718 task_chain,
1719 parser_chain,
1720 error_chain,
1721 }
1722 }
1723
1724 pub async fn execute(
1725 &self,
1726 input: UnifiedTaskInput,
1727 context: ProcessorContext,
1728 ) -> ProcessorResult<SyncBoxStream<'static, ()>> {
1729 match input {
1730 UnifiedTaskInput::Task(task) => self.task_chain.execute(task, context).await,
1731 UnifiedTaskInput::ParserTask(task) => self.parser_chain.execute(task, context).await,
1732 UnifiedTaskInput::ErrorTask(task) => self.error_chain.execute(task, context).await,
1733 }
1734 }
1735}
1736
1737pub async fn create_unified_task_ingress_chain(
1744 task_manager: Arc<TaskManager>,
1745 queue_manager: Arc<QueueManager>,
1746 event_bus: Option<Arc<EventBus>>,
1747 state: Arc<PipelineContext>,
1748) -> UnifiedTaskIngressChain {
1749 let task_concurrency = state
1750 .config
1751 .read()
1752 .await
1753 .crawler
1754 .task_concurrency
1755 .unwrap_or(1024);
1756 let parser_concurrency = {
1757 let cfg = state.config.read().await;
1758 cfg.crawler
1759 .parser_concurrency
1760 .or(cfg.crawler.task_concurrency)
1761 .unwrap_or(256)
1762 };
1763 let error_task_concurrency = {
1764 let cfg = state.config.read().await;
1765 cfg.crawler
1766 .error_task_concurrency
1767 .or(cfg.crawler.parser_concurrency)
1768 .or(cfg.crawler.task_concurrency)
1769 .unwrap_or(4)
1770 };
1771 let task_publish_concurrency = state
1772 .config
1773 .read()
1774 .await
1775 .crawler
1776 .publish_concurrency
1777 .unwrap_or(1024);
1778 let parser_publish_concurrency = state
1779 .config
1780 .read()
1781 .await
1782 .crawler
1783 .publish_concurrency
1784 .unwrap_or(256);
1785 let error_publish_concurrency = state
1786 .config
1787 .read()
1788 .await
1789 .crawler
1790 .publish_concurrency
1791 .unwrap_or(32);
1792
1793 let task_chain = Arc::new(
1794 EventAwareTypedChain::<TaskEvent, TaskEvent>::new(event_bus.clone())
1795 .then::<Task, _>(TaskModelProcessor {
1796 task_manager: task_manager.clone(),
1797 state: state.clone(),
1798 queue_manager: queue_manager.clone(),
1799 event_bus: event_bus.clone(),
1800 threshold_decision_service: Arc::new(StatusTrackerThresholdDecisionService::new(
1801 state.clone(),
1802 )),
1803 })
1804 .then::<Vec<Module>, _>(TaskModuleProcessor {
1805 state: state.clone(),
1806 })
1807 .then(VecToStreamProcessor::new())
1808 .then_map_stream_in_with_strategy::<SyncBoxStream<'static, Request>, _>(
1809 TaskProcessor {
1810 task_manager: task_manager.clone(),
1811 },
1812 task_concurrency,
1813 ErrorStrategy::Skip,
1814 )
1815 .then_one_shot(FlattenStreamProcessor::new())
1816 .then_one_shot(StreamLoggerProcessor::new("AfterFlatten").with_logger(
1817 |req: &Request| {
1818 info!(
1819 "[FlattenStreamProcessor] yielding request: request_id={}",
1820 req.id
1821 );
1822 },
1823 ))
1824 .then_map_stream_in_with_strategy::<(), _>(
1825 RequestPublish {
1826 queue_manager: queue_manager.clone(),
1827 state: state.clone(),
1828 },
1829 task_publish_concurrency,
1830 ErrorStrategy::Skip,
1831 ),
1832 );
1833
1834 let parser_chain = Arc::new(
1835 EventAwareTypedChain::<TaskParserEvent, TaskParserEvent>::new(event_bus.clone())
1836 .then::<Task, _>(TaskModelProcessor {
1837 task_manager: task_manager.clone(),
1838 state: state.clone(),
1839 queue_manager: queue_manager.clone(),
1840 event_bus: event_bus.clone(),
1841 threshold_decision_service: Arc::new(StatusTrackerThresholdDecisionService::new(
1842 state.clone(),
1843 )),
1844 })
1845 .then::<Vec<Module>, _>(TaskModuleProcessor {
1846 state: state.clone(),
1847 })
1848 .then(VecToStreamProcessor::new())
1849 .then_map_stream_in_with_strategy::<SyncBoxStream<'static, Request>, _>(
1850 TaskProcessor {
1851 task_manager: task_manager.clone(),
1852 },
1853 parser_concurrency,
1854 ErrorStrategy::Skip,
1855 )
1856 .then_one_shot(FlattenStreamProcessor::new())
1857 .then_map_stream_in_with_strategy::<(), _>(
1858 RequestPublish {
1859 queue_manager: queue_manager.clone(),
1860 state: state.clone(),
1861 },
1862 parser_publish_concurrency,
1863 ErrorStrategy::Skip,
1864 ),
1865 );
1866
1867 let error_chain = Arc::new(
1868 EventAwareTypedChain::<TaskErrorEvent, TaskErrorEvent>::new(event_bus)
1869 .then::<Task, _>(TaskModelProcessor {
1870 task_manager: task_manager.clone(),
1871 state: state.clone(),
1872 queue_manager: queue_manager.clone(),
1873 event_bus: None,
1874 threshold_decision_service: Arc::new(StatusTrackerThresholdDecisionService::new(
1875 state.clone(),
1876 )),
1877 })
1878 .then::<Vec<Module>, _>(TaskModuleProcessor {
1879 state: state.clone(),
1880 })
1881 .then(VecToStreamProcessor::new())
1882 .then_map_stream_in_with_strategy::<SyncBoxStream<'static, Request>, _>(
1883 TaskProcessor {
1884 task_manager: task_manager.clone(),
1885 },
1886 error_task_concurrency,
1887 ErrorStrategy::Skip,
1888 )
1889 .then_one_shot(FlattenStreamProcessor::new())
1890 .then_map_stream_in_with_strategy::<(), _>(
1891 RequestPublish {
1892 queue_manager,
1893 state,
1894 },
1895 error_publish_concurrency,
1896 ErrorStrategy::Skip,
1897 ),
1898 );
1899
1900 UnifiedTaskIngressChain::new(task_chain, parser_chain, error_chain)
1901}