1mod runner;
16
17use std::collections::hash_map::Entry;
18use std::collections::{HashMap, HashSet, VecDeque};
19use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
20use std::sync::{Arc, Mutex, RwLock};
21use std::time::{Duration, Instant};
22
23use async_trait::async_trait;
24use backon::ExponentialBuilder;
25use common_error::ext::BoxedError;
26use common_event_recorder::EventRecorderRef;
27use common_runtime::{JoinHandle, RepeatedTask, TaskFunction};
28use common_telemetry::tracing_context::{FutureExt, TracingContext};
29use common_telemetry::{error, info, tracing};
30use snafu::{OptionExt, ResultExt, ensure};
31use tokio::sync::watch::{self, Receiver, Sender};
32use tokio::sync::{Mutex as TokioMutex, Notify};
33
34use crate::error::{
35 self, CheckStatusSnafu, DuplicateProcedureSnafu, Error, LoaderConflictSnafu,
36 ManagerNotStartSnafu, ManagerPasuedSnafu, PoisonKeyNotDefinedSnafu, ProcedureNotFoundSnafu,
37 Result, StartRemoveOutdatedMetaTaskSnafu, StopRemoveOutdatedMetaTaskSnafu,
38 TooManyRunningProceduresSnafu,
39};
40use crate::local::runner::Runner;
41use crate::procedure::{BoxedProcedureLoader, InitProcedureState, PoisonKeys, ProcedureInfo};
42use crate::rwlock::{KeyRwLock, OwnedKeyRwLockGuard};
43use crate::store::poison_store::PoisonStoreRef;
44use crate::store::{ProcedureMessage, ProcedureMessages, ProcedureStore, StateStoreRef};
45use crate::{
46 BoxedProcedure, ContextProvider, EventTrigger, LockKey, PoisonKey, ProcedureContext,
47 ProcedureId, ProcedureManager, ProcedureState, ProcedureWithId, StringKey, Watcher,
48};
49
50const META_TTL: Duration = Duration::from_secs(60 * 10);
52
53#[derive(Clone, Copy)]
54enum RootSubmissionOrigin {
55 Fresh,
56 Recovery,
57}
58
59impl RootSubmissionOrigin {
60 fn event_trigger(self) -> EventTrigger {
61 match self {
62 Self::Fresh => EventTrigger::Submitted,
63 Self::Recovery => EventTrigger::Recovered,
64 }
65 }
66}
67
68#[derive(Debug)]
77pub(crate) struct ProcedureMeta {
78 id: ProcedureId,
80 type_name: String,
82 parent_id: Option<ProcedureId>,
84 context: ProcedureContext,
86 child_notify: Notify,
88 lock_key: LockKey,
90 poison_keys: PoisonKeys,
92 state_sender: Sender<ProcedureState>,
94 state_receiver: Receiver<ProcedureState>,
96 children: Mutex<Vec<ProcedureId>>,
98 start_time_ms: AtomicI64,
100 end_time_ms: AtomicI64,
102}
103
104impl ProcedureMeta {
105 #[allow(clippy::too_many_arguments)]
106 fn new(
107 id: ProcedureId,
108 procedure_state: ProcedureState,
109 parent_id: Option<ProcedureId>,
110 context: ProcedureContext,
111 lock_key: LockKey,
112 poison_keys: PoisonKeys,
113 type_name: &str,
114 ) -> ProcedureMeta {
115 let (state_sender, state_receiver) = watch::channel(procedure_state);
116 ProcedureMeta {
117 id,
118 parent_id,
119 context,
120 child_notify: Notify::new(),
121 lock_key,
122 poison_keys,
123 state_sender,
124 state_receiver,
125 children: Mutex::new(Vec::new()),
126 start_time_ms: AtomicI64::new(0),
127 end_time_ms: AtomicI64::new(0),
128 type_name: type_name.to_string(),
129 }
130 }
131
132 fn state(&self) -> ProcedureState {
134 self.state_receiver.borrow().clone()
135 }
136
137 fn set_state(&self, state: ProcedureState) {
139 self.state_sender.send(state).unwrap();
141 }
142
143 fn push_child(&self, procedure_id: ProcedureId) {
145 let mut children = self.children.lock().unwrap();
146 children.push(procedure_id);
147 }
148
149 fn list_children(&self, buffer: &mut Vec<ProcedureId>) {
151 let children = self.children.lock().unwrap();
152 buffer.extend_from_slice(&children);
153 }
154
155 fn num_children(&self) -> usize {
157 self.children.lock().unwrap().len()
158 }
159
160 fn set_start_time_ms(&self) {
162 self.start_time_ms
163 .store(common_time::util::current_time_millis(), Ordering::Relaxed);
164 }
165
166 fn set_end_time_ms(&self) {
168 self.end_time_ms
169 .store(common_time::util::current_time_millis(), Ordering::Relaxed);
170 }
171}
172
173type ProcedureMetaRef = Arc<ProcedureMeta>;
175
176struct LoadedProcedure {
178 procedure: BoxedProcedure,
179 step: u32,
180}
181
182pub(crate) type DynamicKeyLock = Arc<KeyRwLock<String>>;
189
190pub async fn acquire_dynamic_key_lock(
195 lock: &DynamicKeyLock,
196 key: &StringKey,
197) -> DynamicKeyLockGuard {
198 match key {
199 StringKey::Share(key) => {
200 let guard = lock.read(key.clone()).await;
201 DynamicKeyLockGuard {
202 guard: Some(OwnedKeyRwLockGuard::from(guard)),
203 key: key.clone(),
204 lock: lock.clone(),
205 }
206 }
207 StringKey::Exclusive(key) => {
208 let guard = lock.write(key.clone()).await;
209 DynamicKeyLockGuard {
210 guard: Some(OwnedKeyRwLockGuard::from(guard)),
211 key: key.clone(),
212 lock: lock.clone(),
213 }
214 }
215 }
216}
217pub struct DynamicKeyLockGuard {
222 guard: Option<OwnedKeyRwLockGuard>,
223 key: String,
224 lock: DynamicKeyLock,
225}
226
227impl Drop for DynamicKeyLockGuard {
228 fn drop(&mut self) {
229 if let Some(guard) = self.guard.take() {
230 drop(guard);
231 }
232 self.lock.clean_keys(std::slice::from_ref(&self.key));
233 }
234}
235
236pub(crate) struct ManagerContext {
238 loaders: Mutex<HashMap<String, BoxedProcedureLoader>>,
240 key_lock: KeyRwLock<String>,
246 dynamic_key_lock: DynamicKeyLock,
253 procedures: RwLock<HashMap<ProcedureId, ProcedureMetaRef>>,
255 running_procedures: Mutex<HashSet<ProcedureId>>,
257 finished_procedures: Mutex<VecDeque<(ProcedureId, Instant)>>,
259 runner_tasks: Mutex<HashMap<ProcedureId, JoinHandle<()>>>,
261 running: Arc<AtomicBool>,
263 poison_manager: PoisonStoreRef,
265}
266
267#[async_trait]
268impl ContextProvider for ManagerContext {
269 async fn procedure_state(&self, procedure_id: ProcedureId) -> Result<Option<ProcedureState>> {
270 Ok(self.state(procedure_id))
271 }
272
273 async fn procedure_state_receiver(
274 &self,
275 procedure_id: ProcedureId,
276 ) -> Result<Option<Receiver<ProcedureState>>> {
277 Ok(self.state_receiver(procedure_id))
278 }
279
280 async fn try_put_poison(&self, key: &PoisonKey, procedure_id: ProcedureId) -> Result<()> {
281 {
282 let procedures = self.procedures.read().unwrap();
284 let procedure = procedures
285 .get(&procedure_id)
286 .context(ProcedureNotFoundSnafu { procedure_id })?;
287
288 ensure!(
290 procedure.poison_keys.contains(key),
291 PoisonKeyNotDefinedSnafu {
292 key: key.clone(),
293 procedure_id
294 }
295 );
296 }
297 let key = key.to_string();
298 let procedure_id = procedure_id.to_string();
299 self.poison_manager.try_put_poison(key, procedure_id).await
300 }
301
302 async fn acquire_lock(&self, key: &StringKey) -> DynamicKeyLockGuard {
303 acquire_dynamic_key_lock(&self.dynamic_key_lock, key).await
304 }
305}
306
307impl ManagerContext {
308 fn new(poison_manager: PoisonStoreRef) -> ManagerContext {
310 ManagerContext {
311 key_lock: KeyRwLock::new(),
312 dynamic_key_lock: Arc::new(KeyRwLock::new()),
313 loaders: Mutex::new(HashMap::new()),
314 procedures: RwLock::new(HashMap::new()),
315 running_procedures: Mutex::new(HashSet::new()),
316 finished_procedures: Mutex::new(VecDeque::new()),
317 runner_tasks: Mutex::new(HashMap::new()),
318 running: Arc::new(AtomicBool::new(false)),
319 poison_manager,
320 }
321 }
322
323 #[cfg(test)]
324 pub(crate) fn set_running(&self) {
325 self.running.store(true, Ordering::Relaxed);
326 }
327
328 pub(crate) fn start(&self) {
330 self.running.store(true, Ordering::Relaxed);
331 }
332
333 pub(crate) fn stop(&self) {
334 self.running.store(false, Ordering::Relaxed);
335 }
336
337 fn reset_runtime_state(&self) {
338 self.procedures.write().unwrap().clear();
339 self.running_procedures.lock().unwrap().clear();
340 self.finished_procedures.lock().unwrap().clear();
341 for handle in self
342 .runner_tasks
343 .lock()
344 .unwrap()
345 .drain()
346 .map(|(_, handle)| handle)
347 {
348 handle.abort();
349 }
350 self.key_lock.clear();
351 self.dynamic_key_lock.clear();
352 }
353
354 fn spawn_runner_task<F>(&self, procedure_id: ProcedureId, spawn: F) -> bool
355 where
356 F: FnOnce() -> JoinHandle<()>,
357 {
358 let mut tasks = self.runner_tasks.lock().unwrap();
359 if !self.running() {
360 return false;
361 }
362
363 let handle = spawn();
364 let _ = tasks.insert(procedure_id, handle);
365 true
366 }
367
368 fn remove_procedure(&self, procedure_id: ProcedureId) {
369 self.procedures.write().unwrap().remove(&procedure_id);
370 self.running_procedures
371 .lock()
372 .unwrap()
373 .remove(&procedure_id);
374 }
375
376 pub(crate) fn remove_runner_task(&self, procedure_id: ProcedureId) {
377 let _ = self.runner_tasks.lock().unwrap().remove(&procedure_id);
378 }
379
380 fn take_runner_tasks(&self) -> Vec<JoinHandle<()>> {
381 self.runner_tasks
382 .lock()
383 .unwrap()
384 .drain()
385 .map(|(_, handle)| handle)
386 .collect()
387 }
388
389 async fn abort_runner_tasks(&self) {
390 let handles = self.take_runner_tasks();
391
392 for handle in &handles {
393 handle.abort();
394 }
395
396 for handle in handles {
397 if let Err(e) = handle.await
398 && !e.is_cancelled()
399 {
400 error!(
401 e; "Procedure runner task exits unexpectedly during stop",
402 );
403 }
404 }
405 }
406
407 pub(crate) fn running(&self) -> bool {
409 self.running.load(Ordering::Relaxed)
410 }
411
412 fn contains_procedure(&self, procedure_id: ProcedureId) -> bool {
414 let procedures = self.procedures.read().unwrap();
415 procedures.contains_key(&procedure_id)
416 }
417
418 fn num_running_procedures(&self) -> usize {
420 self.running_procedures.lock().unwrap().len()
421 }
422
423 fn try_insert_procedure(&self, meta: ProcedureMetaRef) -> bool {
428 let procedure_id = meta.id;
429 let mut procedures = self.procedures.write().unwrap();
430 match procedures.entry(procedure_id) {
431 Entry::Occupied(_) => return false,
432 Entry::Vacant(vacant_entry) => {
433 vacant_entry.insert(meta);
434 }
435 }
436
437 let mut running_procedures = self.running_procedures.lock().unwrap();
438 running_procedures.insert(procedure_id);
439
440 true
441 }
442
443 fn state(&self, procedure_id: ProcedureId) -> Option<ProcedureState> {
445 let procedures = self.procedures.read().unwrap();
446 procedures.get(&procedure_id).map(|meta| meta.state())
447 }
448
449 fn state_receiver(&self, procedure_id: ProcedureId) -> Option<Receiver<ProcedureState>> {
451 let procedures = self.procedures.read().unwrap();
452 procedures
453 .get(&procedure_id)
454 .map(|meta| meta.state_receiver.clone())
455 }
456
457 fn list_procedure(&self) -> Vec<ProcedureInfo> {
459 let procedures = self.procedures.read().unwrap();
460 procedures
461 .values()
462 .map(|meta| ProcedureInfo {
463 id: meta.id,
464 type_name: meta.type_name.clone(),
465 start_time_ms: meta.start_time_ms.load(Ordering::Relaxed),
466 end_time_ms: meta.end_time_ms.load(Ordering::Relaxed),
467 state: meta.state(),
468 lock_keys: meta.lock_key.get_keys(),
469 })
470 .collect()
471 }
472
473 fn watcher(&self, procedure_id: ProcedureId) -> Option<Watcher> {
475 let procedures = self.procedures.read().unwrap();
476 procedures
477 .get(&procedure_id)
478 .map(|meta| meta.state_receiver.clone())
479 }
480
481 fn notify_by_subprocedure(&self, procedure_id: ProcedureId) {
483 let procedures = self.procedures.read().unwrap();
484 if let Some(meta) = procedures.get(&procedure_id) {
485 meta.child_notify.notify_one();
486 }
487 }
488
489 fn load_one_procedure_from_message(
491 &self,
492 procedure_id: ProcedureId,
493 message: &ProcedureMessage,
494 ) -> Option<LoadedProcedure> {
495 let loaders = self.loaders.lock().unwrap();
496 let loader = loaders.get(&message.type_name).or_else(|| {
497 error!(
498 "Loader not found, procedure_id: {}, type_name: {}",
499 procedure_id, message.type_name
500 );
501 None
502 })?;
503
504 let procedure = loader(&message.data)
505 .map_err(|e| {
506 error!(
507 "Failed to load procedure data, key: {}, source: {:?}",
508 procedure_id, e
509 );
510 e
511 })
512 .ok()?;
513
514 Some(LoadedProcedure {
515 procedure,
516 step: message.step,
517 })
518 }
519
520 fn procedures_in_tree(&self, root: &ProcedureMetaRef) -> Vec<ProcedureId> {
525 let sub_num = root.num_children();
526 let mut procedures = Vec::with_capacity(1 + sub_num);
528
529 let mut queue = VecDeque::with_capacity(1 + sub_num);
530 queue.push_back(root.clone());
532
533 let mut children_ids = Vec::with_capacity(sub_num);
534 let mut children = Vec::with_capacity(sub_num);
535 while let Some(meta) = queue.pop_front() {
536 procedures.push(meta.id);
537
538 children_ids.clear();
540 meta.list_children(&mut children_ids);
541 self.find_procedures(&children_ids, &mut children);
542
543 for child in children.drain(..) {
545 queue.push_back(child);
546 }
547 }
548
549 procedures
550 }
551
552 fn find_procedures(&self, procedure_ids: &[ProcedureId], metas: &mut Vec<ProcedureMetaRef>) {
556 let procedures = self.procedures.read().unwrap();
557 for procedure_id in procedure_ids {
558 if let Some(meta) = procedures.get(procedure_id) {
559 metas.push(meta.clone());
560 }
561 }
562 }
563
564 fn on_procedures_finish(&self, procedure_ids: &[ProcedureId]) {
566 let now = Instant::now();
569 let mut finished_procedures = self.finished_procedures.lock().unwrap();
570 finished_procedures.extend(procedure_ids.iter().map(|id| (*id, now)));
571
572 let mut running_procedures = self.running_procedures.lock().unwrap();
574 for procedure_id in procedure_ids {
575 running_procedures.remove(procedure_id);
576 }
577 }
578
579 fn remove_outdated_meta(&self, ttl: Duration) {
581 let ids = {
582 let mut finished_procedures = self.finished_procedures.lock().unwrap();
583 if finished_procedures.is_empty() {
584 return;
585 }
586
587 let mut ids_to_remove = Vec::new();
588 while let Some((id, finish_time)) = finished_procedures.front() {
589 if finish_time.elapsed() > ttl {
590 ids_to_remove.push(*id);
591 let _ = finished_procedures.pop_front();
592 } else {
593 break;
596 }
597 }
598 ids_to_remove
599 };
600
601 if ids.is_empty() {
602 return;
603 }
604
605 let mut procedures = self.procedures.write().unwrap();
606 for id in ids {
607 let _ = procedures.remove(&id);
608 }
609 }
610}
611
612#[derive(Debug)]
614pub struct ManagerConfig {
615 pub parent_path: String,
616 pub max_retry_times: usize,
617 pub retry_delay: Duration,
618 pub remove_outdated_meta_task_interval: Duration,
619 pub remove_outdated_meta_ttl: Duration,
620 pub max_running_procedures: usize,
621}
622
623impl Default for ManagerConfig {
624 fn default() -> Self {
625 Self {
626 parent_path: String::default(),
627 max_retry_times: 3,
628 retry_delay: Duration::from_millis(500),
629 remove_outdated_meta_task_interval: Duration::from_secs(60 * 10),
630 remove_outdated_meta_ttl: META_TTL,
631 max_running_procedures: 128,
632 }
633 }
634}
635
636type PauseAwareRef = Arc<dyn PauseAware>;
637
638struct EventRecorderConfig {
639 recorder: Option<EventRecorderRef>,
640}
641
642#[derive(Clone)]
644pub struct EventRecorderHandle(Arc<Mutex<EventRecorderConfig>>);
645
646impl EventRecorderHandle {
647 fn new(recorder: Option<EventRecorderRef>) -> Self {
648 Self(Arc::new(Mutex::new(EventRecorderConfig { recorder })))
649 }
650
651 pub fn install(&self, recorder: EventRecorderRef) {
653 let mut config = self.0.lock().unwrap();
654 config.recorder = Some(recorder);
655 }
656}
657
658#[async_trait]
659pub trait PauseAware: Send + Sync {
660 async fn is_paused(&self) -> std::result::Result<bool, BoxedError>;
662}
663
664pub struct LocalManager {
666 manager_ctx: Arc<ManagerContext>,
667 procedure_store: Arc<ProcedureStore>,
668 max_retry_times: usize,
669 retry_delay: Duration,
670 remove_outdated_meta_task: TokioMutex<Option<RepeatedTask<Error>>>,
672 config: ManagerConfig,
673 pause_aware: Option<PauseAwareRef>,
674 event_recorder: EventRecorderHandle,
675}
676
677impl LocalManager {
678 pub fn new(
680 config: ManagerConfig,
681 state_store: StateStoreRef,
682 poison_store: PoisonStoreRef,
683 pause_aware: Option<PauseAwareRef>,
684 event_recorder: Option<EventRecorderRef>,
685 ) -> LocalManager {
686 let manager_ctx = Arc::new(ManagerContext::new(poison_store));
687
688 LocalManager {
689 manager_ctx,
690 procedure_store: Arc::new(ProcedureStore::new(&config.parent_path, state_store)),
691 max_retry_times: config.max_retry_times,
692 retry_delay: config.retry_delay,
693 remove_outdated_meta_task: TokioMutex::new(None),
694 config,
695 pause_aware,
696 event_recorder: EventRecorderHandle::new(event_recorder),
697 }
698 }
699
700 pub fn event_recorder_handle(&self) -> EventRecorderHandle {
702 self.event_recorder.clone()
703 }
704
705 pub fn build_remove_outdated_meta_task(&self) -> RepeatedTask<Error> {
707 RepeatedTask::new(
708 self.config.remove_outdated_meta_task_interval,
709 Box::new(RemoveOutdatedMetaFunction {
710 manager_ctx: self.manager_ctx.clone(),
711 ttl: self.config.remove_outdated_meta_ttl,
712 }),
713 )
714 }
715
716 fn submit_root(
718 &self,
719 procedure_id: ProcedureId,
720 procedure_state: ProcedureState,
721 step: u32,
722 procedure: BoxedProcedure,
723 context: ProcedureContext,
724 origin: RootSubmissionOrigin,
725 ) -> Result<Watcher> {
726 ensure!(self.manager_ctx.running(), ManagerNotStartSnafu);
727
728 let meta = Arc::new(ProcedureMeta::new(
729 procedure_id,
730 procedure_state,
731 None,
732 context,
733 procedure.lock_key(),
734 procedure.poison_keys(),
735 procedure.type_name(),
736 ));
737 let event_recorder = self.event_recorder.0.lock().unwrap();
738 let runner = Runner {
739 meta: meta.clone(),
740 procedure,
741 manager_ctx: self.manager_ctx.clone(),
742 step,
743 exponential_builder: ExponentialBuilder::default()
744 .with_min_delay(self.retry_delay)
745 .with_max_times(self.max_retry_times),
746 store: self.procedure_store.clone(),
747 rolling_back: false,
748 event_recorder: event_recorder.recorder.clone(),
749 execute_retry_attempt: 0,
750 rollback_retry_attempt: 0,
751 };
752
753 let watcher = meta.state_receiver.clone();
754
755 ensure!(
756 self.manager_ctx.num_running_procedures() < self.config.max_running_procedures,
757 TooManyRunningProceduresSnafu {
758 max_running_procedures: self.config.max_running_procedures,
759 }
760 );
761
762 ensure!(
764 self.manager_ctx.try_insert_procedure(meta),
765 DuplicateProcedureSnafu { procedure_id },
766 );
767
768 let initial_event = runner.build_event(origin.event_trigger());
769
770 let tracing_context = TracingContext::from_current_span();
771
772 ensure!(
773 self.manager_ctx.spawn_runner_task(procedure_id, || {
774 common_runtime::spawn_global(async move {
775 let span = tracing_context.attach(tracing::info_span!(
776 "LocalManager::submit_root_procedure",
777 procedure_name = %runner.meta.type_name,
778 procedure_id = %runner.meta.id,
779 ));
780 runner.run().trace(span).await;
784 })
785 }),
786 {
787 self.manager_ctx.remove_procedure(procedure_id);
788 ManagerNotStartSnafu
789 }
790 );
791
792 if let Some(event) = initial_event
793 && let Some(recorder) = event_recorder.recorder.as_ref()
794 {
795 recorder.record(Box::new(event));
796 }
797
798 Ok(watcher)
799 }
800
801 fn submit_recovered_messages(
802 &self,
803 messages: HashMap<ProcedureId, ProcedureMessage>,
804 init_state: InitProcedureState,
805 ) {
806 for (procedure_id, message) in &messages {
807 if message.parent_id.is_none() {
808 let Some(mut loaded_procedure) = self
811 .manager_ctx
812 .load_one_procedure_from_message(*procedure_id, message)
813 else {
814 continue;
816 };
817
818 info!(
819 "Recover root procedure {}-{}, step: {}",
820 loaded_procedure.procedure.type_name(),
821 procedure_id,
822 loaded_procedure.step
823 );
824
825 let procedure_state = match init_state {
826 InitProcedureState::RollingBack => ProcedureState::RollingBack {
827 error: Arc::new(
828 error::RollbackProcedureRecoveredSnafu {
829 error: message.error.clone().unwrap_or("Unknown error".to_string()),
830 }
831 .build(),
832 ),
833 },
834 InitProcedureState::Running => ProcedureState::Running,
835 };
836
837 if let Err(e) = loaded_procedure.procedure.recover() {
838 error!(e; "Failed to recover procedure {}", procedure_id);
839 }
840
841 if let Err(e) = self.submit_root(
842 *procedure_id,
843 procedure_state,
844 loaded_procedure.step,
845 loaded_procedure.procedure,
846 message.context.clone(),
847 RootSubmissionOrigin::Recovery,
848 ) {
849 error!(e; "Failed to recover procedure {}", procedure_id);
850 }
851 }
852 }
853 }
854
855 async fn recover(&self) -> Result<()> {
857 info!("LocalManager start to recover");
858 let recover_start = Instant::now();
859
860 let ProcedureMessages {
861 messages,
862 rollback_messages,
863 finished_ids,
864 } = self.procedure_store.load_messages().await?;
865 self.submit_recovered_messages(rollback_messages, InitProcedureState::RollingBack);
867 self.submit_recovered_messages(messages, InitProcedureState::Running);
868
869 if !finished_ids.is_empty() {
870 info!(
871 "LocalManager try to clean finished procedures, num: {}",
872 finished_ids.len()
873 );
874
875 for procedure_id in finished_ids {
876 if let Err(e) = self.procedure_store.delete_procedure(procedure_id).await {
877 error!(e; "Failed to delete procedure {}", procedure_id);
878 }
879 }
880 }
881
882 info!(
883 "LocalManager finish recovery, cost: {}ms",
884 recover_start.elapsed().as_millis()
885 );
886
887 Ok(())
888 }
889
890 #[cfg(any(test, feature = "testing"))]
891 pub fn contains_loader(&self, name: &str) -> bool {
893 let loaders = self.manager_ctx.loaders.lock().unwrap();
894 loaders.contains_key(name)
895 }
896
897 async fn check_status(&self) -> Result<()> {
898 if let Some(pause_aware) = self.pause_aware.as_ref() {
899 ensure!(
900 !pause_aware.is_paused().await.context(CheckStatusSnafu)?,
901 ManagerPasuedSnafu
902 );
903 }
904
905 Ok(())
906 }
907}
908
909#[async_trait]
910impl ProcedureManager for LocalManager {
911 fn register_loader(&self, name: &str, loader: BoxedProcedureLoader) -> Result<()> {
912 let mut loaders = self.manager_ctx.loaders.lock().unwrap();
913 ensure!(!loaders.contains_key(name), LoaderConflictSnafu { name });
914
915 let _ = loaders.insert(name.to_string(), loader);
916
917 Ok(())
918 }
919
920 async fn start(&self) -> Result<()> {
921 let mut task = self.remove_outdated_meta_task.lock().await;
922
923 if task.is_some() {
924 return Ok(());
925 }
926
927 let task_inner = self.build_remove_outdated_meta_task();
928
929 task_inner
930 .start(common_runtime::global_runtime())
931 .context(StartRemoveOutdatedMetaTaskSnafu)?;
932
933 *task = Some(task_inner);
934
935 self.manager_ctx.reset_runtime_state();
936 self.manager_ctx.start();
937
938 info!("LocalManager is start.");
939
940 self.recover().await
941 }
942
943 async fn stop(&self) -> Result<()> {
944 self.manager_ctx.stop();
945
946 let mut task = self.remove_outdated_meta_task.lock().await;
947 if let Some(task) = task.take()
948 && let Err(e) = task.stop().await.context(StopRemoveOutdatedMetaTaskSnafu)
949 {
950 error!(e; "Failed to stop remove outdated meta task");
951 };
952
953 self.manager_ctx.abort_runner_tasks().await;
954 self.manager_ctx.reset_runtime_state();
955
956 info!("LocalManager is stopped.");
957
958 Ok(())
959 }
960
961 async fn submit(&self, procedure: ProcedureWithId) -> Result<Watcher> {
962 let procedure_id = procedure.id;
963 ensure!(
964 !self.manager_ctx.contains_procedure(procedure_id),
965 DuplicateProcedureSnafu { procedure_id }
966 );
967 self.check_status().await?;
968
969 self.submit_root(
970 procedure.id,
971 ProcedureState::Running,
972 0,
973 procedure.procedure,
974 procedure.context,
975 RootSubmissionOrigin::Fresh,
976 )
977 }
978
979 async fn procedure_state(&self, procedure_id: ProcedureId) -> Result<Option<ProcedureState>> {
980 Ok(self.manager_ctx.state(procedure_id))
981 }
982
983 fn procedure_watcher(&self, procedure_id: ProcedureId) -> Option<Watcher> {
984 self.manager_ctx.watcher(procedure_id)
985 }
986
987 async fn list_procedures(&self) -> Result<Vec<ProcedureInfo>> {
988 Ok(self.manager_ctx.list_procedure())
989 }
990
991 async fn has_unfinished_procedure(&self, type_names: &[&str]) -> Result<bool> {
992 let messages = self.procedure_store.load_messages().await?;
993 Ok(messages
994 .messages
995 .values()
996 .chain(messages.rollback_messages.values())
997 .any(|message| type_names.contains(&message.type_name.as_str())))
998 }
999}
1000
1001struct RemoveOutdatedMetaFunction {
1002 manager_ctx: Arc<ManagerContext>,
1003 ttl: Duration,
1004}
1005
1006#[async_trait::async_trait]
1007impl TaskFunction<Error> for RemoveOutdatedMetaFunction {
1008 fn name(&self) -> &str {
1009 "ProcedureManager-remove-outdated-meta-task"
1010 }
1011
1012 async fn call(&mut self) -> Result<()> {
1013 self.manager_ctx.remove_outdated_meta(self.ttl);
1014 Ok(())
1015 }
1016}
1017
1018#[cfg(test)]
1020pub(crate) mod test_util {
1021 use common_test_util::temp_dir::TempDir;
1022 use object_store::ObjectStore;
1023 use object_store::services::Fs as Builder;
1024
1025 use super::*;
1026
1027 pub(crate) fn procedure_meta_for_test() -> ProcedureMeta {
1028 ProcedureMeta::new(
1029 ProcedureId::random(),
1030 ProcedureState::Running,
1031 None,
1032 ProcedureContext::default(),
1033 LockKey::default(),
1034 PoisonKeys::default(),
1035 "ProcedureAdapter",
1036 )
1037 }
1038
1039 pub(crate) fn new_object_store(dir: &TempDir) -> ObjectStore {
1040 let store_dir = dir.path().to_str().unwrap();
1041 let builder = Builder::default();
1042 ObjectStore::new(builder.root(store_dir)).unwrap()
1043 }
1044}
1045
1046#[cfg(test)]
1047mod tests {
1048 use std::assert_matches;
1049 use std::collections::HashSet;
1050 use std::sync::Mutex;
1051 use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
1052
1053 use common_error::mock::MockError;
1054 use common_error::status_code::StatusCode;
1055 use common_event_recorder::{
1056 Event, EventRecorder, EventTypeFilter, EventTypeFilterRef, PersistentEventContext,
1057 TriggerReason,
1058 };
1059 use common_test_util::temp_dir::create_temp_dir;
1060 use tokio::sync::oneshot;
1061 use tokio::time::{sleep, timeout};
1062
1063 use super::*;
1064 use crate::error::{self, Error};
1065 use crate::store::state_store::{ObjectStateStore, StateStore};
1066 use crate::test_util::InMemoryPoisonStore;
1067 use crate::{
1068 ChildSubmissionOutcome, Context, EventContext, EventTrigger, Procedure, ProcedureEvent,
1069 Status,
1070 };
1071
1072 fn new_test_manager_context() -> ManagerContext {
1073 let poison_manager = Arc::new(InMemoryPoisonStore::default());
1074 ManagerContext::new(poison_manager)
1075 }
1076
1077 #[derive(Debug)]
1078 struct CapturingEventRecorder {
1079 events: Mutex<Vec<Box<dyn Event>>>,
1080 event_type_filter: EventTypeFilterRef,
1081 }
1082
1083 impl CapturingEventRecorder {
1084 fn with_event_type_filter(event_type_filter: EventTypeFilterRef) -> Self {
1085 Self {
1086 events: Mutex::new(vec![]),
1087 event_type_filter,
1088 }
1089 }
1090
1091 fn triggers(&self) -> Vec<EventTrigger> {
1092 self.events
1093 .lock()
1094 .unwrap()
1095 .iter()
1096 .map(|event| {
1097 event
1098 .as_any()
1099 .downcast_ref::<ProcedureEvent>()
1100 .unwrap()
1101 .trigger
1102 .clone()
1103 })
1104 .collect()
1105 }
1106
1107 fn procedure_events(&self) -> Vec<(ProcedureId, EventTrigger)> {
1108 self.events
1109 .lock()
1110 .unwrap()
1111 .iter()
1112 .map(|event| {
1113 let event = event.as_any().downcast_ref::<ProcedureEvent>().unwrap();
1114 (event.procedure_id, event.trigger.clone())
1115 })
1116 .collect()
1117 }
1118
1119 fn procedure_context(&self, procedure_id: ProcedureId) -> Option<ProcedureContext> {
1120 self.events.lock().unwrap().iter().find_map(|event| {
1121 let event = event.as_any().downcast_ref::<ProcedureEvent>().unwrap();
1122 (event.procedure_id == procedure_id).then(|| event.context.clone())
1123 })
1124 }
1125 }
1126
1127 impl EventRecorder for CapturingEventRecorder {
1128 fn record(&self, event: Box<dyn Event>) {
1129 self.events.lock().unwrap().push(event);
1130 }
1131
1132 fn event_type_filter(&self) -> EventTypeFilterRef {
1133 self.event_type_filter.clone()
1134 }
1135
1136 fn close(&self) {}
1137 }
1138
1139 impl Default for CapturingEventRecorder {
1140 fn default() -> Self {
1141 Self::with_event_type_filter(Arc::new(EventTypeFilter::All))
1142 }
1143 }
1144
1145 async fn wait_for_trigger(event_recorder: &CapturingEventRecorder, trigger: EventTrigger) {
1146 timeout(Duration::from_secs(1), async {
1147 while !event_recorder.triggers().contains(&trigger) {
1148 sleep(Duration::from_millis(10)).await;
1149 }
1150 })
1151 .await
1152 .unwrap();
1153 }
1154
1155 #[derive(Debug)]
1156 struct TestProcedureEvent;
1157
1158 impl Event for TestProcedureEvent {
1159 fn event_type(&self) -> &str {
1160 "test_procedure"
1161 }
1162
1163 fn as_any(&self) -> &dyn std::any::Any {
1164 self
1165 }
1166 }
1167
1168 #[test]
1169 fn test_manager_context() {
1170 let ctx = new_test_manager_context();
1171 let meta = Arc::new(test_util::procedure_meta_for_test());
1172
1173 assert!(!ctx.contains_procedure(meta.id));
1174 assert!(ctx.state(meta.id).is_none());
1175
1176 assert!(ctx.try_insert_procedure(meta.clone()));
1177 assert!(ctx.contains_procedure(meta.id));
1178
1179 assert!(ctx.state(meta.id).unwrap().is_running());
1180 meta.set_state(ProcedureState::Done { output: None });
1181 assert!(ctx.state(meta.id).unwrap().is_done());
1182 }
1183
1184 #[test]
1185 fn test_reset_runtime_state() {
1186 let ctx = new_test_manager_context();
1187 ctx.set_running();
1188 let mut meta = test_util::procedure_meta_for_test();
1189 meta.lock_key = LockKey::single_exclusive("test.reset_runtime_state");
1190 let meta = Arc::new(meta);
1191 let procedure_id = meta.id;
1192
1193 assert!(ctx.try_insert_procedure(meta.clone()));
1194 ctx.finished_procedures
1195 .lock()
1196 .unwrap()
1197 .push_back((procedure_id, Instant::now()));
1198 ctx.spawn_runner_task(procedure_id, || {
1199 common_runtime::spawn_global(std::future::pending::<()>())
1200 });
1201
1202 drop(
1203 ctx.key_lock
1204 .try_write("test.reset_runtime_state".to_string()),
1205 );
1206 drop(
1207 ctx.dynamic_key_lock
1208 .try_write("test.reset_runtime_state.dynamic".to_string()),
1209 );
1210 assert!(ctx.contains_procedure(procedure_id));
1211 assert_eq!(1, ctx.running_procedures.lock().unwrap().len());
1212 assert_eq!(1, ctx.finished_procedures.lock().unwrap().len());
1213 assert_eq!(1, ctx.runner_tasks.lock().unwrap().len());
1214 assert_eq!(1, ctx.key_lock.len());
1215 assert_eq!(1, ctx.dynamic_key_lock.len());
1216
1217 ctx.reset_runtime_state();
1218
1219 assert!(!ctx.contains_procedure(procedure_id));
1220 assert!(ctx.running_procedures.lock().unwrap().is_empty());
1221 assert!(ctx.finished_procedures.lock().unwrap().is_empty());
1222 assert!(ctx.runner_tasks.lock().unwrap().is_empty());
1223 assert!(ctx.key_lock.is_empty());
1224 assert!(ctx.dynamic_key_lock.is_empty());
1225 }
1226
1227 #[test]
1228 fn test_spawn_runner_task_not_started_after_stop() {
1229 let ctx = new_test_manager_context();
1230 let procedure_id = ProcedureId::random();
1231
1232 let spawned = Arc::new(AtomicBool::new(false));
1233 let spawned_in_task = spawned.clone();
1234 let started = ctx.spawn_runner_task(procedure_id, || {
1235 common_runtime::spawn_global(async move {
1236 spawned_in_task.store(true, AtomicOrdering::Relaxed);
1237 })
1238 });
1239
1240 assert!(!started);
1241 assert!(!spawned.load(AtomicOrdering::Relaxed));
1242 assert!(ctx.runner_tasks.lock().unwrap().is_empty());
1243 }
1244
1245 #[test]
1246 fn test_manager_context_insert_duplicate() {
1247 let ctx = new_test_manager_context();
1248 let meta = Arc::new(test_util::procedure_meta_for_test());
1249
1250 assert!(ctx.try_insert_procedure(meta.clone()));
1251 assert!(!ctx.try_insert_procedure(meta));
1252 }
1253
1254 fn new_child(parent_id: ProcedureId, ctx: &ManagerContext) -> ProcedureMetaRef {
1255 let mut child = test_util::procedure_meta_for_test();
1256 child.parent_id = Some(parent_id);
1257 let child = Arc::new(child);
1258 assert!(ctx.try_insert_procedure(child.clone()));
1259
1260 let mut parent = Vec::new();
1261 ctx.find_procedures(&[parent_id], &mut parent);
1262 parent[0].push_child(child.id);
1263
1264 child
1265 }
1266
1267 #[test]
1268 fn test_procedures_in_tree() {
1269 let ctx = new_test_manager_context();
1270 let root = Arc::new(test_util::procedure_meta_for_test());
1271 assert!(ctx.try_insert_procedure(root.clone()));
1272
1273 assert_eq!(1, ctx.procedures_in_tree(&root).len());
1274
1275 let child1 = new_child(root.id, &ctx);
1276 let child2 = new_child(root.id, &ctx);
1277
1278 let child3 = new_child(child1.id, &ctx);
1279 let child4 = new_child(child1.id, &ctx);
1280
1281 let child5 = new_child(child2.id, &ctx);
1282
1283 let expect = vec![
1284 root.id, child1.id, child2.id, child3.id, child4.id, child5.id,
1285 ];
1286 assert_eq!(expect, ctx.procedures_in_tree(&root));
1287 }
1288
1289 #[derive(Debug)]
1290 struct ProcedureToLoad {
1291 content: String,
1292 lock_key: LockKey,
1293 poison_keys: PoisonKeys,
1294 }
1295
1296 #[async_trait]
1297 impl Procedure for ProcedureToLoad {
1298 fn type_name(&self) -> &str {
1299 "ProcedureToLoad"
1300 }
1301
1302 async fn execute(&mut self, _ctx: &Context) -> Result<Status> {
1303 Ok(Status::done())
1304 }
1305
1306 fn dump(&self) -> Result<String> {
1307 Ok(self.content.clone())
1308 }
1309
1310 fn lock_key(&self) -> LockKey {
1311 self.lock_key.clone()
1312 }
1313
1314 fn poison_keys(&self) -> PoisonKeys {
1315 self.poison_keys.clone()
1316 }
1317
1318 fn event(&self, _ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
1319 Some(Box::new(TestProcedureEvent))
1320 }
1321 }
1322
1323 impl ProcedureToLoad {
1324 fn new(content: &str) -> ProcedureToLoad {
1325 ProcedureToLoad {
1326 content: content.to_string(),
1327 lock_key: LockKey::default(),
1328 poison_keys: PoisonKeys::default(),
1329 }
1330 }
1331
1332 fn loader() -> BoxedProcedureLoader {
1333 let f = |json: &str| {
1334 let procedure = ProcedureToLoad::new(json);
1335 Ok(Box::new(procedure) as _)
1336 };
1337 Box::new(f)
1338 }
1339 }
1340
1341 #[derive(Debug)]
1342 struct ParentWithChildProcedure {
1343 child_id: ProcedureId,
1344 child_submitted: bool,
1345 }
1346
1347 #[async_trait]
1348 impl Procedure for ParentWithChildProcedure {
1349 fn type_name(&self) -> &str {
1350 "ParentWithChildProcedure"
1351 }
1352
1353 async fn execute(&mut self, _ctx: &Context) -> Result<Status> {
1354 if self.child_submitted {
1355 return Ok(Status::done());
1356 }
1357
1358 self.child_submitted = true;
1359 Ok(Status::suspended(
1360 vec![ProcedureWithId {
1361 id: self.child_id,
1362 procedure: Box::new(ProcedureToLoad::new("child")),
1363 context: ProcedureContext::default(),
1364 }],
1365 false,
1366 ))
1367 }
1368
1369 fn dump(&self) -> Result<String> {
1370 Ok(String::new())
1371 }
1372
1373 fn lock_key(&self) -> LockKey {
1374 LockKey::default()
1375 }
1376
1377 fn event(&self, _ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
1378 Some(Box::new(TestProcedureEvent))
1379 }
1380 }
1381
1382 struct ParentWithBlockedChildProcedure {
1383 child_id: ProcedureId,
1384 ready: Option<oneshot::Sender<()>>,
1385 proceed: Option<oneshot::Receiver<()>>,
1386 child_submitted: bool,
1387 }
1388
1389 #[async_trait]
1390 impl Procedure for ParentWithBlockedChildProcedure {
1391 fn type_name(&self) -> &str {
1392 "ParentWithBlockedChildProcedure"
1393 }
1394
1395 async fn execute(&mut self, _ctx: &Context) -> Result<Status> {
1396 if self.child_submitted {
1397 return Ok(Status::done());
1398 }
1399
1400 self.child_submitted = true;
1401 self.ready.take().unwrap().send(()).unwrap();
1402 self.proceed.take().unwrap().await.unwrap();
1403 Ok(Status::suspended(
1404 vec![ProcedureWithId {
1405 id: self.child_id,
1406 procedure: Box::new(ProcedureToLoad::new("child")),
1407 context: ProcedureContext::default(),
1408 }],
1409 false,
1410 ))
1411 }
1412
1413 fn dump(&self) -> Result<String> {
1414 Ok(String::new())
1415 }
1416
1417 fn lock_key(&self) -> LockKey {
1418 LockKey::default()
1419 }
1420
1421 fn event(&self, _ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
1422 Some(Box::new(TestProcedureEvent))
1423 }
1424 }
1425
1426 struct FilterCapturingProcedure {
1427 captured_filter: Arc<Mutex<Option<EventTypeFilterRef>>>,
1428 }
1429
1430 #[async_trait]
1431 impl Procedure for FilterCapturingProcedure {
1432 fn type_name(&self) -> &str {
1433 "FilterCapturingProcedure"
1434 }
1435
1436 async fn execute(&mut self, _: &Context) -> Result<Status> {
1437 Ok(Status::done())
1438 }
1439
1440 fn dump(&self) -> Result<String> {
1441 Ok(String::new())
1442 }
1443
1444 fn lock_key(&self) -> LockKey {
1445 LockKey::default()
1446 }
1447
1448 fn event(&self, ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
1449 *self.captured_filter.lock().unwrap() = Some(ctx.event_type_filter.clone());
1450 Some(Box::new(TestProcedureEvent))
1451 }
1452 }
1453
1454 #[tokio::test]
1455 async fn test_event_filter_is_shared_with_procedure_context() {
1456 let dir = create_temp_dir("shared_event_filter");
1457 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
1458 let poison_manager = Arc::new(InMemoryPoisonStore::new());
1459 let event_type_filter = Arc::new(EventTypeFilter::Only(HashSet::from([String::from(
1460 "test_procedure",
1461 )])));
1462 let event_recorder = Arc::new(CapturingEventRecorder::with_event_type_filter(
1463 event_type_filter.clone(),
1464 ));
1465 let captured_filter = Arc::new(Mutex::new(None));
1466 let manager = LocalManager::new(
1467 ManagerConfig::default(),
1468 state_store,
1469 poison_manager,
1470 None,
1471 None,
1472 );
1473 manager.event_recorder_handle().install(event_recorder);
1474 manager.manager_ctx.start();
1475
1476 manager
1477 .submit(ProcedureWithId {
1478 id: ProcedureId::random(),
1479 procedure: Box::new(FilterCapturingProcedure {
1480 captured_filter: captured_filter.clone(),
1481 }),
1482 context: ProcedureContext::default(),
1483 })
1484 .await
1485 .unwrap();
1486
1487 let captured_filter = timeout(Duration::from_secs(1), async {
1488 loop {
1489 if let Some(captured_filter) = captured_filter.lock().unwrap().clone() {
1490 return captured_filter;
1491 }
1492 sleep(Duration::from_millis(10)).await;
1493 }
1494 })
1495 .await
1496 .unwrap();
1497 assert!(Arc::ptr_eq(&event_type_filter, &captured_filter));
1498 assert!(captured_filter.allows("test_procedure"));
1499 assert!(!captured_filter.allows("other_procedure"));
1500 }
1501
1502 #[tokio::test]
1503 async fn test_fresh_submission_emits_submitted_event() {
1504 let dir = create_temp_dir("fresh_submission_event");
1505 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
1506 let poison_manager = Arc::new(InMemoryPoisonStore::new());
1507 let event_recorder = Arc::new(CapturingEventRecorder::default());
1508 let manager = LocalManager::new(
1509 ManagerConfig::default(),
1510 state_store,
1511 poison_manager,
1512 None,
1513 None,
1514 );
1515 manager
1516 .event_recorder_handle()
1517 .install(event_recorder.clone());
1518 manager.manager_ctx.start();
1519
1520 manager
1521 .submit(ProcedureWithId {
1522 id: ProcedureId::random(),
1523 procedure: Box::new(ProcedureToLoad::new("fresh submission")),
1524 context: ProcedureContext::default(),
1525 })
1526 .await
1527 .unwrap();
1528
1529 wait_for_trigger(&event_recorder, EventTrigger::Submitted).await;
1530 }
1531
1532 #[test]
1533 fn test_submit_root_does_not_record_event_when_runner_is_not_spawned() {
1534 let dir = create_temp_dir("submit_root_without_runner");
1535 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
1536 let poison_manager = Arc::new(InMemoryPoisonStore::new());
1537 let event_recorder = Arc::new(CapturingEventRecorder::default());
1538 let manager = LocalManager::new(
1539 ManagerConfig::default(),
1540 state_store,
1541 poison_manager,
1542 None,
1543 None,
1544 );
1545 manager
1546 .event_recorder_handle()
1547 .install(event_recorder.clone());
1548 manager.manager_ctx.start();
1549
1550 let procedure_id = ProcedureId::random();
1551 let runner_tasks = manager.manager_ctx.runner_tasks.lock().unwrap();
1552 std::thread::scope(|scope| {
1553 let handle = scope.spawn(|| {
1554 manager.submit_root(
1555 procedure_id,
1556 ProcedureState::Running,
1557 0,
1558 Box::new(ProcedureToLoad::new("submit root without runner")),
1559 ProcedureContext::default(),
1560 RootSubmissionOrigin::Fresh,
1561 )
1562 });
1563
1564 let deadline = Instant::now() + Duration::from_secs(1);
1565 while !manager.manager_ctx.contains_procedure(procedure_id) {
1566 assert!(
1567 Instant::now() < deadline,
1568 "procedure metadata was not inserted"
1569 );
1570 std::thread::sleep(Duration::from_millis(10));
1571 }
1572
1573 manager.manager_ctx.stop();
1574 drop(runner_tasks);
1575
1576 let err = handle.join().unwrap().unwrap_err();
1577 assert_matches!(err, Error::ManagerNotStart { .. });
1578 });
1579
1580 assert!(!manager.manager_ctx.contains_procedure(procedure_id));
1581 assert!(!event_recorder.triggers().contains(&EventTrigger::Submitted));
1582 }
1583
1584 #[tokio::test]
1585 async fn test_child_submission_emits_submitted_event_for_child() {
1586 let dir = create_temp_dir("child_submission_event");
1587 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
1588 let poison_manager = Arc::new(InMemoryPoisonStore::new());
1589 let event_recorder = Arc::new(CapturingEventRecorder::default());
1590 let manager = LocalManager::new(
1591 ManagerConfig::default(),
1592 state_store,
1593 poison_manager,
1594 None,
1595 None,
1596 );
1597 manager
1598 .event_recorder_handle()
1599 .install(event_recorder.clone());
1600 manager.manager_ctx.start();
1601
1602 let parent_id = ProcedureId::random();
1603 let child_id = ProcedureId::random();
1604 let mut watcher = manager
1605 .submit(ProcedureWithId {
1606 id: parent_id,
1607 procedure: Box::new(ParentWithChildProcedure {
1608 child_id,
1609 child_submitted: false,
1610 }),
1611 context: ProcedureContext::from_event_context(PersistentEventContext::new(
1612 TriggerReason::AutoRepartition,
1613 )),
1614 })
1615 .await
1616 .unwrap();
1617 timeout(Duration::from_secs(1), async {
1618 while !watcher.borrow().is_done() {
1619 watcher.changed().await.unwrap();
1620 }
1621 })
1622 .await
1623 .unwrap();
1624
1625 let procedure_events = event_recorder.procedure_events();
1626 assert!(procedure_events.contains(&(
1627 parent_id,
1628 EventTrigger::ChildSubmitted {
1629 procedure_id: child_id,
1630 outcome: ChildSubmissionOutcome::Accepted,
1631 },
1632 )));
1633 assert!(procedure_events.contains(&(child_id, EventTrigger::Submitted)));
1634 assert_eq!(
1635 event_recorder
1636 .procedure_context(child_id)
1637 .unwrap()
1638 .event_context
1639 .unwrap()
1640 .reason,
1641 TriggerReason::AutoRepartition
1642 );
1643 }
1644
1645 #[tokio::test]
1646 async fn test_child_submission_does_not_emit_event_when_spawn_fails() {
1647 let dir = create_temp_dir("child_submission_spawn_failed_event");
1648 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
1649 let poison_manager = Arc::new(InMemoryPoisonStore::new());
1650 let event_recorder = Arc::new(CapturingEventRecorder::default());
1651 let manager = LocalManager::new(
1652 ManagerConfig::default(),
1653 state_store,
1654 poison_manager,
1655 None,
1656 None,
1657 );
1658 manager
1659 .event_recorder_handle()
1660 .install(event_recorder.clone());
1661 manager.manager_ctx.start();
1662
1663 let parent_id = ProcedureId::random();
1664 let child_id = ProcedureId::random();
1665 let (ready_tx, ready_rx) = oneshot::channel();
1666 let (proceed_tx, proceed_rx) = oneshot::channel();
1667 manager
1668 .submit(ProcedureWithId {
1669 id: parent_id,
1670 procedure: Box::new(ParentWithBlockedChildProcedure {
1671 child_id,
1672 ready: Some(ready_tx),
1673 proceed: Some(proceed_rx),
1674 child_submitted: false,
1675 }),
1676 context: ProcedureContext::default(),
1677 })
1678 .await
1679 .unwrap();
1680
1681 timeout(Duration::from_secs(1), ready_rx)
1682 .await
1683 .unwrap()
1684 .unwrap();
1685 let runner_tasks = manager.manager_ctx.runner_tasks.lock().unwrap();
1686 proceed_tx.send(()).unwrap();
1687 let deadline = Instant::now() + Duration::from_secs(1);
1688 while !manager.manager_ctx.contains_procedure(child_id) {
1689 assert!(
1690 Instant::now() < deadline,
1691 "child procedure was not inserted"
1692 );
1693 std::thread::yield_now();
1694 }
1695
1696 manager.manager_ctx.stop();
1697 drop(runner_tasks);
1698
1699 timeout(Duration::from_secs(1), async {
1700 while !event_recorder.procedure_events().contains(&(
1701 parent_id,
1702 EventTrigger::ChildSubmitted {
1703 procedure_id: child_id,
1704 outcome: ChildSubmissionOutcome::SpawnFailed,
1705 },
1706 )) {
1707 tokio::task::yield_now().await;
1708 }
1709 })
1710 .await
1711 .unwrap();
1712
1713 assert!(!manager.manager_ctx.contains_procedure(child_id));
1714 assert!(
1715 !event_recorder
1716 .procedure_events()
1717 .contains(&(child_id, EventTrigger::Submitted))
1718 );
1719
1720 manager.stop().await.unwrap();
1721 }
1722
1723 #[tokio::test]
1724 async fn test_recovery_loads_legacy_procedure_message() {
1725 let dir = create_temp_dir("legacy_procedure_message_recovery");
1726 let object_store = test_util::new_object_store(&dir);
1727 let state_store = Arc::new(ObjectStateStore::new(object_store));
1728 let poison_manager = Arc::new(InMemoryPoisonStore::new());
1729 let event_recorder = Arc::new(CapturingEventRecorder::default());
1730 let manager = LocalManager::new(
1731 ManagerConfig {
1732 parent_path: "data/".to_string(),
1733 ..Default::default()
1734 },
1735 state_store.clone(),
1736 poison_manager,
1737 None,
1738 None,
1739 );
1740 manager
1741 .event_recorder_handle()
1742 .install(event_recorder.clone());
1743 manager.manager_ctx.start();
1744 manager
1745 .register_loader("ProcedureToLoad", ProcedureToLoad::loader())
1746 .unwrap();
1747
1748 let procedure = ProcedureToLoad::new("legacy recovered submission");
1749 let procedure_id = ProcedureId::random();
1750 let key = format!("data/procedure/{procedure_id}/0000000000.step");
1751 let legacy_message = serde_json::json!({
1753 "type_name": procedure.type_name(),
1754 "data": procedure.dump().unwrap(),
1755 "parent_id": null,
1756 "step": 0,
1757 });
1758 state_store
1759 .put(&key, serde_json::to_vec(&legacy_message).unwrap())
1760 .await
1761 .unwrap();
1762
1763 manager.recover().await.unwrap();
1764
1765 assert!(
1766 manager
1767 .procedure_state(procedure_id)
1768 .await
1769 .unwrap()
1770 .is_some()
1771 );
1772 wait_for_trigger(&event_recorder, EventTrigger::Recovered).await;
1773 assert_eq!(
1774 event_recorder.procedure_context(procedure_id),
1775 Some(ProcedureContext::default())
1776 );
1777 }
1778
1779 #[tokio::test]
1780 async fn test_recovery_preserves_procedure_context() {
1781 let dir = create_temp_dir("recovery_submission_event");
1782 let object_store = test_util::new_object_store(&dir);
1783 let state_store = Arc::new(ObjectStateStore::new(object_store.clone()));
1784 let poison_manager = Arc::new(InMemoryPoisonStore::new());
1785 let event_recorder = Arc::new(CapturingEventRecorder::default());
1786 let manager = LocalManager::new(
1787 ManagerConfig {
1788 parent_path: "data/".to_string(),
1789 ..Default::default()
1790 },
1791 state_store,
1792 poison_manager,
1793 None,
1794 None,
1795 );
1796 manager
1797 .event_recorder_handle()
1798 .install(event_recorder.clone());
1799 manager.manager_ctx.start();
1800 manager
1801 .register_loader("ProcedureToLoad", ProcedureToLoad::loader())
1802 .unwrap();
1803
1804 let procedure = ProcedureToLoad::new("recovered submission");
1805 let procedure_id = ProcedureId::random();
1806 let mut event_context =
1807 PersistentEventContext::new(TriggerReason::AutoRebalance).with_protocol("mysql");
1808 event_context.extensions.insert(
1809 "source".to_string(),
1810 serde_json::Value::String("test".to_string()),
1811 );
1812 let procedure_context = ProcedureContext {
1813 actor: Some("alice".to_string()),
1814 event_context: Some(event_context),
1815 };
1816 ProcedureStore::from_object_store(object_store)
1817 .store_procedure_with_context(
1818 procedure_id,
1819 0,
1820 procedure.type_name().to_string(),
1821 procedure.dump().unwrap(),
1822 None,
1823 procedure_context.clone(),
1824 )
1825 .await
1826 .unwrap();
1827
1828 manager.recover().await.unwrap();
1829
1830 assert!(
1831 manager
1832 .procedure_state(procedure_id)
1833 .await
1834 .unwrap()
1835 .is_some()
1836 );
1837 wait_for_trigger(&event_recorder, EventTrigger::Recovered).await;
1838 assert!(!event_recorder.triggers().contains(&EventTrigger::Submitted));
1839 assert_eq!(
1840 event_recorder.procedure_context(procedure_id),
1841 Some(procedure_context)
1842 );
1843 }
1844
1845 #[tokio::test]
1846 async fn test_event_recorder_handle_installs_after_start() {
1847 let dir = create_temp_dir("set_event_recorder_after_start");
1848 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
1849 let poison_manager = Arc::new(InMemoryPoisonStore::new());
1850 let manager = LocalManager::new(
1851 ManagerConfig::default(),
1852 state_store,
1853 poison_manager,
1854 None,
1855 None,
1856 );
1857
1858 manager.start().await.unwrap();
1859
1860 manager
1861 .event_recorder_handle()
1862 .install(Arc::new(CapturingEventRecorder::default()));
1863
1864 manager.stop().await.unwrap();
1865 }
1866
1867 #[derive(Debug)]
1868 struct BlockingProcedure {
1869 started_tx: Option<oneshot::Sender<()>>,
1870 dropped: Arc<AtomicBool>,
1871 lock_key: LockKey,
1872 }
1873
1874 impl Drop for BlockingProcedure {
1875 fn drop(&mut self) {
1876 self.dropped.store(true, AtomicOrdering::Relaxed);
1877 }
1878 }
1879
1880 #[async_trait]
1881 impl Procedure for BlockingProcedure {
1882 fn type_name(&self) -> &str {
1883 "BlockingProcedure"
1884 }
1885
1886 async fn execute(&mut self, _ctx: &Context) -> Result<Status> {
1887 if let Some(tx) = self.started_tx.take() {
1888 let _ = tx.send(());
1889 }
1890 std::future::pending::<Result<Status>>().await
1891 }
1892
1893 fn dump(&self) -> Result<String> {
1894 Ok(String::new())
1895 }
1896
1897 fn lock_key(&self) -> LockKey {
1898 self.lock_key.clone()
1899 }
1900 }
1901
1902 #[tokio::test]
1903 async fn test_stop_aborts_runner_and_resets_runtime_state() {
1904 let dir = create_temp_dir("stop_aborts_runner_and_resets_runtime_state");
1905 let config = ManagerConfig::default();
1906 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
1907 let poison_manager = Arc::new(InMemoryPoisonStore::new());
1908 let manager = LocalManager::new(config, state_store, poison_manager, None, None);
1909 manager.start().await.unwrap();
1910
1911 let procedure_id = ProcedureId::random();
1912 let (started_tx, started_rx) = oneshot::channel();
1913 let dropped = Arc::new(AtomicBool::new(false));
1914 let procedure = BlockingProcedure {
1915 started_tx: Some(started_tx),
1916 dropped: dropped.clone(),
1917 lock_key: LockKey::single_exclusive("test.stop_aborts_runner"),
1918 };
1919
1920 manager
1921 .submit(ProcedureWithId {
1922 id: procedure_id,
1923 procedure: Box::new(procedure),
1924 context: ProcedureContext::default(),
1925 })
1926 .await
1927 .unwrap();
1928 timeout(Duration::from_secs(5), started_rx)
1929 .await
1930 .unwrap()
1931 .unwrap();
1932
1933 assert!(manager.manager_ctx.contains_procedure(procedure_id));
1934 assert_eq!(
1935 1,
1936 manager.manager_ctx.running_procedures.lock().unwrap().len()
1937 );
1938 assert_eq!(1, manager.manager_ctx.runner_tasks.lock().unwrap().len());
1939 assert_eq!(1, manager.manager_ctx.key_lock.len());
1940
1941 manager.stop().await.unwrap();
1942
1943 assert!(dropped.load(AtomicOrdering::Relaxed));
1944 assert!(!manager.manager_ctx.running());
1945 assert!(!manager.manager_ctx.contains_procedure(procedure_id));
1946 assert!(
1947 manager
1948 .manager_ctx
1949 .running_procedures
1950 .lock()
1951 .unwrap()
1952 .is_empty()
1953 );
1954 assert!(
1955 manager
1956 .manager_ctx
1957 .finished_procedures
1958 .lock()
1959 .unwrap()
1960 .is_empty()
1961 );
1962 assert!(manager.manager_ctx.runner_tasks.lock().unwrap().is_empty());
1963 assert!(manager.manager_ctx.key_lock.is_empty());
1964 assert!(manager.manager_ctx.dynamic_key_lock.is_empty());
1965 }
1966
1967 #[test]
1968 fn test_register_loader() {
1969 let dir = create_temp_dir("register");
1970 let config = ManagerConfig {
1971 parent_path: "data/".to_string(),
1972 max_retry_times: 3,
1973 retry_delay: Duration::from_millis(500),
1974 ..Default::default()
1975 };
1976 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
1977 let poison_manager = Arc::new(InMemoryPoisonStore::new());
1978 let manager = LocalManager::new(config, state_store, poison_manager, None, None);
1979 manager.manager_ctx.start();
1980
1981 manager
1982 .register_loader("ProcedureToLoad", ProcedureToLoad::loader())
1983 .unwrap();
1984 let err = manager
1986 .register_loader("ProcedureToLoad", ProcedureToLoad::loader())
1987 .unwrap_err();
1988 assert!(matches!(err, Error::LoaderConflict { .. }), "{err}");
1989 }
1990
1991 #[tokio::test]
1992 async fn test_recover() {
1993 let dir = create_temp_dir("recover");
1994 let object_store = test_util::new_object_store(&dir);
1995 let config = ManagerConfig {
1996 parent_path: "data/".to_string(),
1997 max_retry_times: 3,
1998 retry_delay: Duration::from_millis(500),
1999 ..Default::default()
2000 };
2001 let state_store = Arc::new(ObjectStateStore::new(object_store.clone()));
2002 let poison_manager = Arc::new(InMemoryPoisonStore::new());
2003 let manager = LocalManager::new(config, state_store, poison_manager, None, None);
2004 manager.manager_ctx.start();
2005
2006 manager
2007 .register_loader("ProcedureToLoad", ProcedureToLoad::loader())
2008 .unwrap();
2009
2010 let procedure_store = ProcedureStore::from_object_store(object_store.clone());
2012 let root: BoxedProcedure = Box::new(ProcedureToLoad::new("test recover manager"));
2013 let root_id = ProcedureId::random();
2014 for step in 0..3 {
2016 let type_name = root.type_name().to_string();
2017 let data = root.dump().unwrap();
2018 procedure_store
2019 .store_procedure(root_id, step, type_name, data, None)
2020 .await
2021 .unwrap();
2022 }
2023
2024 let child: BoxedProcedure = Box::new(ProcedureToLoad::new("a child procedure"));
2025 let child_id = ProcedureId::random();
2026 for step in 0..2 {
2028 let type_name = child.type_name().to_string();
2029 let data = child.dump().unwrap();
2030 procedure_store
2031 .store_procedure(child_id, step, type_name, data, Some(root_id))
2032 .await
2033 .unwrap();
2034 }
2035
2036 manager.recover().await.unwrap();
2038
2039 let _ = manager.procedure_state(root_id).await.unwrap().unwrap();
2041 assert!(manager.procedure_state(child_id).await.unwrap().is_none());
2044 }
2045
2046 #[tokio::test]
2047 async fn test_submit_procedure() {
2048 let dir = create_temp_dir("submit");
2049 let config = ManagerConfig {
2050 parent_path: "data/".to_string(),
2051 max_retry_times: 3,
2052 retry_delay: Duration::from_millis(500),
2053 ..Default::default()
2054 };
2055 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
2056 let poison_manager = Arc::new(InMemoryPoisonStore::new());
2057 let manager = LocalManager::new(config, state_store, poison_manager, None, None);
2058 manager.manager_ctx.start();
2059
2060 let procedure_id = ProcedureId::random();
2061 assert!(
2062 manager
2063 .procedure_state(procedure_id)
2064 .await
2065 .unwrap()
2066 .is_none()
2067 );
2068 assert!(manager.procedure_watcher(procedure_id).is_none());
2069
2070 let mut procedure = ProcedureToLoad::new("submit");
2071 procedure.lock_key = LockKey::single_exclusive("test.submit");
2072 assert!(
2073 manager
2074 .submit(ProcedureWithId {
2075 id: procedure_id,
2076 procedure: Box::new(procedure),
2077 context: ProcedureContext::default(),
2078 })
2079 .await
2080 .is_ok()
2081 );
2082 assert!(
2083 manager
2084 .procedure_state(procedure_id)
2085 .await
2086 .unwrap()
2087 .is_some()
2088 );
2089 let mut watcher = manager.procedure_watcher(procedure_id).unwrap();
2091 watcher.changed().await.unwrap();
2092 assert!(watcher.borrow().is_done());
2093
2094 let err = manager
2096 .submit(ProcedureWithId {
2097 id: procedure_id,
2098 procedure: Box::new(ProcedureToLoad::new("submit")),
2099 context: ProcedureContext::default(),
2100 })
2101 .await
2102 .unwrap_err();
2103 assert!(matches!(err, Error::DuplicateProcedure { .. }), "{err}");
2104 }
2105
2106 #[tokio::test]
2107 async fn test_state_changed_on_err() {
2108 let dir = create_temp_dir("on_err");
2109 let config = ManagerConfig {
2110 parent_path: "data/".to_string(),
2111 max_retry_times: 3,
2112 retry_delay: Duration::from_millis(500),
2113 ..Default::default()
2114 };
2115 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
2116 let poison_manager = Arc::new(InMemoryPoisonStore::new());
2117 let manager = LocalManager::new(config, state_store, poison_manager, None, None);
2118 manager.manager_ctx.start();
2119
2120 #[derive(Debug)]
2121 struct MockProcedure {
2122 panic: bool,
2123 }
2124
2125 #[async_trait]
2126 impl Procedure for MockProcedure {
2127 fn type_name(&self) -> &str {
2128 "MockProcedure"
2129 }
2130
2131 async fn execute(&mut self, _ctx: &Context) -> Result<Status> {
2132 if self.panic {
2133 panic!();
2136 } else {
2137 Err(Error::external(MockError::new(StatusCode::Unexpected)))
2138 }
2139 }
2140
2141 async fn rollback(&mut self, _: &Context) -> Result<()> {
2142 Ok(())
2143 }
2144
2145 fn rollback_supported(&self) -> bool {
2146 true
2147 }
2148
2149 fn dump(&self) -> Result<String> {
2150 Ok(String::new())
2151 }
2152
2153 fn lock_key(&self) -> LockKey {
2154 LockKey::single_exclusive("test.submit")
2155 }
2156
2157 fn poison_keys(&self) -> PoisonKeys {
2158 PoisonKeys::default()
2159 }
2160 }
2161
2162 let check_procedure = |procedure| async {
2163 let procedure_id = ProcedureId::random();
2164 manager
2165 .submit(ProcedureWithId {
2166 id: procedure_id,
2167 procedure: Box::new(procedure),
2168 context: ProcedureContext::default(),
2169 })
2170 .await
2171 .unwrap()
2172 };
2173
2174 let mut watcher = check_procedure(MockProcedure { panic: false }).await;
2175 watcher.changed().await.unwrap();
2177 assert!(watcher.borrow().is_prepare_rollback());
2178 watcher.changed().await.unwrap();
2179 assert!(watcher.borrow().is_rolling_back());
2180 watcher.changed().await.unwrap();
2181 assert!(watcher.borrow().is_failed());
2182 let mut watcher = check_procedure(MockProcedure { panic: true }).await;
2184 watcher.changed().await.unwrap();
2185 assert!(watcher.borrow().is_failed());
2186 }
2187
2188 #[tokio::test]
2189 async fn test_procedure_manager_stopped() {
2190 let dir = create_temp_dir("procedure_manager_stopped");
2191 let config = ManagerConfig {
2192 parent_path: "data/".to_string(),
2193 max_retry_times: 3,
2194 retry_delay: Duration::from_millis(500),
2195 ..Default::default()
2196 };
2197 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
2198 let poison_manager = Arc::new(InMemoryPoisonStore::new());
2199 let manager = LocalManager::new(config, state_store, poison_manager, None, None);
2200
2201 let mut procedure = ProcedureToLoad::new("submit");
2202 procedure.lock_key = LockKey::single_exclusive("test.submit");
2203 let procedure_id = ProcedureId::random();
2204 assert_matches!(
2205 manager
2206 .submit(ProcedureWithId {
2207 id: procedure_id,
2208 procedure: Box::new(procedure),
2209 context: ProcedureContext::default(),
2210 })
2211 .await
2212 .unwrap_err(),
2213 error::Error::ManagerNotStart { .. }
2214 );
2215 }
2216
2217 #[tokio::test]
2218 async fn test_procedure_manager_restart() {
2219 let dir = create_temp_dir("procedure_manager_restart");
2220 let config = ManagerConfig {
2221 parent_path: "data/".to_string(),
2222 max_retry_times: 3,
2223 retry_delay: Duration::from_millis(500),
2224 ..Default::default()
2225 };
2226 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
2227 let poison_manager = Arc::new(InMemoryPoisonStore::new());
2228 let manager = LocalManager::new(config, state_store, poison_manager, None, None);
2229
2230 manager.start().await.unwrap();
2231 manager.stop().await.unwrap();
2232 manager.start().await.unwrap();
2233
2234 let mut procedure = ProcedureToLoad::new("submit");
2235 procedure.lock_key = LockKey::single_exclusive("test.submit");
2236 let procedure_id = ProcedureId::random();
2237 assert!(
2238 manager
2239 .submit(ProcedureWithId {
2240 id: procedure_id,
2241 procedure: Box::new(procedure),
2242 context: ProcedureContext::default(),
2243 })
2244 .await
2245 .is_ok()
2246 );
2247 assert!(
2248 manager
2249 .procedure_state(procedure_id)
2250 .await
2251 .unwrap()
2252 .is_some()
2253 );
2254 }
2255
2256 #[tokio::test(flavor = "multi_thread")]
2257 async fn test_remove_outdated_meta_task() {
2258 let dir = create_temp_dir("remove_outdated_meta_task");
2259 let object_store = test_util::new_object_store(&dir);
2260 let config = ManagerConfig {
2261 parent_path: "data/".to_string(),
2262 max_retry_times: 3,
2263 retry_delay: Duration::from_millis(500),
2264 remove_outdated_meta_task_interval: Duration::from_millis(1),
2265 remove_outdated_meta_ttl: Duration::from_millis(1),
2266 max_running_procedures: 128,
2267 };
2268 let state_store = Arc::new(ObjectStateStore::new(object_store.clone()));
2269 let poison_manager = Arc::new(InMemoryPoisonStore::new());
2270 let manager = LocalManager::new(config, state_store, poison_manager, None, None);
2271 manager.manager_ctx.set_running();
2272
2273 let mut procedure = ProcedureToLoad::new("submit");
2274 procedure.lock_key = LockKey::single_exclusive("test.submit");
2275 let procedure_id = ProcedureId::random();
2276 assert!(
2277 manager
2278 .submit(ProcedureWithId {
2279 id: procedure_id,
2280 procedure: Box::new(procedure),
2281 context: ProcedureContext::default(),
2282 })
2283 .await
2284 .is_ok()
2285 );
2286 let mut watcher = manager.procedure_watcher(procedure_id).unwrap();
2287 watcher.changed().await.unwrap();
2288
2289 manager.start().await.unwrap();
2290 tokio::time::sleep(Duration::from_millis(300)).await;
2291 assert!(
2292 manager
2293 .procedure_state(procedure_id)
2294 .await
2295 .unwrap()
2296 .is_none()
2297 );
2298
2299 manager.stop().await.unwrap();
2301 let mut procedure = ProcedureToLoad::new("submit");
2302 procedure.lock_key = LockKey::single_exclusive("test.submit");
2303 let procedure_id = ProcedureId::random();
2304
2305 manager.manager_ctx.set_running();
2306 assert!(
2307 manager
2308 .submit(ProcedureWithId {
2309 id: procedure_id,
2310 procedure: Box::new(procedure),
2311 context: ProcedureContext::default(),
2312 })
2313 .await
2314 .is_ok()
2315 );
2316 let mut watcher = manager.procedure_watcher(procedure_id).unwrap();
2317 watcher.changed().await.unwrap();
2318 tokio::time::sleep(Duration::from_millis(300)).await;
2319 assert!(
2320 manager
2321 .procedure_state(procedure_id)
2322 .await
2323 .unwrap()
2324 .is_some()
2325 );
2326
2327 let mut procedure = ProcedureToLoad::new("submit");
2329 procedure.lock_key = LockKey::single_exclusive("test.submit");
2330 let procedure_id = ProcedureId::random();
2331 assert!(
2332 manager
2333 .submit(ProcedureWithId {
2334 id: procedure_id,
2335 procedure: Box::new(procedure),
2336 context: ProcedureContext::default(),
2337 })
2338 .await
2339 .is_ok()
2340 );
2341 let mut watcher = manager.procedure_watcher(procedure_id).unwrap();
2342 watcher.changed().await.unwrap();
2343
2344 manager.start().await.unwrap();
2345 tokio::time::sleep(Duration::from_millis(300)).await;
2346 assert!(
2347 manager
2348 .procedure_state(procedure_id)
2349 .await
2350 .unwrap()
2351 .is_none()
2352 );
2353 }
2354
2355 #[tokio::test]
2356 async fn test_too_many_running_procedures() {
2357 let dir = create_temp_dir("too_many_running_procedures");
2358 let config = ManagerConfig {
2359 parent_path: "data/".to_string(),
2360 max_retry_times: 3,
2361 retry_delay: Duration::from_millis(500),
2362 max_running_procedures: 1,
2363 ..Default::default()
2364 };
2365 let state_store = Arc::new(ObjectStateStore::new(test_util::new_object_store(&dir)));
2366 let poison_manager = Arc::new(InMemoryPoisonStore::new());
2367 let manager = LocalManager::new(config, state_store, poison_manager, None, None);
2368 manager.start().await.unwrap();
2369
2370 manager
2371 .manager_ctx
2372 .running_procedures
2373 .lock()
2374 .unwrap()
2375 .insert(ProcedureId::random());
2376
2377 let mut procedure = ProcedureToLoad::new("submit");
2379 procedure.lock_key = LockKey::single_exclusive("test.submit");
2380 let procedure_id = ProcedureId::random();
2381 let err = manager
2382 .submit(ProcedureWithId {
2383 id: procedure_id,
2384 procedure: Box::new(procedure),
2385 context: ProcedureContext::default(),
2386 })
2387 .await
2388 .unwrap_err();
2389 assert!(matches!(err, Error::TooManyRunningProcedures { .. }));
2390
2391 manager
2392 .manager_ctx
2393 .running_procedures
2394 .lock()
2395 .unwrap()
2396 .clear();
2397
2398 let mut procedure = ProcedureToLoad::new("submit");
2400 procedure.lock_key = LockKey::single_exclusive("test.submit");
2401 assert!(
2402 manager
2403 .submit(ProcedureWithId {
2404 id: procedure_id,
2405 procedure: Box::new(procedure),
2406 context: ProcedureContext::default(),
2407 })
2408 .await
2409 .is_ok()
2410 );
2411 assert!(
2412 manager
2413 .procedure_state(procedure_id)
2414 .await
2415 .unwrap()
2416 .is_some()
2417 );
2418 let mut watcher = manager.procedure_watcher(procedure_id).unwrap();
2420 watcher.changed().await.unwrap();
2421 assert!(watcher.borrow().is_done());
2422 }
2423
2424 #[derive(Debug)]
2425 struct ProcedureToRecover {
2426 content: String,
2427 lock_key: LockKey,
2428 notify: Option<Arc<Notify>>,
2429 poison_keys: PoisonKeys,
2430 }
2431
2432 #[async_trait]
2433 impl Procedure for ProcedureToRecover {
2434 fn type_name(&self) -> &str {
2435 "ProcedureToRecover"
2436 }
2437
2438 async fn execute(&mut self, _ctx: &Context) -> Result<Status> {
2439 Ok(Status::done())
2440 }
2441
2442 fn dump(&self) -> Result<String> {
2443 Ok(self.content.clone())
2444 }
2445
2446 fn lock_key(&self) -> LockKey {
2447 self.lock_key.clone()
2448 }
2449
2450 fn recover(&mut self) -> Result<()> {
2451 self.notify.as_ref().unwrap().notify_one();
2452 Ok(())
2453 }
2454
2455 fn poison_keys(&self) -> PoisonKeys {
2456 self.poison_keys.clone()
2457 }
2458 }
2459
2460 impl ProcedureToRecover {
2461 fn new(content: &str) -> ProcedureToRecover {
2462 ProcedureToRecover {
2463 content: content.to_string(),
2464 lock_key: LockKey::default(),
2465 poison_keys: PoisonKeys::default(),
2466 notify: None,
2467 }
2468 }
2469
2470 fn loader(notify: Arc<Notify>) -> BoxedProcedureLoader {
2471 let f = move |json: &str| {
2472 let procedure = ProcedureToRecover {
2473 content: json.to_string(),
2474 lock_key: LockKey::default(),
2475 poison_keys: PoisonKeys::default(),
2476 notify: Some(notify.clone()),
2477 };
2478 Ok(Box::new(procedure) as _)
2479 };
2480 Box::new(f)
2481 }
2482 }
2483
2484 #[tokio::test]
2485 async fn test_procedure_recover() {
2486 common_telemetry::init_default_ut_logging();
2487 let dir = create_temp_dir("procedure_recover");
2488 let object_store = test_util::new_object_store(&dir);
2489 let config = ManagerConfig {
2490 parent_path: "data/".to_string(),
2491 max_retry_times: 3,
2492 retry_delay: Duration::from_millis(500),
2493 ..Default::default()
2494 };
2495 let state_store = Arc::new(ObjectStateStore::new(object_store.clone()));
2496 let poison_manager = Arc::new(InMemoryPoisonStore::new());
2497 let manager = LocalManager::new(config, state_store, poison_manager, None, None);
2498 manager.manager_ctx.start();
2499
2500 let notify = Arc::new(Notify::new());
2501 manager
2502 .register_loader(
2503 "ProcedureToRecover",
2504 ProcedureToRecover::loader(notify.clone()),
2505 )
2506 .unwrap();
2507
2508 let procedure_store = ProcedureStore::from_object_store(object_store.clone());
2510 let root: BoxedProcedure = Box::new(ProcedureToRecover::new("test procedure recovery"));
2511 let root_id = ProcedureId::random();
2512 for step in 0..3 {
2514 let type_name = root.type_name().to_string();
2515 let data = root.dump().unwrap();
2516 procedure_store
2517 .store_procedure(root_id, step, type_name, data, None)
2518 .await
2519 .unwrap();
2520 }
2521
2522 manager.recover().await.unwrap();
2524 timeout(Duration::from_secs(10), notify.notified())
2525 .await
2526 .unwrap();
2527 }
2528}