1use crate::{TimerNameLike, TimerRegistration, TimerResult, TimerTarget, TimerWrapper, timer_iter};
2use anyhow::{Result, anyhow};
3use framework_core::current_millis;
4use futures_util::FutureExt;
5use std::any::Any;
6use std::collections::HashMap;
7use std::panic::AssertUnwindSafe;
8use std::sync::{Arc, OnceLock};
9use tokio::sync::Semaphore;
10use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};
11use tokio::task::{Id, JoinSet};
12use tokio::time::{Duration, sleep};
13
14static WAKE_SENDER: OnceLock<UnboundedSender<String>> = OnceLock::new();
15
16#[derive(Clone, Copy, Debug, Default)]
18pub struct TimerOption {
19 pub max_concurrent_tasks: Option<usize>,
21 pub retry_immediately_on_panic: bool,
23}
24
25struct TimerState {
26 name: String,
27 registration: &'static TimerRegistration,
28 next_run_at: i64,
29 running: bool,
30}
31
32pub fn start(wrapper: Option<TimerWrapper>, option: TimerOption) {
37 let semaphore = option.max_concurrent_tasks.map(|count| {
38 assert!(count > 0, "max_concurrent_tasks 必须大于 0");
39 Arc::new(Semaphore::new(count))
40 });
41 let states = timer_iter()
42 .flat_map(|registration| {
43 (1..=registration.count).map(move |index| TimerState {
44 name: if registration.count == 1 {
45 registration.name.to_owned()
46 } else {
47 format!("{}_{index}", registration.name)
48 },
49 registration,
50 next_run_at: 0,
51 running: false,
52 })
53 })
54 .collect::<Vec<_>>();
55 let (wake_sender, wake_receiver) = unbounded_channel();
56 WAKE_SENDER
57 .set(wake_sender)
58 .expect("定时任务运行时只能启动一次");
59
60 tokio::spawn(run_scheduler(
61 states,
62 wake_receiver,
63 wrapper,
64 option,
65 semaphore,
66 ));
67}
68
69pub fn wake(name: impl TimerNameLike) {
71 let Some(sender) = WAKE_SENDER.get() else {
72 return;
73 };
74 let _ = sender.send(name.timer_name().to_owned());
75}
76
77async fn run_scheduler(
78 mut states: Vec<TimerState>,
79 mut wake_receiver: UnboundedReceiver<String>,
80 wrapper: Option<TimerWrapper>,
81 option: TimerOption,
82 semaphore: Option<Arc<Semaphore>>,
83) {
84 let mut tasks = JoinSet::new();
85 let mut task_indices = HashMap::new();
86
87 loop {
88 let wait_millis = dispatch_due_tasks(
89 &mut states,
90 &mut tasks,
91 &mut task_indices,
92 &wrapper,
93 semaphore.as_ref(),
94 current_millis().expect("获取当前毫秒时间戳失败"),
95 );
96
97 match wait_millis {
98 Some(wait_millis) => {
99 tokio::select! {
100 Some(name) = wake_receiver.recv() => wake_timer(&mut states, name),
101 result = tasks.join_next_with_id(), if !tasks.is_empty() => {
102 if let Some(result) = result {
103 handle_task_result(result, &mut task_indices, &mut states, option);
104 }
105 }
106 _ = sleep(Duration::from_millis(wait_millis)) => {}
107 else => return,
108 }
109 }
110 None => {
111 tokio::select! {
112 Some(name) = wake_receiver.recv() => wake_timer(&mut states, name),
113 result = tasks.join_next_with_id() => {
114 if let Some(result) = result {
115 handle_task_result(result, &mut task_indices, &mut states, option);
116 }
117 }
118 else => return,
119 }
120 }
121 }
122 }
123}
124
125fn wake_timer(states: &mut [TimerState], name: String) {
126 if let Some(state) = states
127 .iter_mut()
128 .find(|state| !state.running && state.registration.name == name)
129 {
130 state.next_run_at = -10086;
131 }
132}
133
134fn dispatch_due_tasks(
135 states: &mut [TimerState],
136 tasks: &mut JoinSet<Result<TimerResult>>,
137 task_indices: &mut HashMap<Id, usize>,
138 wrapper: &Option<TimerWrapper>,
139 semaphore: Option<&Arc<Semaphore>>,
140 now: i64,
141) -> Option<u64> {
142 let mut next_run_at: Option<i64> = None;
143 for (index, state) in states.iter_mut().enumerate() {
144 if state.running {
145 continue;
146 }
147 if state.next_run_at > now {
148 next_run_at =
149 Some(next_run_at.map_or(state.next_run_at, |next| next.min(state.next_run_at)));
150 continue;
151 }
152
153 state.running = true;
154 let target = state.registration.target;
155 let wrapper = wrapper.clone();
156 let semaphore = semaphore.cloned();
157 let task = tasks.spawn(async move {
158 execute_timer(target, wrapper.as_ref(), semaphore.as_ref()).await
159 });
160 task_indices.insert(task.id(), index);
161 }
162 next_run_at.map(|next_run_at| next_run_at.saturating_sub(now).max(0) as u64)
163}
164
165fn handle_task_result(
166 result: Result<(Id, Result<TimerResult>), tokio::task::JoinError>,
167 task_indices: &mut HashMap<Id, usize>,
168 states: &mut [TimerState],
169 option: TimerOption,
170) {
171 let (task_id, result) = match result {
172 Ok((task_id, result)) => (task_id, result),
173 Err(error) => {
174 log::error!("定时任务 Tokio 子任务执行失败:{error:?}");
175 (
176 error.id(),
177 Err(anyhow!("定时任务 Tokio 子任务执行失败:{error:?}")),
178 )
179 }
180 };
181 let index = task_indices
182 .remove(&task_id)
183 .expect("定时任务缺少任务实例索引");
184 let state = &mut states[index];
185 state.running = false;
186
187 let now = current_millis().expect("获取当前毫秒时间戳失败");
188 let next = match result {
189 Ok(result) => {
190 if let Some(error) = result.error {
191 log::error!("定时任务 {} 执行失败:{error:?}", state.name);
192 }
193 match result.next {
194 None => now.saturating_add(state.registration.interval),
195 Some(v) => v,
196 }
197 }
198 Err(error) => {
199 log::error!("定时任务 {} 发生 panic: {error:?}", state.name);
200 if state
201 .registration
202 .retry_immediately_on_panic
203 .unwrap_or(option.retry_immediately_on_panic)
204 {
205 0
206 } else {
207 now.saturating_add(state.registration.interval)
208 }
209 }
210 };
211 state.next_run_at = next
212}
213
214async fn execute_timer(
215 target: TimerTarget,
216 wrapper: Option<&TimerWrapper>,
217 semaphore: Option<&Arc<Semaphore>>,
218) -> Result<TimerResult> {
219 let _permit = match semaphore {
220 Some(semaphore) => Some(
221 semaphore
222 .acquire()
223 .await
224 .map_err(|_| anyhow!("定时任务并发控制 Semaphore 已关闭"))?,
225 ),
226 None => None,
227 };
228
229 let future = match wrapper {
230 Some(wrapper) => wrapper(target),
231 None => target(),
232 };
233
234 AssertUnwindSafe(future)
235 .catch_unwind()
236 .await
237 .map_err(panic_to_error)
238}
239
240fn panic_to_error(payload: Box<dyn Any + Send>) -> anyhow::Error {
241 let message = if let Some(message) = payload.downcast_ref::<&str>() {
242 *message
243 } else if let Some(message) = payload.downcast_ref::<String>() {
244 message.as_str()
245 } else {
246 "未知 panic payload"
247 };
248
249 anyhow!("定时任务执行发生 panic: {message}")
250}