1use chrono::Utc;
2use std::collections::{HashMap, HashSet, BinaryHeap};
3use tokio::sync::Semaphore;
4use std::sync::{Arc, Mutex};
5use std::time::Duration;
6use tokio::sync::RwLock;
7
8use crate::file_store::FileStore;
9use crate::rate_limiter::RateLimiter;
10use crate::task::{RetryableTask, PriorityTask, ProgressMessage};
11use tokio::sync::broadcast;
12
13pub type TaskHandler = Arc<dyn Fn(String) -> Result<(), String> + Send + Sync>;
14pub type MaxRetryHandler = Arc<dyn Fn(String) -> Result<(), String> + Send + Sync>;
15
16struct ExecutingGuard {
17 executing_tasks: Arc<Mutex<HashSet<String>>>,
18 task_id: String,
19}
20
21impl Drop for ExecutingGuard {
22 fn drop(&mut self) {
23 if let Ok(mut executing) = self.executing_tasks.lock() {
24 executing.remove(&self.task_id);
25 }
26 }
27}
28
29#[derive(Clone)]
30pub struct SnerdQueue {
31 pub name: String,
32 pub file_store: FileStore,
33 pub rate_limiter: RateLimiter,
34 task_handlers: Arc<RwLock<HashMap<String, TaskHandler>>>,
35 max_retry_handlers: Arc<RwLock<HashMap<String, MaxRetryHandler>>>,
36 active_hashes: Arc<Mutex<HashSet<String>>>,
37 executing_tasks: Arc<Mutex<HashSet<String>>>,
38 worker_semaphore: Arc<Semaphore>,
39 pub progress_tx: broadcast::Sender<ProgressMessage>,
40}
41
42impl SnerdQueue {
43 pub fn new(name: &str, file_store: FileStore, rate_limiter: RateLimiter) -> Self {
44 let mut initial_hashes = HashSet::new();
45 if let Ok(tasks) = file_store.read_tasks() {
46 for task in tasks {
47 if task.deleted_at.is_none() {
48 if let Some(hash) = task.payload_hash {
49 initial_hashes.insert(hash);
50 }
51 }
52 }
53 }
54
55 let (progress_tx, _) = broadcast::channel(1024);
56 Self {
57 name: name.to_string(),
58 file_store,
59 rate_limiter,
60 task_handlers: Arc::new(RwLock::new(HashMap::new())),
61 max_retry_handlers: Arc::new(RwLock::new(HashMap::new())),
62 active_hashes: Arc::new(Mutex::new(initial_hashes)),
63 executing_tasks: Arc::new(Mutex::new(HashSet::new())),
64 worker_semaphore: Arc::new(Semaphore::new(100)), progress_tx,
66 }
67 }
68
69 pub fn subscribe_progress(&self) -> broadcast::Receiver<ProgressMessage> {
70 self.progress_tx.subscribe()
71 }
72
73 pub fn yield_progress(&self, task_id: &str, data: &str) {
74 let _ = self.progress_tx.send(ProgressMessage {
75 task_id: task_id.to_string(),
76 data: data.to_string(),
77 });
78 }
79
80 pub async fn register_task_handler<F>(&self, task_type: &str, handler: F)
81 where
82 F: Fn(String) -> Result<(), String> + Send + Sync + 'static,
83 {
84 self.task_handlers
85 .write()
86 .await
87 .insert(task_type.to_string(), Arc::new(handler));
88 }
89
90 pub async fn register_max_retry_handler<F>(&self, task_type: &str, handler: F)
91 where
92 F: Fn(String) -> Result<(), String> + Send + Sync + 'static,
93 {
94 self.max_retry_handlers
95 .write()
96 .await
97 .insert(task_type.to_string(), Arc::new(handler));
98 }
99
100 pub fn enqueue(&self, mut task: RetryableTask) -> std::io::Result<()> {
101 if let Some(ref hash) = task.payload_hash {
102 if let Ok(mut hashes) = self.active_hashes.lock() {
103 if hashes.contains(hash) {
104 return Ok(());
105 }
106 hashes.insert(hash.clone());
107 }
108 }
109 task.deleted_at = None;
110 self.file_store.save_task(&task)?;
111
112 if task.retry_after_time <= Utc::now() {
113 if let Some(ref group) = task.rate_limit_group {
114 if let Some(limit) = task.max_per_minute {
115 match self.rate_limiter.check_and_increment(group, limit) {
116 Ok(true) => {}
117 Ok(false) | Err(_) => {
118 task.retry_after_time = Utc::now() + chrono::Duration::seconds(60);
119 let _ = self.file_store.save_task(&task);
120 return Ok(());
121 }
122 }
123 }
124 }
125
126 if let Ok(mut executing) = self.executing_tasks.lock() {
128 if executing.contains(&task.task_id) {
129 return Ok(());
130 }
131 executing.insert(task.task_id.clone());
132 }
133
134 let q = self.clone();
135 tokio::spawn(async move {
136 q.execute_task(task).await;
137 });
138 }
139 Ok(())
140 }
141
142 pub async fn start_processor(&self, interval: Duration) {
143 let q = self.clone();
144 tokio::spawn(async move {
145 let mut interval_timer = tokio::time::interval(interval);
146 loop {
147 interval_timer.tick().await;
148 q.process_due_tasks().await;
149 }
150 });
151 }
152
153 pub async fn process_due_tasks(&self) {
154 let tasks = match self.file_store.read_tasks() {
155 Ok(t) => t,
156 Err(_) => return,
157 };
158
159 let now = Utc::now();
160 let mut heap = BinaryHeap::new();
161
162 for task in tasks {
163 if task.retry_after_time <= now && task.deleted_at.is_none() {
164 heap.push(PriorityTask(task));
165 }
166 }
167
168 let available = self.worker_semaphore.available_permits();
169 for _ in 0..available {
170 if let Some(PriorityTask(mut task)) = heap.pop() {
171 if let Some(ref group) = task.rate_limit_group {
172 if let Some(limit) = task.max_per_minute {
173 match self.rate_limiter.check_and_increment(group, limit) {
174 Ok(true) => {}
175 Ok(false) | Err(_) => {
176 task.retry_after_time = now + chrono::Duration::seconds(60);
177 let _ = self.file_store.save_task(&task);
178 continue;
179 }
180 }
181 }
182 }
183
184 if let Ok(mut executing) = self.executing_tasks.lock() {
186 if executing.contains(&task.task_id) {
187 continue;
188 }
189 executing.insert(task.task_id.clone());
190 }
191
192 if let Ok(permit) = self.worker_semaphore.clone().try_acquire_owned() {
193 let q = self.clone();
194 tokio::spawn(async move {
195 let _p = permit;
196 q.execute_task(task).await;
197 });
198 }
199 } else {
200 break;
201 }
202 }
203 }
204
205 async fn execute_task(&self, mut task: RetryableTask) {
206 let _guard = ExecutingGuard {
208 executing_tasks: Arc::clone(&self.executing_tasks),
209 task_id: task.task_id.clone(),
210 };
211
212 let handler = {
213 let handlers = self.task_handlers.read().await;
214 handlers.get(&task.task_type).cloned()
215 };
216
217 if let Some(h) = handler {
218 let task_data = task.task_data.clone();
219
220 let result = tokio::task::spawn_blocking(move || h(task_data))
221 .await
222 .unwrap_or_else(|e| Err(format!("Task panic: {:?}", e)));
223
224 match result {
225 Ok(_) => {
226 let _ = self.file_store.delete_task(&task.task_id);
227 if let Some(ref hash) = task.payload_hash {
228 if let Ok(mut hashes) = self.active_hashes.lock() {
229 hashes.remove(hash);
230 }
231 }
232 }
233 Err(e) => {
234 if task.retry_count < task.max_retries {
235 task.update_retry_config(Some(e));
236 let _ = self.file_store.save_task(&task);
237 } else {
238 let max_handler = {
240 let max_handlers = self.max_retry_handlers.read().await;
241 max_handlers.get(&task.task_type).cloned()
242 };
243
244 if let Some(mh) = max_handler {
245 let max_data = task.task_data.clone();
246 let _ = tokio::task::spawn_blocking(move || mh(max_data)).await;
247 }
248
249 let _ = self.file_store.delete_task(&task.task_id);
250 if let Some(ref hash) = task.payload_hash {
251 if let Ok(mut hashes) = self.active_hashes.lock() {
252 hashes.remove(hash);
253 }
254 }
255 }
256 }
257 }
258 }
259 }
260}