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