1use std::any::Any;
16use std::fmt;
17use std::fmt::Display;
18use std::str::FromStr;
19use std::sync::Arc;
20
21use async_trait::async_trait;
22use common_event_recorder::{Event, EventTypeFilterRef, PersistentEventContext};
23use serde::{Deserialize, Serialize};
24use smallvec::{SmallVec, smallvec};
25use snafu::{ResultExt, Snafu};
26use tokio::sync::watch::Receiver;
27use uuid::Uuid;
28
29use crate::error::{self, Error, Result};
30use crate::local::DynamicKeyLockGuard;
31use crate::watcher::Watcher;
32
33pub type Output = Arc<dyn Any + Send + Sync>;
34
35#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
37pub struct ProcedureContext {
38 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub actor: Option<String>,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub event_context: Option<PersistentEventContext>,
44}
45
46impl ProcedureContext {
47 pub fn from_event_context(event_context: PersistentEventContext) -> Self {
48 Self {
49 actor: None,
50 event_context: Some(event_context),
51 }
52 }
53
54 pub fn is_empty(&self) -> bool {
55 self.actor.is_none() && self.event_context.is_none()
56 }
57}
58
59#[derive(Debug)]
61pub enum Status {
62 Executing {
64 persist: bool,
66 clean_poisons: bool,
68 },
69 Suspended {
71 subprocedures: Vec<ProcedureWithId>,
72 persist: bool,
74 },
75 Poisoned {
77 keys: PoisonKeys,
79 error: Error,
81 },
82 Done { output: Option<Output> },
84}
85
86impl Status {
87 pub fn suspended(subprocedures: Vec<ProcedureWithId>, persist: bool) -> Status {
89 Status::Suspended {
90 subprocedures,
91 persist,
92 }
93 }
94
95 pub fn poisoned(keys: impl IntoIterator<Item = PoisonKey>, error: Error) -> Status {
97 Status::Poisoned {
98 keys: PoisonKeys::new(keys),
99 error,
100 }
101 }
102
103 pub fn executing(persist: bool) -> Status {
105 Status::Executing {
106 persist,
107 clean_poisons: false,
108 }
109 }
110
111 pub fn executing_with_clean_poisons(persist: bool) -> Status {
113 Status::Executing {
114 persist,
115 clean_poisons: true,
116 }
117 }
118
119 pub fn done() -> Status {
121 Status::Done { output: None }
122 }
123
124 #[cfg(any(test, feature = "testing"))]
125 pub fn downcast_output_ref<T: 'static>(&self) -> Option<&T> {
130 if let Status::Done { output } = self {
131 output
132 .as_ref()
133 .expect("Try to downcast the output of Status::Done, but the output is None")
134 .downcast_ref()
135 } else {
136 panic!("Expected the Status::Done, but got: {:?}", self)
137 }
138 }
139
140 pub fn done_with_output<T: Any + Send + Sync>(output: T) -> Status {
142 Status::Done {
143 output: Some(Arc::new(output)),
144 }
145 }
146 pub fn is_done(&self) -> bool {
148 matches!(self, Status::Done { .. })
149 }
150
151 pub fn need_persist(&self) -> bool {
153 match self {
154 Status::Executing { persist, .. } | Status::Suspended { persist, .. } => *persist,
157 Status::Done { .. } | Status::Poisoned { .. } => false,
158 }
159 }
160
161 pub fn need_clean_poisons(&self) -> bool {
163 match self {
164 Status::Executing { clean_poisons, .. } => *clean_poisons,
165 Status::Done { .. } => true,
166 _ => false,
167 }
168 }
169}
170
171#[async_trait]
173pub trait ContextProvider: Send + Sync {
174 async fn procedure_state(&self, procedure_id: ProcedureId) -> Result<Option<ProcedureState>>;
176
177 async fn procedure_state_receiver(
178 &self,
179 procedure_id: ProcedureId,
180 ) -> Result<Option<Receiver<ProcedureState>>>;
181
182 async fn try_put_poison(&self, key: &PoisonKey, procedure_id: ProcedureId) -> Result<()>;
187
188 async fn acquire_lock(&self, key: &StringKey) -> DynamicKeyLockGuard;
190}
191
192pub type ContextProviderRef = Arc<dyn ContextProvider>;
194
195#[derive(Clone)]
197pub struct Context {
198 pub procedure_id: ProcedureId,
200 pub provider: ContextProviderRef,
202 pub event_context: Option<PersistentEventContext>,
204}
205
206impl Context {
207 pub async fn is_retrying(&self) -> Option<bool> {
209 self.provider
210 .procedure_state(self.procedure_id)
211 .await
212 .ok()
213 .flatten()
214 .map(|s| s.is_retrying())
215 }
216}
217
218#[async_trait]
220pub trait Procedure: Send {
221 fn type_name(&self) -> &str;
223
224 async fn execute(&mut self, ctx: &Context) -> Result<Status>;
228
229 async fn rollback(&mut self, _: &Context) -> Result<()> {
233 error::RollbackNotSupportedSnafu {}.fail()
234 }
235
236 fn rollback_supported(&self) -> bool {
238 false
239 }
240
241 fn dump(&self) -> Result<String>;
243
244 fn recover(&mut self) -> Result<()> {
246 Ok(())
247 }
248
249 fn lock_key(&self) -> LockKey;
251
252 fn poison_keys(&self) -> PoisonKeys {
254 PoisonKeys::default()
255 }
256
257 fn event(&self, _ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
267 None
268 }
269}
270
271pub struct EventContext<'a> {
273 pub procedure_id: ProcedureId,
275 pub lifecycle_state: &'a ProcedureState,
277 pub trigger: EventTrigger,
279 pub event_type_filter: EventTypeFilterRef,
281 pub event_context: Option<&'a PersistentEventContext>,
283}
284
285#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
289#[serde(tag = "type")]
290pub enum EventTrigger {
291 Submitted,
293 Recovered,
295 ChildSubmitted {
297 procedure_id: ProcedureId,
299 outcome: ChildSubmissionOutcome,
301 },
302 Retrying {
304 phase: RetryPhase,
306 attempt: u32,
308 },
309 RollingBack,
311 Succeeded,
313 Failed,
315 Poisoned,
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
321pub enum RetryPhase {
322 Execute,
324 Rollback,
326}
327
328#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
330pub enum ChildSubmissionOutcome {
331 Accepted,
332 AlreadyAccepted,
333 ManagerStopped,
334 SpawnFailed,
335}
336
337#[async_trait]
338impl<T: Procedure + ?Sized> Procedure for Box<T> {
339 fn type_name(&self) -> &str {
340 (**self).type_name()
341 }
342
343 async fn execute(&mut self, ctx: &Context) -> Result<Status> {
344 (**self).execute(ctx).await
345 }
346
347 async fn rollback(&mut self, ctx: &Context) -> Result<()> {
348 (**self).rollback(ctx).await
349 }
350
351 fn rollback_supported(&self) -> bool {
352 (**self).rollback_supported()
353 }
354
355 fn dump(&self) -> Result<String> {
356 (**self).dump()
357 }
358
359 fn lock_key(&self) -> LockKey {
360 (**self).lock_key()
361 }
362
363 fn poison_keys(&self) -> PoisonKeys {
364 (**self).poison_keys()
365 }
366
367 fn event(&self, ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
368 (**self).event(ctx)
369 }
370}
371
372#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
373pub struct PoisonKey(String);
374
375impl Display for PoisonKey {
376 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377 write!(f, "{}", self.0)
378 }
379}
380
381impl PoisonKey {
382 pub fn new(key: impl Into<String>) -> Self {
384 Self(key.into())
385 }
386}
387
388#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
392pub struct PoisonKeys(SmallVec<[PoisonKey; 2]>);
393
394impl PoisonKeys {
395 pub fn single(key: impl Into<String>) -> Self {
397 Self(smallvec![PoisonKey::new(key)])
398 }
399
400 pub fn new(keys: impl IntoIterator<Item = PoisonKey>) -> Self {
402 Self(keys.into_iter().collect())
403 }
404
405 pub fn contains(&self, key: &PoisonKey) -> bool {
407 self.0.contains(key)
408 }
409
410 pub fn iter(&self) -> impl Iterator<Item = &PoisonKey> {
412 self.0.iter()
413 }
414}
415
416#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
417pub enum StringKey {
418 Share(String),
419 Exclusive(String),
420}
421
422#[derive(Clone, Debug, Default, PartialEq, Eq)]
428pub struct LockKey(SmallVec<[StringKey; 2]>);
429
430impl StringKey {
431 pub fn into_string(self) -> String {
432 match self {
433 StringKey::Share(s) => s,
434 StringKey::Exclusive(s) => s,
435 }
436 }
437
438 pub fn as_string(&self) -> &String {
439 match self {
440 StringKey::Share(s) => s,
441 StringKey::Exclusive(s) => s,
442 }
443 }
444}
445
446impl LockKey {
447 pub fn single(key: impl Into<StringKey>) -> LockKey {
449 LockKey(smallvec![key.into()])
450 }
451
452 pub fn single_exclusive(key: impl Into<String>) -> LockKey {
454 LockKey(smallvec![StringKey::Exclusive(key.into())])
455 }
456
457 pub fn new(iter: impl IntoIterator<Item = StringKey>) -> LockKey {
459 let mut vec: SmallVec<_> = iter.into_iter().collect();
460 vec.sort();
461 vec.dedup();
463 LockKey(vec)
464 }
465
466 pub fn new_exclusive(iter: impl IntoIterator<Item = String>) -> LockKey {
468 Self::new(iter.into_iter().map(StringKey::Exclusive))
469 }
470
471 pub fn keys_to_lock(&self) -> impl Iterator<Item = &StringKey> {
473 self.0.iter()
474 }
475
476 pub fn get_keys(&self) -> Vec<String> {
478 self.0.iter().map(|key| format!("{:?}", key)).collect()
479 }
480}
481
482pub type BoxedProcedure = Box<dyn Procedure>;
484
485pub struct ProcedureWithId {
487 pub id: ProcedureId,
489 pub procedure: BoxedProcedure,
490 pub context: ProcedureContext,
492}
493
494impl ProcedureWithId {
495 pub fn with_random_id(procedure: BoxedProcedure) -> ProcedureWithId {
498 ProcedureWithId {
499 id: ProcedureId::random(),
500 procedure,
501 context: ProcedureContext::default(),
502 }
503 }
504
505 pub fn with_context(mut self, context: ProcedureContext) -> Self {
507 self.context = context;
508 self
509 }
510}
511
512impl fmt::Debug for ProcedureWithId {
513 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
514 write!(f, "{}-{}", self.procedure.type_name(), self.id)
515 }
516}
517
518#[derive(Debug, Snafu)]
519pub struct ParseIdError {
520 source: uuid::Error,
521}
522
523#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
525pub struct ProcedureId(Uuid);
526
527impl ProcedureId {
528 pub fn random() -> ProcedureId {
530 ProcedureId(Uuid::new_v4())
531 }
532
533 pub fn parse_str(input: &str) -> std::result::Result<ProcedureId, ParseIdError> {
535 Uuid::parse_str(input)
536 .map(ProcedureId)
537 .context(ParseIdSnafu)
538 }
539}
540
541impl fmt::Display for ProcedureId {
542 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
543 write!(f, "{}", self.0)
544 }
545}
546
547impl FromStr for ProcedureId {
548 type Err = ParseIdError;
549
550 fn from_str(s: &str) -> std::result::Result<ProcedureId, ParseIdError> {
551 ProcedureId::parse_str(s)
552 }
553}
554
555pub type BoxedProcedureLoader = Box<dyn Fn(&str) -> Result<BoxedProcedure> + Send>;
557
558#[derive(Debug, Default, Clone)]
560pub enum ProcedureState {
561 #[default]
563 Running,
564 Done { output: Option<Output> },
566 Retrying { error: Arc<Error> },
568 PrepareRollback { error: Arc<Error> },
570 RollingBack { error: Arc<Error> },
572 Failed { error: Arc<Error> },
574 Poisoned { keys: PoisonKeys, error: Arc<Error> },
576}
577
578impl ProcedureState {
579 pub fn failed(error: Arc<Error>) -> ProcedureState {
581 ProcedureState::Failed { error }
582 }
583
584 pub fn prepare_rollback(error: Arc<Error>) -> ProcedureState {
586 ProcedureState::PrepareRollback { error }
587 }
588
589 pub fn rolling_back(error: Arc<Error>) -> ProcedureState {
591 ProcedureState::RollingBack { error }
592 }
593
594 pub fn retrying(error: Arc<Error>) -> ProcedureState {
596 ProcedureState::Retrying { error }
597 }
598
599 pub fn poisoned(keys: PoisonKeys, error: Arc<Error>) -> ProcedureState {
601 ProcedureState::Poisoned { keys, error }
602 }
603
604 pub fn is_running(&self) -> bool {
606 matches!(self, ProcedureState::Running)
607 }
608
609 pub fn is_done(&self) -> bool {
611 matches!(self, ProcedureState::Done { .. })
612 }
613
614 pub fn is_poisoned(&self) -> bool {
616 matches!(self, ProcedureState::Poisoned { .. })
617 }
618
619 pub fn is_failed(&self) -> bool {
621 matches!(self, ProcedureState::Failed { .. })
622 }
623
624 pub fn is_retrying(&self) -> bool {
626 matches!(self, ProcedureState::Retrying { .. })
627 }
628
629 pub fn is_rolling_back(&self) -> bool {
631 matches!(self, ProcedureState::RollingBack { .. })
632 }
633
634 pub fn is_prepare_rollback(&self) -> bool {
636 matches!(self, ProcedureState::PrepareRollback { .. })
637 }
638
639 pub fn error(&self) -> Option<&Arc<Error>> {
641 match self {
642 ProcedureState::Failed { error } => Some(error),
643 ProcedureState::Retrying { error } => Some(error),
644 ProcedureState::RollingBack { error } => Some(error),
645 ProcedureState::Poisoned { error, .. } => Some(error),
646 _ => None,
647 }
648 }
649
650 pub fn as_str_name(&self) -> &str {
652 match self {
653 ProcedureState::Running => "Running",
654 ProcedureState::Done { .. } => "Done",
655 ProcedureState::Retrying { .. } => "Retrying",
656 ProcedureState::Failed { .. } => "Failed",
657 ProcedureState::PrepareRollback { .. } => "PrepareRollback",
658 ProcedureState::RollingBack { .. } => "RollingBack",
659 ProcedureState::Poisoned { .. } => "Poisoned",
660 }
661 }
662}
663
664#[derive(Debug, Clone)]
666pub enum InitProcedureState {
667 Running,
668 RollingBack,
669}
670
671#[async_trait]
674pub trait ProcedureManager: Send + Sync + 'static {
675 fn register_loader(&self, name: &str, loader: BoxedProcedureLoader) -> Result<()>;
677
678 async fn start(&self) -> Result<()>;
684
685 async fn stop(&self) -> Result<()>;
687
688 async fn submit(&self, procedure: ProcedureWithId) -> Result<Watcher>;
692
693 async fn procedure_state(&self, procedure_id: ProcedureId) -> Result<Option<ProcedureState>>;
697
698 fn procedure_watcher(&self, procedure_id: ProcedureId) -> Option<Watcher>;
700
701 async fn list_procedures(&self) -> Result<Vec<ProcedureInfo>>;
703
704 async fn has_unfinished_procedure(&self, type_names: &[&str]) -> Result<bool>;
708}
709
710pub type ProcedureManagerRef = Arc<dyn ProcedureManager>;
712
713#[derive(Debug, Clone)]
714pub struct ProcedureInfo {
715 pub id: ProcedureId,
717 pub type_name: String,
719 pub start_time_ms: i64,
721 pub end_time_ms: i64,
723 pub state: ProcedureState,
725 pub lock_keys: Vec<String>,
727}
728
729#[cfg(test)]
730mod tests {
731 use async_trait::async_trait;
732 use common_error::mock::MockError;
733 use common_error::status_code::StatusCode;
734
735 use super::*;
736
737 struct DefaultEventProcedure;
738
739 #[async_trait]
740 impl Procedure for DefaultEventProcedure {
741 fn type_name(&self) -> &str {
742 "default_event"
743 }
744
745 async fn execute(&mut self, _: &Context) -> Result<Status> {
746 Ok(Status::done())
747 }
748
749 fn dump(&self) -> Result<String> {
750 Ok(String::new())
751 }
752
753 fn lock_key(&self) -> LockKey {
754 LockKey::default()
755 }
756 }
757
758 #[test]
759 fn test_default_procedure_event_hook() {
760 let state = ProcedureState::Running;
761 let context = EventContext {
762 procedure_id: ProcedureId::random(),
763 lifecycle_state: &state,
764 trigger: EventTrigger::Succeeded,
765 event_type_filter: Arc::new(common_event_recorder::EventTypeFilter::All),
766 event_context: None,
767 };
768
769 assert!(DefaultEventProcedure.event(&context).is_none());
770 assert!(Box::new(DefaultEventProcedure).event(&context).is_none());
771 }
772
773 #[test]
774 fn test_status() {
775 let status = Status::executing(false);
776 assert!(!status.need_persist());
777
778 let status = Status::executing(true);
779 assert!(status.need_persist());
780
781 let status = Status::executing_with_clean_poisons(false);
782 assert!(status.need_clean_poisons());
783
784 let status = Status::executing_with_clean_poisons(true);
785 assert!(status.need_clean_poisons());
786
787 let status = Status::Suspended {
788 subprocedures: Vec::new(),
789 persist: false,
790 };
791 assert!(!status.need_persist());
792
793 let status = Status::Suspended {
794 subprocedures: Vec::new(),
795 persist: true,
796 };
797 assert!(status.need_persist());
798
799 let status = Status::done();
800 assert!(!status.need_persist());
801 assert!(status.need_clean_poisons());
802 }
803
804 #[test]
805 fn test_lock_key() {
806 let entity = "catalog.schema.my_table";
807 let key = LockKey::single_exclusive(entity);
808 assert_eq!(
809 vec![&StringKey::Exclusive(entity.to_string())],
810 key.keys_to_lock().collect::<Vec<_>>()
811 );
812
813 let key = LockKey::new_exclusive([
814 "b".to_string(),
815 "c".to_string(),
816 "a".to_string(),
817 "c".to_string(),
818 ]);
819 assert_eq!(
820 vec![
821 &StringKey::Exclusive("a".to_string()),
822 &StringKey::Exclusive("b".to_string()),
823 &StringKey::Exclusive("c".to_string())
824 ],
825 key.keys_to_lock().collect::<Vec<_>>()
826 );
827 }
828
829 #[test]
830 fn test_procedure_id() {
831 let id = ProcedureId::random();
832 let uuid_str = id.to_string();
833 assert_eq!(id.0.to_string(), uuid_str);
834
835 let parsed = ProcedureId::parse_str(&uuid_str).unwrap();
836 assert_eq!(id, parsed);
837 let parsed = uuid_str.parse().unwrap();
838 assert_eq!(id, parsed);
839 }
840
841 #[test]
842 fn test_procedure_id_serialization() {
843 let id = ProcedureId::random();
844 let json = serde_json::to_string(&id).unwrap();
845 assert_eq!(format!("\"{id}\""), json);
846
847 let parsed = serde_json::from_str(&json).unwrap();
848 assert_eq!(id, parsed);
849 }
850
851 #[test]
852 fn test_procedure_state() {
853 assert!(ProcedureState::Running.is_running());
854 assert!(ProcedureState::Running.error().is_none());
855 assert!(ProcedureState::Done { output: None }.is_done());
856
857 let state = ProcedureState::failed(Arc::new(Error::external(MockError::new(
858 StatusCode::Unexpected,
859 ))));
860 assert!(state.is_failed());
861 let _ = state.error().unwrap();
862 }
863}