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