Skip to main content

mocra_core/engine/chain/
parser_chain.rs

1use crate::common::context::PipelineContext;
2use crate::common::interface::middleware_manager::MiddlewareManager;
3use crate::common::model::data::DataEvent;
4use crate::common::model::message::{TaskErrorEvent, TaskEvent};
5use crate::common::model::{ModuleConfig, Response};
6use crate::common::status_tracker::ErrorDecision;
7use crate::engine::events::{
8    DataMiddlewareEvent, DataStoreEvent, EventBus, EventEnvelope, EventPhase, EventType,
9    ModuleGenerateEvent, ParserEvent,
10};
11use crate::engine::processors::event_processor::{EventAwareTypedChain, EventProcessorTrait};
12use crate::engine::task::TaskManager;
13use crate::errors::{DataMiddlewareError, Error, Result};
14use async_trait::async_trait;
15
16use crate::cacheable::{CacheAble, CacheService};
17use crate::common::model::login_info::LoginInfo;
18use crate::common::processors::processor::{
19    ProcessorContext, ProcessorResult, ProcessorTrait, RetryPolicy,
20};
21use crate::common::processors::processor_chain::ErrorStrategy;
22use crate::engine::chain::backpressure::{BackpressureSendState, send_with_backpressure};
23use crate::engine::task::module::Module;
24use crate::queue::{QueueManager, QueuedItem};
25use dashmap::DashMap;
26use futures::StreamExt;
27use log::{debug, error, info, warn};
28use metrics::counter;
29use serde_json::json;
30use std::sync::Arc;
31use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
32
33/// Resolves response to module/config/login context before parsing.
34pub struct ResponseModuleProcessor {
35    #[allow(dead_code)]
36    task_manager: Arc<TaskManager>,
37    cache_service: Arc<CacheService>,
38    state: Arc<PipelineContext>,
39    config_cache: Arc<DashMap<String, (Arc<ModuleConfig>, Instant)>>,
40}
41#[async_trait]
42impl ProcessorTrait<Response, (Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>)>
43    for ResponseModuleProcessor
44{
45    fn name(&self) -> &'static str {
46        "ResponseModuleProcessor"
47    }
48
49    async fn process(
50        &self,
51        input: Response,
52        context: ProcessorContext,
53    ) -> ProcessorResult<(Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>)> {
54        // [LOG_OPTIMIZATION] Removed start log to reduce I/O blocking latency
55        // info!(
56        //     "[ResponseModuleProcessor] start: request_id={} module_id={}",
57        //     input.id,
58        //     input.module_id()
59        // );
60
61        // Guard checks before parser execution.
62        // 1) Task-level threshold.
63        match self
64            .state
65            .status_tracker
66            .should_task_continue(&input.task_runtime_id())
67            .await
68        {
69            Ok(ErrorDecision::Continue) => {
70                // [LOG_OPTIMIZATION] debug!("[ResponseModuleProcessor] task check passed: task_id={}", input.task_runtime_id());
71            }
72            Ok(ErrorDecision::Terminate(reason)) => {
73                error!(
74                    "[ResponseModuleProcessor] task terminated before parsing: task_id={} reason={}",
75                    input.task_runtime_id(),
76                    reason
77                );
78                // Release lock on task termination path.
79                self.state
80                    .status_tracker
81                    .release_module_locker(&input.module_runtime_id())
82                    .await;
83                return ProcessorResult::FatalFailure(
84                    crate::errors::ModuleError::TaskMaxError(reason.into()).into(),
85                );
86            }
87            Err(e) => {
88                warn!(
89                    "[ResponseModuleProcessor] task error check failed, continue anyway: task_id={} error={}",
90                    input.task_runtime_id(),
91                    e
92                );
93            }
94            _ => {}
95        }
96
97        // 2) Module-level threshold.
98        match self
99            .state
100            .status_tracker
101            .should_module_continue(&input.module_runtime_id())
102            .await
103        {
104            Ok(ErrorDecision::Continue) => {
105                // [LOG_OPTIMIZATION] debug!("[ResponseModuleProcessor] module check passed: module_id={}", input.module_runtime_id());
106            }
107            Ok(ErrorDecision::Terminate(reason)) => {
108                error!(
109                    "[ResponseModuleProcessor] module terminated before parsing: module_id={} reason={}",
110                    input.module_runtime_id(),
111                    reason
112                );
113                // Release lock on module termination path.
114                self.state
115                    .status_tracker
116                    .release_module_locker(&input.module_runtime_id())
117                    .await;
118                return ProcessorResult::FatalFailure(
119                    crate::errors::ModuleError::ModuleMaxError(reason.into()).into(),
120                );
121            }
122            Err(e) => {
123                warn!(
124                    "[ResponseModuleProcessor] module error check failed, continue anyway: module_id={} error={}",
125                    input.module_runtime_id(),
126                    e
127                );
128            }
129            _ => {}
130        }
131
132        let task: Result<(Arc<Module>, Option<LoginInfo>)> =
133            self.task_manager.load_module_with_response(&input).await;
134        match task {
135            Ok((module, login_info)) => {
136                // Prefer local short-lived config cache.
137                let module_id = module.id();
138                let now = Instant::now();
139                let mut should_prune_cache = false;
140                let cached_config = if let Some(entry) = self.config_cache.get(&module_id) {
141                    let (cfg, expires_at) = entry.value();
142                    if now < *expires_at {
143                        Some(cfg.clone())
144                    } else {
145                        should_prune_cache = true;
146                        None
147                    }
148                } else {
149                    None
150                };
151
152                if should_prune_cache {
153                    self.config_cache.remove(&module_id);
154                }
155
156                let config = if let Some(c) = cached_config {
157                    c
158                } else {
159                    match ModuleConfig::sync(&module_id, &self.cache_service).await {
160                        Ok(Some(config)) => {
161                            let config = Arc::new(config);
162                            self.config_cache.insert(
163                                module_id,
164                                (config.clone(), Instant::now() + Duration::from_secs(10)),
165                            );
166                            config
167                        }
168                        _ => module.config.clone(),
169                    }
170                };
171
172                // info!(
173                //     "[ResponseModuleProcessor] module loaded: module_name={} module_id={}",
174                //     module.module.name(),
175                //     module.id()
176                // );
177                ProcessorResult::Success((input, module, config, login_info))
178            }
179            Err(e) => {
180                warn!(
181                    "[ResponseModuleProcessor] load_with_response failed, will retry: account={} platform={} request_id={} err={e}",
182                    input.account, input.platform, input.id
183                );
184                ProcessorResult::RetryableFailure(
185                    context
186                        .retry_policy
187                        .unwrap_or(RetryPolicy::default().with_reason(e.to_string())),
188                )
189            }
190        }
191    }
192    async fn pre_process(&self, input: &Response, _context: &ProcessorContext) -> Result<()> {
193        if self.state.config.read().await.download_config.enable_locker {
194            debug!(
195                "[ResponseModuleProcessor] lock module before parsing: module_id={}",
196                input.module_runtime_id()
197            );
198            self.state
199                .status_tracker
200                .lock_module(&input.module_runtime_id())
201                .await;
202        }
203        Ok(())
204    }
205}
206#[async_trait]
207impl EventProcessorTrait<Response, (Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>)>
208    for ResponseModuleProcessor
209{
210    fn pre_status(&self, input: &Response) -> Option<EventEnvelope> {
211        Some(EventEnvelope::engine(
212            EventType::ModuleGenerate,
213            EventPhase::Started,
214            ModuleGenerateEvent::from(input),
215        ))
216    }
217
218    fn finish_status(
219        &self,
220        input: &Response,
221        _output: &(Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>),
222    ) -> Option<EventEnvelope> {
223        Some(EventEnvelope::engine(
224            EventType::ModuleGenerate,
225            EventPhase::Completed,
226            ModuleGenerateEvent::from(input),
227        ))
228    }
229
230    fn working_status(&self, input: &Response) -> Option<EventEnvelope> {
231        Some(EventEnvelope::engine(
232            EventType::ModuleGenerate,
233            EventPhase::Started,
234            ModuleGenerateEvent::from(input),
235        ))
236    }
237
238    fn error_status(&self, input: &Response, err: &Error) -> Option<EventEnvelope> {
239        Some(EventEnvelope::engine_error(
240            EventType::ModuleGenerate,
241            EventPhase::Failed,
242            ModuleGenerateEvent::from(input),
243            err,
244        ))
245    }
246
247    fn retry_status(&self, input: &Response, retry_policy: &RetryPolicy) -> Option<EventEnvelope> {
248        Some(EventEnvelope::engine(
249            EventType::ModuleGenerate,
250            EventPhase::Retry,
251            json!({
252                "data": ModuleGenerateEvent::from(input),
253                "retry_count": retry_policy.current_retry,
254                "reason": retry_policy.reason.clone().unwrap_or_default(),
255            }),
256        ))
257    }
258}
259
260pub struct ResponseParserProcessor {
261    #[allow(dead_code)]
262    task_manager: Arc<TaskManager>,
263    queue_manager: Arc<QueueManager>,
264    state: Arc<PipelineContext>,
265    cache_service: Arc<CacheService>,
266    event_bus: Option<Arc<EventBus>>,
267}
268
269impl ResponseParserProcessor {
270    async fn emit_semantic_event(&self, event_type: EventType, payload: serde_json::Value) {
271        if let Some(event_bus) = &self.event_bus {
272            let _ = event_bus
273                .publish(EventEnvelope::engine(
274                    event_type,
275                    EventPhase::Completed,
276                    payload,
277                ))
278                .await;
279        }
280    }
281
282    async fn send_with_backpressure<T>(
283        &self,
284        tx: &tokio::sync::mpsc::Sender<QueuedItem<T>>,
285        item: QueuedItem<T>,
286        queue_kind: &'static str,
287        log_context: &str,
288    ) -> bool {
289        match send_with_backpressure(tx, item).await {
290            Ok(BackpressureSendState::Direct) => true,
291            Ok(BackpressureSendState::RecoveredFromFull) => {
292                counter!("mocra_parser_chain_backpressure_total", "queue" => queue_kind, "reason" => "queue_full").increment(1);
293                warn!(
294                    "[ResponseParserProcessor] queue full, fallback to awaited send: queue={} context={} remaining_capacity={}",
295                    queue_kind,
296                    log_context,
297                    tx.capacity()
298                );
299                true
300            }
301            Err(err) => {
302                if err.after_full {
303                    counter!("mocra_parser_chain_backpressure_total", "queue" => queue_kind, "reason" => "queue_full").increment(1);
304                    warn!(
305                        "[ResponseParserProcessor] queue full before close: queue={} context={} remaining_capacity={}",
306                        queue_kind,
307                        log_context,
308                        tx.capacity()
309                    );
310                }
311                counter!("mocra_parser_chain_backpressure_total", "queue" => queue_kind, "reason" => "queue_closed").increment(1);
312                error!(
313                    "[ResponseParserProcessor] queue closed before send: queue={} context={}",
314                    queue_kind, log_context
315                );
316                false
317            }
318        }
319    }
320}
321
322#[async_trait]
323impl ProcessorTrait<(Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>), Vec<DataEvent>>
324    for ResponseParserProcessor
325{
326    fn name(&self) -> &'static str {
327        "ResponseParserProcessor"
328    }
329
330    async fn process(
331        &self,
332        input: (Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>),
333        context: ProcessorContext,
334    ) -> ProcessorResult<Vec<DataEvent>> {
335        info!(
336            "[ResponseParserProcessor] start parse: account={} platform={} module={} request_id={} module_id={}",
337            input.0.account,
338            input.0.platform,
339            input.0.module,
340            input.0.id,
341            input.0.module_id()
342        );
343        let module = input.1.clone();
344        let config = input.2.clone();
345        let login_info = input.3.clone();
346
347        // Propagate module config through processor context for downstream data stages.
348        // DataMiddlewareProcessor/DataStoreProcessor read `context.metadata["config"]`.
349        match serde_json::to_value(config.as_ref()) {
350            Ok(cfg_val) => {
351                context
352                    .metadata
353                    .write()
354                    .await
355                    .insert("config".to_string(), cfg_val);
356            }
357            Err(e) => {
358                warn!(
359                    "[ResponseParserProcessor] failed to serialize module config into context: request_id={} module_id={} error={}",
360                    input.0.id,
361                    input.0.module_id(),
362                    e
363                );
364            }
365        }
366
367        // StateHandle has been removed; SyncService is used internally by ModuleProcessor.
368        let task_id = input.0.task_runtime_id();
369        let module_id = input.0.module_runtime_id();
370        let request_id = input.0.id.to_string();
371        let account = input.0.account.clone();
372        let platform = input.0.platform.clone();
373        let module_name = input.0.module.clone();
374
375        let data = module.parser(input.0.clone(), Some(config)).await;
376        let mut data = match data {
377            Ok(d) => {
378                let has_next_task = !d.parser_task.is_empty();
379                let has_error = d.error_task.is_some();
380                info!(
381                    "[ResponseParserProcessor] parser returned: request_id={} data_len={} has_next_task={} has_error={}",
382                    request_id,
383                    d.data.len(),
384                    has_next_task,
385                    has_error
386                );
387
388                // Record parser success metrics/state.
389                self.state
390                    .status_tracker
391                    .record_parse_success(&request_id)
392                    .await
393                    .ok();
394
395                d
396            }
397            Err(e) => {
398                warn!(
399                    "[ResponseParserProcessor] parser error: account={} platform={} module={} request_id={} error={e}",
400                    account, platform, module_name, request_id
401                );
402
403                // Record parser error and retrieve threshold decision.
404                match self
405                    .state
406                    .status_tracker
407                    .record_parse_error(&task_id, &module_id, &request_id, &e)
408                    .await
409                {
410                    Ok(ErrorDecision::Continue) | Ok(ErrorDecision::RetryAfter(_)) => {
411                        debug!(
412                            "[ResponseParserProcessor] will retry parsing: request_id={}",
413                            request_id
414                        );
415                        return ProcessorResult::RetryableFailure(
416                            context
417                                .retry_policy
418                                .unwrap_or(RetryPolicy::default().with_reason(e.to_string())),
419                        );
420                    }
421                    Ok(ErrorDecision::Skip) => {
422                        warn!(
423                            "[ResponseParserProcessor] skip parse after max retries: request_id={}",
424                            request_id
425                        );
426                        // Skip this parse attempt and return empty output.
427                        return ProcessorResult::Success(vec![]);
428                    }
429                    Ok(ErrorDecision::Terminate(reason)) => {
430                        error!("[ResponseParserProcessor] terminate: {}", reason);
431                        return ProcessorResult::FatalFailure(e);
432                    }
433                    Err(err) => {
434                        error!("[ResponseParserProcessor] error tracker failed: {}", err);
435                        return ProcessorResult::FatalFailure(e);
436                    }
437                }
438            }
439        };
440
441        let parser_task_queue = self.queue_manager.get_parser_task_push_channel();
442        for task in data.parser_task.drain(..) {
443            let task_account = task.account_task.account.clone();
444            let task_platform = task.account_task.platform.clone();
445            let task_run_id = task.run_id;
446            let task_module_id = task.context.module_id.clone();
447            let task_step_idx = task.context.step_idx;
448            let task_prefix_request = task.prefix_request;
449            // OPTIMIZATION: Check if we can bypass the queue and generate requests locally
450            let target_module_name = task.account_task.module.as_ref().and_then(|v| v.first());
451            let is_same_module =
452                target_module_name.is_none_or(|name| name == module.module.name().as_str());
453
454            // Check if account/platform match (usually yes for parser tasks)
455            let is_same_context = task.account_task.account == module.account.name
456                && task.account_task.platform == module.platform.name;
457
458            if is_same_module && is_same_context {
459                debug!(
460                    "[ResponseParserProcessor] Optimizing: Generating requests locally for same module"
461                );
462
463                // Create a shallow clone of the module to inject context
464                let mut module_clone = (*module).clone();
465                module_clone.pending_ctx = Some(task.context.clone());
466                module_clone.prefix_request = task.prefix_request;
467                module_clone.run_id = task.run_id;
468
469                let task_meta = task.metadata.clone();
470
471                match module_clone.generate(task_meta, login_info.clone()).await {
472                    Ok(mut stream) => {
473                        let request_queue = self.queue_manager.get_request_push_channel();
474                        let mut generated_count = 0;
475                        while let Some(req) = stream.next().await {
476                            let req_id = req.id.to_string();
477                            let req_module_id = req.module_id();
478                            let context = format!(
479                                "generated_request request_id={} module_id={}",
480                                req_id, req_module_id
481                            );
482                            if self
483                                .send_with_backpressure(
484                                    &request_queue,
485                                    QueuedItem::new(req),
486                                    "request",
487                                    &context,
488                                )
489                                .await
490                            {
491                                generated_count += 1;
492                            }
493                        }
494                        info!(
495                            "[ResponseParserProcessor] Locally generated {} requests",
496                            generated_count
497                        );
498                        self.emit_semantic_event(
499                            EventType::ParserTaskProduced,
500                            json!({
501                                "account": task_account,
502                                "platform": task_platform,
503                                "run_id": task_run_id,
504                                "module_id": task_module_id,
505                                "step_idx": task_step_idx,
506                                "prefix_request": task_prefix_request,
507                                "path": "local_generate"
508                            }),
509                        )
510                        .await;
511                        self.emit_semantic_event(
512                            EventType::ModuleStepAdvanced,
513                            json!({
514                                "account": task_account,
515                                "platform": task_platform,
516                                "run_id": task_run_id,
517                                "module_id": task_module_id,
518                                "step_idx": task_step_idx,
519                                "prefix_request": task_prefix_request,
520                                "generated_count": generated_count,
521                                "mode": "local_generate"
522                            }),
523                        )
524                        .await;
525                        // Successfully handled, do NOT enqueue ParserTaskModel
526                    }
527                    Err(e) => {
528                        error!(
529                            "[ResponseParserProcessor] Failed to generate requests locally: {}, falling back to queue",
530                            e
531                        );
532                        // Fallback: put the task back into parser_task queue
533                        let context = format!(
534                            "parser_task_fallback account={} platform={} run_id={} module_id={}",
535                            task.account_task.account,
536                            task.account_task.platform,
537                            task.run_id,
538                            task.context.module_id.as_deref().unwrap_or("unknown")
539                        );
540                        if !self
541                            .send_with_backpressure(
542                                &parser_task_queue,
543                                QueuedItem::new(task),
544                                "parser_task",
545                                &context,
546                            )
547                            .await
548                        {
549                            error!(
550                                "[ResponseParserProcessor] failed to send parser task fallback: {}",
551                                context
552                            );
553                        } else {
554                            self.emit_semantic_event(
555                                EventType::ParserTaskProduced,
556                                json!({
557                                    "account": task_account,
558                                    "platform": task_platform,
559                                    "run_id": task_run_id,
560                                    "module_id": task_module_id,
561                                    "step_idx": task_step_idx,
562                                    "prefix_request": task_prefix_request,
563                                    "path": "fallback_queue"
564                                }),
565                            )
566                            .await;
567                            self.emit_semantic_event(
568                                EventType::ModuleStepFallback,
569                                json!({
570                                    "account": task_account,
571                                    "platform": task_platform,
572                                    "run_id": task_run_id,
573                                    "module_id": task_module_id,
574                                    "step_idx": task_step_idx,
575                                    "prefix_request": task_prefix_request,
576                                    "reason": "local_generate_failed",
577                                    "error": e.to_string(),
578                                    "path": "parser_task_queue"
579                                }),
580                            )
581                            .await;
582                        }
583                    }
584                }
585            } else {
586                // Different module/context, enqueue as usual
587                let context = format!(
588                    "parser_task account={} platform={} run_id={} module_id={}",
589                    task.account_task.account,
590                    task.account_task.platform,
591                    task.run_id,
592                    task.context.module_id.as_deref().unwrap_or("unknown")
593                );
594                if !self
595                    .send_with_backpressure(
596                        &parser_task_queue,
597                        QueuedItem::new(task),
598                        "parser_task",
599                        &context,
600                    )
601                    .await
602                {
603                    error!(
604                        "[ResponseParserProcessor] failed to send parser task: {}",
605                        context
606                    );
607                } else {
608                    self.emit_semantic_event(
609                        EventType::ParserTaskProduced,
610                        json!({
611                            "account": task_account,
612                            "platform": task_platform,
613                            "run_id": task_run_id,
614                            "module_id": task_module_id,
615                            "step_idx": task_step_idx,
616                            "prefix_request": task_prefix_request,
617                            "path": "parser_task_queue"
618                        }),
619                    )
620                    .await;
621                }
622            }
623        }
624
625        if let Some(mut msg) = data.error_task {
626            warn!(
627                "[ResponseParserProcessor] recorded response error for request_id={}, message={}",
628                input.0.id, msg.error_msg
629            );
630            msg.prefix_request = input.0.prefix_request;
631            msg.run_id = input.0.run_id;
632            let queue = self.queue_manager.get_error_push_channel();
633            let context = format!(
634                "error_task request_id={} module_id={}",
635                input.0.id,
636                input.0.module_id()
637            );
638            if !self
639                .send_with_backpressure(&queue, QueuedItem::new(msg), "error", &context)
640                .await
641            {
642                error!(
643                    "[ResponseParserProcessor] failed to send parser error: {}",
644                    context
645                );
646            } else {
647                self.emit_semantic_event(
648                    EventType::ErrorTaskProduced,
649                    json!({
650                        "request_id": input.0.id,
651                        "account": input.0.account,
652                        "platform": input.0.platform,
653                        "module_id": input.0.module_id(),
654                        "step_idx": input.0.context.step_idx,
655                        "prefix_request": input.0.prefix_request
656                    }),
657                )
658                .await;
659            }
660
661            // Legacy `record_response_error` is no longer used.
662            // Errors are already tracked via `error_tracker`.
663        }
664
665        data.data.iter_mut().for_each(|x| {
666            x.account = input.0.account.clone();
667            x.platform = input.0.platform.clone();
668            // Keep parser-provided middleware when present; otherwise inherit module defaults.
669            if x.data_middleware.is_empty() {
670                x.data_middleware = input.0.data_middleware.clone();
671            }
672            x.request_id = input.0.id;
673            x.meta = input.0.metadata.clone();
674        });
675        ProcessorResult::Success(data.data)
676    }
677    async fn post_process(
678        &self,
679        input: &(Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>),
680        _output: &Vec<DataEvent>,
681        _context: &ProcessorContext,
682    ) -> Result<()> {
683        // Centralized lock release after parsing to avoid deadlocks and duplicate unlocks.
684
685        let config = self.state.config.read().await;
686
687        // Cache response payload so duplicate downloads can be short-circuited.
688        if config.download_config.enable_session {
689            if let Some(request_hash) = &input.0.request_hash {
690                input.0.send(request_hash, &self.cache_service).await.ok();
691            }
692        }
693
694        if config.download_config.enable_locker {
695            self.state
696                .status_tracker
697                .release_module_locker(&input.0.module_runtime_id())
698                .await;
699            debug!(
700                "[ResponseParserProcessor] released module lock after parsing: module_id={}",
701                input.0.module_runtime_id()
702            );
703        }
704        Ok(())
705    }
706    async fn handle_error(
707        &self,
708        input: &(Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>),
709        error: Error,
710        _context: &ProcessorContext,
711    ) -> ProcessorResult<Vec<DataEvent>> {
712        error!(
713            "[ResponseParserProcessor] fatal parser error: request_id={} module_id={} error={}",
714            input.0.id,
715            input.0.module_id(),
716            error
717        );
718
719        // Error has already been tracked in `process()` via `error_tracker`.
720        // Here we only need to build and enqueue `ErrorTaskModel`.
721
722        let timestamp = match SystemTime::now().duration_since(UNIX_EPOCH) {
723            Ok(duration) => duration.as_secs(),
724            Err(e) => {
725                warn!(
726                    "[ResponseParserProcessor] system time before UNIX_EPOCH, fallback to 0: {}",
727                    e
728                );
729                0
730            }
731        };
732        let error_metadata = input
733            .0
734            .metadata
735            .task
736            .as_object()
737            .cloned()
738            .unwrap_or_default();
739        let error_task = TaskErrorEvent {
740            id: input.0.id,
741            account_task: TaskEvent {
742                account: input.0.account.clone(),
743                platform: input.0.platform.clone(),
744                module: Some(vec![input.0.module.clone()]),
745                run_id: input.0.run_id,
746                priority: input.0.priority,
747            },
748            error_msg: error.to_string(),
749            timestamp,
750            metadata: error_metadata,
751            context: input.0.context.clone(),
752            run_id: input.0.run_id,
753            prefix_request: input.0.prefix_request,
754        };
755        let queue = self.queue_manager.get_error_push_channel();
756        let context = format!(
757            "handle_error request_id={} module_id={}",
758            input.0.id,
759            input.0.module_id()
760        );
761        if !self
762            .send_with_backpressure(&queue, QueuedItem::new(error_task), "error", &context)
763            .await
764        {
765            error!(
766                "[ResponseParserProcessor] failed to enqueue ErrorTaskModel: {}",
767                context
768            );
769        }
770
771        // Release module lock after enqueueing error task.
772        if self.state.config.read().await.download_config.enable_locker {
773            self.state
774                .status_tracker
775                .release_module_locker(&input.0.module_runtime_id())
776                .await;
777        }
778
779        ProcessorResult::FatalFailure(error)
780    }
781}
782impl
783    EventProcessorTrait<
784        (Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>),
785        Vec<DataEvent>,
786    > for ResponseParserProcessor
787{
788    fn pre_status(
789        &self,
790        input: &(Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>),
791    ) -> Option<EventEnvelope> {
792        Some(EventEnvelope::engine(
793            EventType::Parser,
794            EventPhase::Started,
795            ParserEvent::from(&input.0),
796        ))
797    }
798
799    fn finish_status(
800        &self,
801        input: &(Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>),
802        _output: &Vec<DataEvent>,
803    ) -> Option<EventEnvelope> {
804        Some(EventEnvelope::engine(
805            EventType::Parser,
806            EventPhase::Completed,
807            ParserEvent::from(&input.0),
808        ))
809    }
810
811    fn working_status(
812        &self,
813        input: &(Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>),
814    ) -> Option<EventEnvelope> {
815        Some(EventEnvelope::engine(
816            EventType::Parser,
817            EventPhase::Started,
818            ParserEvent::from(&input.0),
819        ))
820    }
821
822    fn error_status(
823        &self,
824        input: &(Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>),
825        err: &Error,
826    ) -> Option<EventEnvelope> {
827        Some(EventEnvelope::engine_error(
828            EventType::Parser,
829            EventPhase::Failed,
830            ParserEvent::from(&input.0),
831            err,
832        ))
833    }
834
835    fn retry_status(
836        &self,
837        input: &(Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>),
838        retry_policy: &RetryPolicy,
839    ) -> Option<EventEnvelope> {
840        Some(EventEnvelope::engine(
841            EventType::Parser,
842            EventPhase::Retry,
843            json!({
844                "data": ParserEvent::from(&input.0),
845                "retry_count": retry_policy.current_retry,
846                "reason": retry_policy.reason.clone().unwrap_or_default(),
847            }),
848        ))
849    }
850}
851
852pub struct DataMiddlewareProcessor {
853    middleware_manager: Arc<MiddlewareManager>,
854}
855
856#[async_trait]
857impl ProcessorTrait<DataEvent, DataEvent> for DataMiddlewareProcessor {
858    fn name(&self) -> &'static str {
859        "DataMiddlewareProcessor"
860    }
861
862    async fn process(
863        &self,
864        input: DataEvent,
865        context: ProcessorContext,
866    ) -> ProcessorResult<DataEvent> {
867        // info!(
868        //    "[DataMiddlewareProcessor] start: account={} platform={} size={}",
869        //    input.account,
870        //    input.platform,
871        //    input.size()
872        // );
873        let start = std::time::Instant::now();
874        let config = context
875            .metadata
876            .read()
877            .await
878            .get("config")
879            .map(|x| {
880                serde_json::from_value::<ModuleConfig>(x.clone()).unwrap_or_else(|e| {
881                    warn!(
882                        "[DataMiddlewareProcessor] config conversion failed, using default: request_id={} module={} error={}",
883                        input.request_id,
884                        input.module,
885                        e
886                    );
887                    ModuleConfig::default()
888                })
889            });
890        let modified_data = self.middleware_manager.handle_data(input, &config).await;
891        let elapsed_ms = start.elapsed().as_millis();
892        if elapsed_ms > 10 {
893            info!(
894                "[DataMiddlewareProcessor] SLOW middleware execution: {} ms",
895                elapsed_ms
896            );
897        }
898        match modified_data {
899            Some(data) => ProcessorResult::Success(data),
900            None => ProcessorResult::FatalFailure(
901                DataMiddlewareError::EmptyData("data dropped by data middleware".into()).into(),
902            ),
903        }
904    }
905}
906impl EventProcessorTrait<DataEvent, DataEvent> for DataMiddlewareProcessor {
907    fn pre_status(&self, input: &DataEvent) -> Option<EventEnvelope> {
908        Some(EventEnvelope::engine(
909            EventType::MiddlewareBefore,
910            EventPhase::Started,
911            DataMiddlewareEvent::from(input),
912        ))
913    }
914
915    fn finish_status(&self, input: &DataEvent, output: &DataEvent) -> Option<EventEnvelope> {
916        let mut event: DataMiddlewareEvent = input.into();
917        event.after_size = output.size().into();
918        Some(EventEnvelope::engine(
919            EventType::MiddlewareBefore,
920            EventPhase::Completed,
921            event,
922        ))
923    }
924
925    fn working_status(&self, input: &DataEvent) -> Option<EventEnvelope> {
926        Some(EventEnvelope::engine(
927            EventType::MiddlewareBefore,
928            EventPhase::Started,
929            DataMiddlewareEvent::from(input),
930        ))
931    }
932
933    fn error_status(&self, input: &DataEvent, err: &Error) -> Option<EventEnvelope> {
934        Some(EventEnvelope::engine_error(
935            EventType::MiddlewareBefore,
936            EventPhase::Failed,
937            DataMiddlewareEvent::from(input),
938            err,
939        ))
940    }
941
942    fn retry_status(&self, input: &DataEvent, retry_policy: &RetryPolicy) -> Option<EventEnvelope> {
943        Some(EventEnvelope::engine(
944            EventType::MiddlewareBefore,
945            EventPhase::Retry,
946            json!({
947                "data": DataMiddlewareEvent::from(input),
948                "retry_count": retry_policy.current_retry,
949                "reason": retry_policy.reason.clone().unwrap_or_default(),
950            }),
951        ))
952    }
953}
954
955pub struct DataStoreProcessor {
956    middleware_manager: Arc<MiddlewareManager>,
957    /// Cumulative store outcomes.
958    ///
959    /// This stage counts **per store operation**, unlike the terminal-outcome counting the other
960    /// stages use, because it has no terminal failure of its own: it can only answer "retry me",
961    /// and the give-up decision happens two layers up (the queue backend's NACK policy), which is
962    /// invisible from here. Under the single-node default (`nack_max_retries` unset → 0) nothing is
963    /// ever retried, so a `RetryableFailure` *is* terminal and the count is exact. With an MQ
964    /// backend and retries enabled, one item failing repeatedly is counted once per attempt.
965    outcomes: Arc<crate::engine::runner::StageCounter>,
966}
967#[async_trait]
968impl ProcessorTrait<DataEvent, ()> for DataStoreProcessor {
969    fn name(&self) -> &'static str {
970        "DataStoreProcessor"
971    }
972    /// This stage uses `RetryableFailure` to trigger retries.
973    ///
974    /// Additional retry context can be passed through `retry_policy.meta`
975    /// for downstream retry behavior customization.
976    async fn process(&self, input: DataEvent, context: ProcessorContext) -> ProcessorResult<()> {
977        info!(
978            "[DataStoreProcessor] start store: request_id={} account={} platform={} module={} size={}",
979            input.request_id,
980            input.account,
981            input.platform,
982            input.module,
983            input.size()
984        );
985        let mut middleware = vec![];
986        if let Some(retry_policy) = &context.retry_policy
987            && let Some(m_val) = retry_policy.meta.get("middleware")
988            && let Some(m) = m_val.as_array()
989        {
990            middleware = m
991                .iter()
992                .filter_map(|x| x.as_str())
993                .map(|x| x.to_string())
994                .collect::<Vec<String>>();
995        }
996        let config = context
997            .metadata
998            .read()
999            .await
1000            .get("config")
1001            .map(|x| {
1002                serde_json::from_value::<ModuleConfig>(x.clone()).unwrap_or_else(|e| {
1003                    warn!(
1004                        "[DataStoreProcessor] config conversion failed, using default: request_id={} module={} error={}",
1005                        input.request_id,
1006                        input.module,
1007                        e
1008                    );
1009                    ModuleConfig::default()
1010                })
1011            });
1012        let request_id = input.request_id;
1013        let res = if middleware.is_empty() {
1014            self.middleware_manager
1015                .handle_store_data(input, &config)
1016                .await
1017        } else {
1018            self.middleware_manager
1019                .handle_store_data_with_middleware(input, middleware, &config)
1020                .await
1021        };
1022        if res.is_empty() {
1023            self.outcomes.record_success();
1024            info!(
1025                "[DataStoreProcessor] store success, request_id={}",
1026                request_id
1027            );
1028            ProcessorResult::Success(())
1029        } else {
1030            self.outcomes.record_failure();
1031            let error_msg = res
1032                .iter()
1033                .map(|(m, e)| format!("Middleware: {m}, Error: {e:?}"))
1034                .collect::<Vec<String>>()
1035                .join("; ");
1036            let mut retry_policy = context
1037                .retry_policy
1038                .unwrap_or_default()
1039                .with_reason(error_msg);
1040            let error_middleware = res.keys().map(|x| x.to_string()).collect::<Vec<String>>();
1041            retry_policy.meta = serde_json::json!({ "middleware": error_middleware });
1042            warn!(
1043                "[DataStoreProcessor] request={}, store error, will retry: {}",
1044                request_id,
1045                retry_policy.reason.clone().unwrap_or_default()
1046            );
1047            ProcessorResult::RetryableFailure(retry_policy)
1048        }
1049    }
1050}
1051impl EventProcessorTrait<DataEvent, ()> for DataStoreProcessor {
1052    fn pre_status(&self, input: &DataEvent) -> Option<EventEnvelope> {
1053        Some(EventEnvelope::engine(
1054            EventType::DataStore,
1055            EventPhase::Started,
1056            DataStoreEvent::from(input),
1057        ))
1058    }
1059
1060    fn finish_status(&self, input: &DataEvent, _output: &()) -> Option<EventEnvelope> {
1061        Some(EventEnvelope::engine(
1062            EventType::DataStore,
1063            EventPhase::Completed,
1064            DataStoreEvent::from(input),
1065        ))
1066    }
1067
1068    fn working_status(&self, input: &DataEvent) -> Option<EventEnvelope> {
1069        Some(EventEnvelope::engine(
1070            EventType::DataStore,
1071            EventPhase::Started,
1072            DataStoreEvent::from(input),
1073        ))
1074    }
1075
1076    fn error_status(&self, input: &DataEvent, err: &Error) -> Option<EventEnvelope> {
1077        Some(EventEnvelope::engine_error(
1078            EventType::DataStore,
1079            EventPhase::Failed,
1080            DataStoreEvent::from(input),
1081            err,
1082        ))
1083    }
1084
1085    fn retry_status(&self, input: &DataEvent, retry_policy: &RetryPolicy) -> Option<EventEnvelope> {
1086        Some(EventEnvelope::engine(
1087            EventType::DataStore,
1088            EventPhase::Retry,
1089            json!({
1090                "data": DataStoreEvent::from(input),
1091                "retry_count": retry_policy.current_retry,
1092                "reason": retry_policy.reason.clone().unwrap_or_default(),
1093            }),
1094        ))
1095    }
1096}
1097
1098/// Builds parser chain for response processing and data persistence.
1099///
1100/// Flow:
1101/// `response -> module/context -> parser -> data middleware -> data store`.
1102///
1103/// Notes:
1104/// - All `Data` emitted from one response share the same module config context.
1105/// - Data middleware and store stages run in parallel map form with skip-on-error strategy.
1106pub async fn create_parser_chain(
1107    state: Arc<PipelineContext>,
1108    #[allow(dead_code)] task_manager: Arc<TaskManager>,
1109    middleware_manager: Arc<MiddlewareManager>,
1110    queue_manager: Arc<QueueManager>,
1111    event_bus: Option<Arc<EventBus>>,
1112    cache_service: Arc<CacheService>,
1113    store_outcomes: Arc<crate::engine::runner::StageCounter>,
1114) -> EventAwareTypedChain<Response, Vec<()>> {
1115    let response_module_processor = ResponseModuleProcessor {
1116        task_manager: task_manager.clone(),
1117        cache_service: cache_service.clone(),
1118        state: state.clone(),
1119        config_cache: Arc::new(DashMap::new()),
1120    };
1121    let response_parser_processor = ResponseParserProcessor {
1122        task_manager: task_manager.clone(),
1123        queue_manager,
1124        state,
1125        cache_service: cache_service.clone(),
1126        event_bus: event_bus.clone(),
1127    };
1128    let data_middleware_processor = DataMiddlewareProcessor {
1129        middleware_manager: middleware_manager.clone(),
1130    };
1131    let data_store_processor = DataStoreProcessor {
1132        middleware_manager,
1133        outcomes: store_outcomes,
1134    };
1135
1136    EventAwareTypedChain::<Response, Response>::new(event_bus)
1137        .then::<(Response, Arc<Module>, Arc<ModuleConfig>, Option<LoginInfo>), _>(
1138            response_module_processor,
1139        )
1140        .then::<Vec<DataEvent>, _>(response_parser_processor)
1141        .then_map_vec_parallel_with_strategy_silent::<DataEvent, _>(
1142            data_middleware_processor,
1143            64,
1144            ErrorStrategy::Skip,
1145        )
1146        .then_map_vec_parallel_with_strategy_silent::<(), _>(
1147            data_store_processor,
1148            64,
1149            ErrorStrategy::Skip,
1150        )
1151}