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, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
65pub enum SubprocedureRecovery {
66 #[default]
69 Discard,
70 Detach,
77}
78
79impl SubprocedureRecovery {
80 pub(crate) fn is_discard(&self) -> bool {
82 matches!(self, Self::Discard)
83 }
84}
85
86#[derive(Debug)]
88pub enum Status {
89 Executing {
91 persist: bool,
93 clean_poisons: bool,
95 },
96 Suspended {
98 subprocedures: Vec<ProcedureWithId>,
99 recovery: SubprocedureRecovery,
101 persist: bool,
103 },
104 Poisoned {
106 keys: PoisonKeys,
108 error: Error,
110 },
111 Done { output: Option<Output> },
113}
114
115impl Status {
116 pub fn suspended(subprocedures: Vec<ProcedureWithId>, persist: bool) -> Status {
119 Status::Suspended {
120 subprocedures,
121 recovery: SubprocedureRecovery::Discard,
122 persist,
123 }
124 }
125
126 pub fn poisoned(keys: impl IntoIterator<Item = PoisonKey>, error: Error) -> Status {
128 Status::Poisoned {
129 keys: PoisonKeys::new(keys),
130 error,
131 }
132 }
133
134 pub fn executing(persist: bool) -> Status {
136 Status::Executing {
137 persist,
138 clean_poisons: false,
139 }
140 }
141
142 pub fn executing_with_clean_poisons(persist: bool) -> Status {
144 Status::Executing {
145 persist,
146 clean_poisons: true,
147 }
148 }
149
150 pub fn done() -> Status {
152 Status::Done { output: None }
153 }
154
155 #[cfg(any(test, feature = "testing"))]
156 pub fn downcast_output_ref<T: 'static>(&self) -> Option<&T> {
161 if let Status::Done { output } = self {
162 output
163 .as_ref()
164 .expect("Try to downcast the output of Status::Done, but the output is None")
165 .downcast_ref()
166 } else {
167 panic!("Expected the Status::Done, but got: {:?}", self)
168 }
169 }
170
171 pub fn done_with_output<T: Any + Send + Sync>(output: T) -> Status {
173 Status::Done {
174 output: Some(Arc::new(output)),
175 }
176 }
177 pub fn is_done(&self) -> bool {
179 matches!(self, Status::Done { .. })
180 }
181
182 pub fn need_persist(&self) -> bool {
184 match self {
185 Status::Executing { persist, .. } | Status::Suspended { persist, .. } => *persist,
188 Status::Done { .. } | Status::Poisoned { .. } => false,
189 }
190 }
191
192 pub fn need_clean_poisons(&self) -> bool {
194 match self {
195 Status::Executing { clean_poisons, .. } => *clean_poisons,
196 Status::Done { .. } => true,
197 _ => false,
198 }
199 }
200}
201
202#[async_trait]
204pub trait ContextProvider: Send + Sync {
205 async fn procedure_state(&self, procedure_id: ProcedureId) -> Result<Option<ProcedureState>>;
207
208 async fn procedure_state_receiver(
209 &self,
210 procedure_id: ProcedureId,
211 ) -> Result<Option<Receiver<ProcedureState>>>;
212
213 async fn try_put_poison(&self, key: &PoisonKey, procedure_id: ProcedureId) -> Result<()>;
218
219 async fn acquire_lock(&self, key: &StringKey) -> DynamicKeyLockGuard;
221}
222
223pub type ContextProviderRef = Arc<dyn ContextProvider>;
225
226#[derive(Clone)]
228pub struct Context {
229 pub procedure_id: ProcedureId,
231 pub provider: ContextProviderRef,
233 pub event_context: Option<PersistentEventContext>,
235}
236
237impl Context {
238 pub async fn is_retrying(&self) -> Option<bool> {
240 self.provider
241 .procedure_state(self.procedure_id)
242 .await
243 .ok()
244 .flatten()
245 .map(|s| s.is_retrying())
246 }
247}
248
249#[async_trait]
251pub trait Procedure: Send {
252 fn type_name(&self) -> &str;
254
255 async fn execute(&mut self, ctx: &Context) -> Result<Status>;
259
260 async fn rollback(&mut self, _: &Context) -> Result<()> {
264 error::RollbackNotSupportedSnafu {}.fail()
265 }
266
267 fn rollback_supported(&self) -> bool {
269 false
270 }
271
272 fn dump(&self) -> Result<String>;
274
275 fn recover(&mut self) -> Result<()> {
277 Ok(())
278 }
279
280 fn lock_key(&self) -> LockKey;
282
283 fn poison_keys(&self) -> PoisonKeys {
285 PoisonKeys::default()
286 }
287
288 fn event(&self, _ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
298 None
299 }
300}
301
302pub struct EventContext<'a> {
304 pub procedure_id: ProcedureId,
306 pub lifecycle_state: &'a ProcedureState,
308 pub trigger: EventTrigger,
310 pub event_type_filter: EventTypeFilterRef,
312 pub event_context: Option<&'a PersistentEventContext>,
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
320#[serde(tag = "type")]
321pub enum EventTrigger {
322 Submitted,
324 Recovered,
326 ChildSubmitted {
328 procedure_id: ProcedureId,
330 outcome: ChildSubmissionOutcome,
332 },
333 Retrying {
335 phase: RetryPhase,
337 attempt: u32,
339 },
340 RollingBack,
342 Succeeded,
344 Failed,
346 Poisoned,
348}
349
350#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
352pub enum RetryPhase {
353 Execute,
355 Rollback,
357}
358
359#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
361pub enum ChildSubmissionOutcome {
362 Accepted,
363 AlreadyAccepted,
364 ManagerStopped,
365 SpawnFailed,
366}
367
368#[async_trait]
369impl<T: Procedure + ?Sized> Procedure for Box<T> {
370 fn type_name(&self) -> &str {
371 (**self).type_name()
372 }
373
374 async fn execute(&mut self, ctx: &Context) -> Result<Status> {
375 (**self).execute(ctx).await
376 }
377
378 async fn rollback(&mut self, ctx: &Context) -> Result<()> {
379 (**self).rollback(ctx).await
380 }
381
382 fn rollback_supported(&self) -> bool {
383 (**self).rollback_supported()
384 }
385
386 fn dump(&self) -> Result<String> {
387 (**self).dump()
388 }
389
390 fn lock_key(&self) -> LockKey {
391 (**self).lock_key()
392 }
393
394 fn poison_keys(&self) -> PoisonKeys {
395 (**self).poison_keys()
396 }
397
398 fn event(&self, ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
399 (**self).event(ctx)
400 }
401}
402
403#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
404pub struct PoisonKey(String);
405
406impl Display for PoisonKey {
407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408 write!(f, "{}", self.0)
409 }
410}
411
412impl PoisonKey {
413 pub fn new(key: impl Into<String>) -> Self {
415 Self(key.into())
416 }
417}
418
419#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
423pub struct PoisonKeys(SmallVec<[PoisonKey; 2]>);
424
425impl PoisonKeys {
426 pub fn single(key: impl Into<String>) -> Self {
428 Self(smallvec![PoisonKey::new(key)])
429 }
430
431 pub fn new(keys: impl IntoIterator<Item = PoisonKey>) -> Self {
433 Self(keys.into_iter().collect())
434 }
435
436 pub fn contains(&self, key: &PoisonKey) -> bool {
438 self.0.contains(key)
439 }
440
441 pub fn iter(&self) -> impl Iterator<Item = &PoisonKey> {
443 self.0.iter()
444 }
445}
446
447#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
448pub enum StringKey {
449 Share(String),
450 Exclusive(String),
451}
452
453#[derive(Clone, Debug, Default, PartialEq, Eq)]
459pub struct LockKey(SmallVec<[StringKey; 2]>);
460
461impl StringKey {
462 pub fn into_string(self) -> String {
463 match self {
464 StringKey::Share(s) => s,
465 StringKey::Exclusive(s) => s,
466 }
467 }
468
469 pub fn as_string(&self) -> &String {
470 match self {
471 StringKey::Share(s) => s,
472 StringKey::Exclusive(s) => s,
473 }
474 }
475}
476
477impl LockKey {
478 pub fn single(key: impl Into<StringKey>) -> LockKey {
480 LockKey(smallvec![key.into()])
481 }
482
483 pub fn single_exclusive(key: impl Into<String>) -> LockKey {
485 LockKey(smallvec![StringKey::Exclusive(key.into())])
486 }
487
488 pub fn new(iter: impl IntoIterator<Item = StringKey>) -> LockKey {
490 let mut vec: SmallVec<_> = iter.into_iter().collect();
491 vec.sort();
492 vec.dedup();
494 LockKey(vec)
495 }
496
497 pub fn new_exclusive(iter: impl IntoIterator<Item = String>) -> LockKey {
499 Self::new(iter.into_iter().map(StringKey::Exclusive))
500 }
501
502 pub fn keys_to_lock(&self) -> impl Iterator<Item = &StringKey> {
504 self.0.iter()
505 }
506
507 pub fn get_keys(&self) -> Vec<String> {
509 self.0.iter().map(|key| format!("{:?}", key)).collect()
510 }
511}
512
513pub type BoxedProcedure = Box<dyn Procedure>;
515
516pub struct ProcedureWithId {
518 pub id: ProcedureId,
520 pub procedure: BoxedProcedure,
521 pub context: ProcedureContext,
523}
524
525impl ProcedureWithId {
526 pub fn with_random_id(procedure: BoxedProcedure) -> ProcedureWithId {
529 ProcedureWithId {
530 id: ProcedureId::random(),
531 procedure,
532 context: ProcedureContext::default(),
533 }
534 }
535
536 pub fn with_context(mut self, context: ProcedureContext) -> Self {
538 self.context = context;
539 self
540 }
541}
542
543impl fmt::Debug for ProcedureWithId {
544 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
545 write!(f, "{}-{}", self.procedure.type_name(), self.id)
546 }
547}
548
549#[derive(Debug, Snafu)]
550pub struct ParseIdError {
551 source: uuid::Error,
552}
553
554#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
556pub struct ProcedureId(Uuid);
557
558impl ProcedureId {
559 pub fn random() -> ProcedureId {
561 ProcedureId(Uuid::new_v4())
562 }
563
564 pub fn parse_str(input: &str) -> std::result::Result<ProcedureId, ParseIdError> {
566 Uuid::parse_str(input)
567 .map(ProcedureId)
568 .context(ParseIdSnafu)
569 }
570}
571
572impl fmt::Display for ProcedureId {
573 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574 write!(f, "{}", self.0)
575 }
576}
577
578impl FromStr for ProcedureId {
579 type Err = ParseIdError;
580
581 fn from_str(s: &str) -> std::result::Result<ProcedureId, ParseIdError> {
582 ProcedureId::parse_str(s)
583 }
584}
585
586pub type BoxedProcedureLoader = Box<dyn Fn(&str) -> Result<BoxedProcedure> + Send>;
588
589#[derive(Debug, Default, Clone)]
591pub enum ProcedureState {
592 #[default]
594 Running,
595 Done { output: Option<Output> },
597 Retrying { error: Arc<Error> },
599 PrepareRollback { error: Arc<Error> },
601 RollingBack { error: Arc<Error> },
603 Failed { error: Arc<Error> },
605 Poisoned { keys: PoisonKeys, error: Arc<Error> },
607}
608
609impl ProcedureState {
610 pub fn failed(error: Arc<Error>) -> ProcedureState {
612 ProcedureState::Failed { error }
613 }
614
615 pub fn prepare_rollback(error: Arc<Error>) -> ProcedureState {
617 ProcedureState::PrepareRollback { error }
618 }
619
620 pub fn rolling_back(error: Arc<Error>) -> ProcedureState {
622 ProcedureState::RollingBack { error }
623 }
624
625 pub fn retrying(error: Arc<Error>) -> ProcedureState {
627 ProcedureState::Retrying { error }
628 }
629
630 pub fn poisoned(keys: PoisonKeys, error: Arc<Error>) -> ProcedureState {
632 ProcedureState::Poisoned { keys, error }
633 }
634
635 pub fn is_running(&self) -> bool {
637 matches!(self, ProcedureState::Running)
638 }
639
640 pub fn is_done(&self) -> bool {
642 matches!(self, ProcedureState::Done { .. })
643 }
644
645 pub fn is_poisoned(&self) -> bool {
647 matches!(self, ProcedureState::Poisoned { .. })
648 }
649
650 pub fn is_failed(&self) -> bool {
652 matches!(self, ProcedureState::Failed { .. })
653 }
654
655 pub fn is_retrying(&self) -> bool {
657 matches!(self, ProcedureState::Retrying { .. })
658 }
659
660 pub fn is_rolling_back(&self) -> bool {
662 matches!(self, ProcedureState::RollingBack { .. })
663 }
664
665 pub fn is_prepare_rollback(&self) -> bool {
667 matches!(self, ProcedureState::PrepareRollback { .. })
668 }
669
670 pub fn error(&self) -> Option<&Arc<Error>> {
672 match self {
673 ProcedureState::Failed { error } => Some(error),
674 ProcedureState::Retrying { error } => Some(error),
675 ProcedureState::RollingBack { error } => Some(error),
676 ProcedureState::Poisoned { error, .. } => Some(error),
677 _ => None,
678 }
679 }
680
681 pub fn as_str_name(&self) -> &str {
683 match self {
684 ProcedureState::Running => "Running",
685 ProcedureState::Done { .. } => "Done",
686 ProcedureState::Retrying { .. } => "Retrying",
687 ProcedureState::Failed { .. } => "Failed",
688 ProcedureState::PrepareRollback { .. } => "PrepareRollback",
689 ProcedureState::RollingBack { .. } => "RollingBack",
690 ProcedureState::Poisoned { .. } => "Poisoned",
691 }
692 }
693}
694
695#[derive(Debug, Clone)]
697pub enum InitProcedureState {
698 Running,
699 RollingBack,
700}
701
702#[async_trait]
705pub trait ProcedureManager: Send + Sync + 'static {
706 fn register_loader(&self, name: &str, loader: BoxedProcedureLoader) -> Result<()>;
708
709 async fn start(&self) -> Result<()>;
715
716 async fn stop(&self) -> Result<()>;
718
719 async fn submit(&self, procedure: ProcedureWithId) -> Result<Watcher>;
723
724 async fn procedure_state(&self, procedure_id: ProcedureId) -> Result<Option<ProcedureState>>;
728
729 fn procedure_watcher(&self, procedure_id: ProcedureId) -> Option<Watcher>;
731
732 async fn list_procedures(&self) -> Result<Vec<ProcedureInfo>>;
734
735 async fn has_unfinished_procedure(&self, type_names: &[&str]) -> Result<bool>;
739}
740
741pub type ProcedureManagerRef = Arc<dyn ProcedureManager>;
743
744#[derive(Debug, Clone)]
745pub struct ProcedureInfo {
746 pub id: ProcedureId,
748 pub type_name: String,
750 pub start_time_ms: i64,
752 pub end_time_ms: i64,
754 pub state: ProcedureState,
756 pub lock_keys: Vec<String>,
758}
759
760#[cfg(test)]
761mod tests {
762 use async_trait::async_trait;
763 use common_error::mock::MockError;
764 use common_error::status_code::StatusCode;
765
766 use super::*;
767
768 struct DefaultEventProcedure;
769
770 #[async_trait]
771 impl Procedure for DefaultEventProcedure {
772 fn type_name(&self) -> &str {
773 "default_event"
774 }
775
776 async fn execute(&mut self, _: &Context) -> Result<Status> {
777 Ok(Status::done())
778 }
779
780 fn dump(&self) -> Result<String> {
781 Ok(String::new())
782 }
783
784 fn lock_key(&self) -> LockKey {
785 LockKey::default()
786 }
787 }
788
789 #[test]
790 fn test_default_procedure_event_hook() {
791 let state = ProcedureState::Running;
792 let context = EventContext {
793 procedure_id: ProcedureId::random(),
794 lifecycle_state: &state,
795 trigger: EventTrigger::Succeeded,
796 event_type_filter: Arc::new(common_event_recorder::EventTypeFilter::All),
797 event_context: None,
798 };
799
800 assert!(DefaultEventProcedure.event(&context).is_none());
801 assert!(Box::new(DefaultEventProcedure).event(&context).is_none());
802 }
803
804 #[test]
805 fn test_status() {
806 let status = Status::executing(false);
807 assert!(!status.need_persist());
808
809 let status = Status::executing(true);
810 assert!(status.need_persist());
811
812 let status = Status::executing_with_clean_poisons(false);
813 assert!(status.need_clean_poisons());
814
815 let status = Status::executing_with_clean_poisons(true);
816 assert!(status.need_clean_poisons());
817
818 let status = Status::Suspended {
819 recovery: SubprocedureRecovery::Discard,
820 subprocedures: Vec::new(),
821 persist: false,
822 };
823 assert!(!status.need_persist());
824
825 let status = Status::Suspended {
826 recovery: SubprocedureRecovery::Discard,
827 subprocedures: Vec::new(),
828 persist: true,
829 };
830 assert!(status.need_persist());
831
832 let status = Status::done();
833 assert!(!status.need_persist());
834 assert!(status.need_clean_poisons());
835 }
836
837 #[test]
838 fn test_lock_key() {
839 let entity = "catalog.schema.my_table";
840 let key = LockKey::single_exclusive(entity);
841 assert_eq!(
842 vec![&StringKey::Exclusive(entity.to_string())],
843 key.keys_to_lock().collect::<Vec<_>>()
844 );
845
846 let key = LockKey::new_exclusive([
847 "b".to_string(),
848 "c".to_string(),
849 "a".to_string(),
850 "c".to_string(),
851 ]);
852 assert_eq!(
853 vec![
854 &StringKey::Exclusive("a".to_string()),
855 &StringKey::Exclusive("b".to_string()),
856 &StringKey::Exclusive("c".to_string())
857 ],
858 key.keys_to_lock().collect::<Vec<_>>()
859 );
860 }
861
862 #[test]
863 fn test_procedure_id() {
864 let id = ProcedureId::random();
865 let uuid_str = id.to_string();
866 assert_eq!(id.0.to_string(), uuid_str);
867
868 let parsed = ProcedureId::parse_str(&uuid_str).unwrap();
869 assert_eq!(id, parsed);
870 let parsed = uuid_str.parse().unwrap();
871 assert_eq!(id, parsed);
872 }
873
874 #[test]
875 fn test_procedure_id_serialization() {
876 let id = ProcedureId::random();
877 let json = serde_json::to_string(&id).unwrap();
878 assert_eq!(format!("\"{id}\""), json);
879
880 let parsed = serde_json::from_str(&json).unwrap();
881 assert_eq!(id, parsed);
882 }
883
884 #[test]
885 fn test_procedure_state() {
886 assert!(ProcedureState::Running.is_running());
887 assert!(ProcedureState::Running.error().is_none());
888 assert!(ProcedureState::Done { output: None }.is_done());
889
890 let state = ProcedureState::failed(Arc::new(Error::external(MockError::new(
891 StatusCode::Unexpected,
892 ))));
893 assert!(state.is_failed());
894 let _ = state.error().unwrap();
895 }
896}