Skip to main content

common_procedure/
local.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod 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
52/// The expired time of a procedure's metadata.
53const 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/// Shared metadata of a procedure.
71///
72/// # Note
73/// [Notify] is not a condition variable, we can't guarantee the waiters are notified
74/// if they didn't call `notified()` before we signal the notify. So we
75/// 1. use dedicated notify for each condition, such as waiting for a lock, waiting
76///    for children;
77/// 2. always use `notify_one` and ensure there are only one waiter.
78#[derive(Debug)]
79pub(crate) struct ProcedureMeta {
80    /// Id of this procedure.
81    id: ProcedureId,
82    /// Type name of this procedure.
83    type_name: String,
84    /// Parent procedure id.
85    parent_id: Option<ProcedureId>,
86    /// Recovery policy captured when the procedure is submitted as a child.
87    subprocedure_recovery: SubprocedureRecovery,
88    /// Context associated with the root submission.
89    context: ProcedureContext,
90    /// Notify to wait for subprocedures.
91    child_notify: Notify,
92    /// Lock required by this procedure.
93    lock_key: LockKey,
94    /// Poison keys that may cause this procedure to become poisoned during execution.
95    poison_keys: PoisonKeys,
96    /// Sender to notify the procedure state.
97    state_sender: Sender<ProcedureState>,
98    /// Receiver to watch the procedure state.
99    state_receiver: Receiver<ProcedureState>,
100    /// Id of child procedures.
101    children: Mutex<Vec<ProcedureId>>,
102    /// Start execution time of this procedure.
103    start_time_ms: AtomicI64,
104    /// End execution time of this procedure.
105    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    /// Sets the recovery policy for this subprocedure.
138    fn with_subprocedure_recovery(mut self, recovery: SubprocedureRecovery) -> Self {
139        self.subprocedure_recovery = recovery;
140        self
141    }
142
143    /// Returns current [ProcedureState].
144    fn state(&self) -> ProcedureState {
145        self.state_receiver.borrow().clone()
146    }
147
148    /// Update current [ProcedureState].
149    fn set_state(&self, state: ProcedureState) {
150        // Safety: ProcedureMeta also holds the receiver, so `send()` should never fail.
151        self.state_sender.send(state).unwrap();
152    }
153
154    /// Push `procedure_id` of the subprocedure to the metadata.
155    fn push_child(&self, procedure_id: ProcedureId) {
156        let mut children = self.children.lock().unwrap();
157        children.push(procedure_id);
158    }
159
160    /// Append subprocedures to given `buffer`.
161    fn list_children(&self, buffer: &mut Vec<ProcedureId>) {
162        let children = self.children.lock().unwrap();
163        buffer.extend_from_slice(&children);
164    }
165
166    /// Returns the number of subprocedures.
167    fn num_children(&self) -> usize {
168        self.children.lock().unwrap().len()
169    }
170
171    /// update the start time of the procedure.
172    fn set_start_time_ms(&self) {
173        self.start_time_ms
174            .store(common_time::util::current_time_millis(), Ordering::Relaxed);
175    }
176
177    /// update the end time of the procedure.
178    fn set_end_time_ms(&self) {
179        self.end_time_ms
180            .store(common_time::util::current_time_millis(), Ordering::Relaxed);
181    }
182}
183
184/// Reference counted pointer to [ProcedureMeta].
185type ProcedureMetaRef = Arc<ProcedureMeta>;
186
187/// Procedure loaded from store.
188struct LoadedProcedure {
189    procedure: BoxedProcedure,
190    step: u32,
191}
192
193/// The dynamic lock for procedure execution.
194///
195/// Unlike the procedure-level locks, these locks are acquired dynamically by the procedure
196/// during execution. They are only held when the procedure specifically needs these keys
197/// and are released as soon as the procedure no longer needs them.
198/// This allows for more fine-grained concurrency control during procedure execution.
199pub(crate) type DynamicKeyLock = Arc<KeyRwLock<String>>;
200
201/// Acquires a dynamic key lock for the given key.
202///
203/// This function takes a reference to the dynamic key lock and a pointer to the key.
204/// It then matches the key type and acquires the appropriate lock.
205pub 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}
228/// A guard for the dynamic key lock.
229///
230/// This guard is used to release the lock when the procedure no longer needs it.
231/// It also ensures that the lock is cleaned up when the guard is dropped.
232pub 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
247/// Shared context of the manager.
248pub(crate) struct ManagerContext {
249    /// Procedure loaders. The key is the type name of the procedure which the loader returns.
250    loaders: Mutex<HashMap<String, BoxedProcedureLoader>>,
251    /// The key lock for the procedure.
252    ///
253    /// The lock keys are defined in `Procedure::lock_key()`.
254    /// These locks are acquired before the procedure starts and released after the procedure finishes.
255    /// They ensure exclusive access to resources throughout the entire procedure lifecycle.
256    key_lock: KeyRwLock<String>,
257    /// The dynamic lock for procedure execution.
258    ///
259    /// Unlike the procedure-level locks, these locks are acquired dynamically by the procedure
260    /// during execution. They are only held when the procedure specifically needs these keys
261    /// and are released as soon as the procedure no longer needs them.
262    /// This allows for more fine-grained concurrency control during procedure execution.
263    dynamic_key_lock: DynamicKeyLock,
264    /// Procedures in the manager.
265    procedures: RwLock<HashMap<ProcedureId, ProcedureMetaRef>>,
266    /// Running procedures.
267    running_procedures: Mutex<HashSet<ProcedureId>>,
268    /// Ids and finished time of finished procedures.
269    finished_procedures: Mutex<VecDeque<(ProcedureId, Instant)>>,
270    /// Runner tasks of procedures.
271    runner_tasks: Mutex<HashMap<ProcedureId, JoinHandle<()>>>,
272    /// Running flag.
273    running: Arc<AtomicBool>,
274    /// Poison manager.
275    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            // validate the procedure exists
294            let procedures = self.procedures.read().unwrap();
295            let procedure = procedures
296                .get(&procedure_id)
297                .context(ProcedureNotFoundSnafu { procedure_id })?;
298
299            // validate the poison key is defined
300            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    /// Returns a new [ManagerContext].
320    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    /// Set the running flag.
340    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    /// Return `ProcedureManager` is running.
419    pub(crate) fn running(&self) -> bool {
420        self.running.load(Ordering::Relaxed)
421    }
422
423    /// Returns true if the procedure with specific `procedure_id` exists.
424    fn contains_procedure(&self, procedure_id: ProcedureId) -> bool {
425        let procedures = self.procedures.read().unwrap();
426        procedures.contains_key(&procedure_id)
427    }
428
429    /// Returns the number of running procedures.
430    fn num_running_procedures(&self) -> usize {
431        self.running_procedures.lock().unwrap().len()
432    }
433
434    /// Try to insert the `procedure` to the context if there is no procedure
435    /// with same [ProcedureId].
436    ///
437    /// Returns `false` if there is already a procedure using the same [ProcedureId].
438    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    /// Returns the [ProcedureState] of specific `procedure_id`.
455    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    /// Returns the [Receiver<ProcedureState>] of specific `procedure_id`.
461    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    /// Returns the [ProcedureMeta] of all procedures.
469    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    /// Returns the [Watcher] of specific `procedure_id`.
485    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    /// Notify a suspended parent procedure with specific `procedure_id` by its subprocedure.
493    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    /// Load procedure from specific [ProcedureMessage].
501    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    /// Returns all procedures in the tree (including given `root` procedure).
532    ///
533    /// If callers need a consistent view of the tree, they must ensure no new
534    /// procedure is added to the tree during using this method.
535    fn procedures_in_tree(&self, root: &ProcedureMetaRef) -> Vec<ProcedureId> {
536        let sub_num = root.num_children();
537        // Reserve capacity for the root procedure and its children.
538        let mut procedures = Vec::with_capacity(1 + sub_num);
539
540        let mut queue = VecDeque::with_capacity(1 + sub_num);
541        // Push the root procedure to the queue.
542        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            // Find metadatas of children.
550            children_ids.clear();
551            meta.list_children(&mut children_ids);
552            self.find_procedures(&children_ids, &mut children);
553
554            // Traverse children later.
555            for child in children.drain(..) {
556                queue.push_back(child);
557            }
558        }
559
560        procedures
561    }
562
563    /// Finds procedures by given `procedure_ids`.
564    ///
565    /// Ignores the id if corresponding procedure is not found.
566    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    /// Clean resources of finished procedures.
576    fn on_procedures_finish(&self, procedure_ids: &[ProcedureId]) {
577        // Since users need to query the procedure state, so we can't remove the
578        // meta of the procedure directly.
579        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        // Remove the procedures from the running set.
584        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    /// Remove metadata of outdated procedures.
591    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                    // The rest procedures are finished later, so we can break
605                    // the loop.
606                    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/// Config for [LocalManager].
624#[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/// A delayed configuration handle for procedure lifecycle event recording.
654#[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    /// Installs the recorder used by subsequently submitted procedures.
663    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    /// Returns true if the procedure manager is paused.
672    async fn is_paused(&self) -> std::result::Result<bool, BoxedError>;
673}
674
675/// A [ProcedureManager] that maintains procedure states locally.
676pub struct LocalManager {
677    manager_ctx: Arc<ManagerContext>,
678    procedure_store: Arc<ProcedureStore>,
679    max_retry_times: usize,
680    retry_delay: Duration,
681    /// GC task.
682    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    /// Create a new [LocalManager] with specific `config`.
690    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    /// Returns the handle used to configure procedure lifecycle event recording.
712    pub fn event_recorder_handle(&self) -> EventRecorderHandle {
713        self.event_recorder.clone()
714    }
715
716    /// Build remove outedated meta task
717    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    /// Submit a root procedure with given `procedure_id`.
728    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        // Serialize admission and registration so concurrent fresh submissions cannot
749        // exceed the limit. Recovery resumes already accepted work and bypasses it.
750        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        // Inserts meta into the manager before actually spawnd the runner.
774        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                    // Run the root procedure.
793                    // The task was moved to another runtime for execution.
794                    // In order not to interrupt tracing, a span needs to be created to continue tracing the current task.
795                    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    /// Recovers unfinished procedures and reruns them.
814    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    /// Returns true if contains a specified loader.
854    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/// Create a new [ProcedureMeta] for test purpose.
981#[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        // This is the persisted JSON shape written before ProcedureMessage had `context`.
1714        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        // Register duplicate loader.
1947        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        // Prepare data
1973        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        // Prepare data for the root procedure.
1977        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        // Prepare data for the child procedure
1989        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        // Recover the manager
1999        manager.recover().await.unwrap();
2000
2001        // The manager should submit the root procedure.
2002        let _ = manager.procedure_state(root_id).await.unwrap().unwrap();
2003        // Since the mocked root procedure actually doesn't submit subprocedures, so there is no
2004        // related state.
2005        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        // Wait for the procedure done.
2052        let mut watcher = manager.procedure_watcher(procedure_id).unwrap();
2053        watcher.changed().await.unwrap();
2054        assert!(watcher.borrow().is_done());
2055
2056        // Try to submit procedure with same id again.
2057        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                    // Test the runner can set the state to failed even the procedure
2096                    // panics.
2097                    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        // Wait for the notification.
2138        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        // The runner won't rollback a panicked procedure.
2145        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        // The remove_outdated_meta method has been stopped, so any procedure meta-data will not be automatically removed.
2262        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        // After restart
2290        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                // Both submissions reach metadata construction before either registers.
2337                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        // Submit a new procedure should fail.
2413        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        // Submit a new procedure should succeed.
2434        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        // Wait for the procedure done.
2454        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        // Prepare data
2544        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        // Prepare data for the root procedure.
2548        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        // Recover the manager
2558        manager.recover().await.unwrap();
2559        timeout(Duration::from_secs(10), notify.notified())
2560            .await
2561            .unwrap();
2562    }
2563}