1use crate::request::Request;
4use std::{sync::Arc, time::Duration};
5
6#[derive(Debug, Clone)]
8pub struct HandledRequest {
9 pub request: Arc<Request>,
11 pub loaded_url: Option<url::Url>,
13 pub outcome: RequestFinalState,
15 pub response_status: Option<http::StatusCode>,
17 pub retry_count: u32,
19 pub duration: Duration,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum RequestFinalState {
27 Succeeded,
29 Failed,
31 Skipped,
33}
34
35#[derive(Debug, Clone, Default)]
39#[non_exhaustive]
40pub struct SystemSnapshot {
41 pub created_at: Option<time::OffsetDateTime>,
43 pub cpu_used_ratio: Option<f32>,
45 pub memory_used_bytes: Option<u64>,
47}
48
49#[derive(Debug, Clone)]
51#[non_exhaustive]
52pub enum CrawlerEvent {
53 PersistState {
55 is_migrating: bool,
57 },
58 RequestFinished(HandledRequest),
60 RequestFailed {
62 request: Arc<Request>,
64 error: String,
66 },
67 SystemInfo(SystemSnapshot),
69 Aborting,
71 Exiting,
73}
74
75pub type EventStream = tokio::sync::broadcast::Receiver<CrawlerEvent>;
77
78pub type ResultStream = tokio::sync::broadcast::Receiver<HandledRequest>;
80
81#[derive(Debug, Clone)]
83pub struct EventBus {
84 tx: tokio::sync::broadcast::Sender<CrawlerEvent>,
85}
86
87impl EventBus {
88 pub fn new(capacity: usize) -> Self {
94 assert!(capacity > 0, "event bus capacity must be greater than zero");
95 let (tx, _) = tokio::sync::broadcast::channel(capacity);
96 Self { tx }
97 }
98
99 pub fn subscribe(&self) -> EventStream {
101 self.tx.subscribe()
102 }
103
104 pub fn emit(&self, event: CrawlerEvent) {
108 let _ = self.tx.send(event);
109 }
110
111 pub fn subscriber_count(&self) -> usize {
113 self.tx.receiver_count()
114 }
115}
116
117impl Default for EventBus {
118 fn default() -> Self {
119 Self::new(1024)
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[tokio::test]
128 async fn emit_without_subscribers_does_not_panic() {
129 EventBus::default().emit(CrawlerEvent::Exiting);
130 }
131
132 #[tokio::test]
133 async fn subscriber_receives_emitted_event() {
134 let bus = EventBus::default();
135 let mut subscriber = bus.subscribe();
136 bus.emit(CrawlerEvent::Aborting);
137 assert!(matches!(
138 subscriber.recv().await,
139 Ok(CrawlerEvent::Aborting)
140 ));
141 }
142
143 #[tokio::test]
144 async fn all_subscribers_receive_the_same_event() {
145 let bus = EventBus::default();
146 let mut first = bus.subscribe();
147 let mut second = bus.subscribe();
148 bus.emit(CrawlerEvent::PersistState { is_migrating: true });
149 assert!(matches!(
150 first.recv().await,
151 Ok(CrawlerEvent::PersistState { is_migrating: true })
152 ));
153 assert!(matches!(
154 second.recv().await,
155 Ok(CrawlerEvent::PersistState { is_migrating: true })
156 ));
157 }
158
159 #[tokio::test]
160 async fn late_subscriber_does_not_receive_earlier_event() {
161 let bus = EventBus::default();
162 let mut existing = bus.subscribe();
163 bus.emit(CrawlerEvent::Exiting);
164 let mut late = bus.subscribe();
165 assert!(matches!(existing.recv().await, Ok(CrawlerEvent::Exiting)));
166 assert!(matches!(
167 late.try_recv(),
168 Err(tokio::sync::broadcast::error::TryRecvError::Empty)
169 ));
170 }
171}