Skip to main content

dtmrs_core/
lib.rs

1//! 类型与状态机 —— 纯逻辑,不碰 I/O。
2//!
3//! 分布式事务的 bug 绝大多数在状态迁移上,所以把这层从存储和网络里隔离出来,
4//! 可以纯单元测试覆盖。
5
6pub mod dialect;
7
8pub use dialect::Backend;
9
10use serde::{Deserialize, Serialize};
11use std::fmt;
12
13/// 分支被调用后的结论。**这四态的区分是整个系统的命门。**
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum BranchResult {
16    /// HTTP 200 —— 成功
17    Success,
18    /// HTTP 409 —— 业务**明确**要求回滚。只有这个才触发补偿
19    Failure,
20    /// HTTP 425 —— 还在处理中,别当失败
21    Ongoing,
22    /// 网络错误、超时、5xx —— 结果**未知**
23    ///
24    /// 绝不能当成失败:超时的时候对方可能已经成功了,贸然补偿会造成不一致。
25    /// 正确做法是重试,直到拿到 Success 或 Failure。
26    Unknown,
27}
28
29impl BranchResult {
30    /// 从 HTTP 状态码 + 响应体判定。响应体里的 `dtm_result` 字段优先于状态码,
31    /// 这样业务方用 200 返回 `{"dtm_result":"FAILURE"}` 也能表达失败。
32    pub fn from_http(status: u16, body: &str) -> Self {
33        if body.contains("FAILURE") {
34            return Self::Failure;
35        }
36        if body.contains("ONGOING") {
37            return Self::Ongoing;
38        }
39        match status {
40            200..=299 => Self::Success,
41            409 => Self::Failure,
42            425 => Self::Ongoing,
43            _ => Self::Unknown,
44        }
45    }
46
47    /// 从 gRPC 状态码判定。取值是 gRPC 规范里的标准编号,跟 DTM 的
48    /// `dtmgrpc` 对齐,这样两边的业务服务可以互换。
49    ///
50    /// # 这个映射为什么是这几个码
51    ///
52    /// HTTP 那边靠 409/425 表达「明确失败」和「还在处理」,gRPC 没有这两个码,
53    /// 得从 16 个标准码里各挑一个**不会被基础设施误用**的:
54    ///
55    /// | gRPC 码 | 语义 | 对应 HTTP |
56    /// |---|---|---|
57    /// | `OK`(0) | 成功 | 200 |
58    /// | `ABORTED`(10) | 业务**明确**要求回滚 | 409 |
59    /// | `FAILED_PRECONDITION`(9) | 还在处理,别当失败 | 425 |
60    /// | 其它全部 | 结果**未知**,重试 | 5xx / 超时 |
61    ///
62    /// 关键在最后一行。`UNAVAILABLE`(14)、`DEADLINE_EXCEEDED`(4)、`INTERNAL`(13)
63    /// 这些**都算未知**而不是失败 —— 它们恰恰是网络抖动和超时会产生的码,
64    /// 而超时的时候对方可能已经成功了。这跟 HTTP 侧「超时不等于失败」是同一条命门。
65    ///
66    /// 特别注意 `CANCELLED`(1) 和 `DEADLINE_EXCEEDED`(4):调用方自己取消/超时
67    /// 产生的码,绝不能当成业务失败 —— 那是**我们这边**放弃了,不是对方拒绝了。
68    pub fn from_grpc(code: i32) -> Self {
69        match code {
70            GRPC_OK => Self::Success,
71            GRPC_ABORTED => Self::Failure,
72            GRPC_FAILED_PRECONDITION => Self::Ongoing,
73            // 包括 CANCELLED / DEADLINE_EXCEEDED / UNAVAILABLE / INTERNAL / …
74            // 一律按未知处理:只重试,不回滚
75            _ => Self::Unknown,
76        }
77    }
78}
79
80/// gRPC 标准状态码。只列用得上的三个,其余一律走 `_ => Unknown`。
81pub const GRPC_OK: i32 = 0;
82/// 业务明确要求回滚。gRPC 侧的 409
83pub const GRPC_ABORTED: i32 = 10;
84/// 还在处理中。gRPC 侧的 425
85pub const GRPC_FAILED_PRECONDITION: i32 = 9;
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
88#[serde(rename_all = "lowercase")]
89pub enum TransType {
90    Saga,
91    Tcc,
92    Msg,
93    Xa,
94    /// 步骤由**用户函数在运行时决定**,靠重放 + 结果记忆化做崩溃恢复。
95    /// 见 [`workflow_advance`]
96    Workflow,
97}
98
99impl fmt::Display for TransType {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        let s = match self {
102            Self::Saga => "saga",
103            Self::Tcc => "tcc",
104            Self::Msg => "msg",
105            Self::Xa => "xa",
106            Self::Workflow => "workflow",
107        };
108        f.write_str(s)
109    }
110}
111
112impl TransType {
113    pub fn parse(s: &str) -> Option<Self> {
114        match s {
115            "saga" => Some(Self::Saga),
116            "tcc" => Some(Self::Tcc),
117            "msg" => Some(Self::Msg),
118            "xa" => Some(Self::Xa),
119            "workflow" => Some(Self::Workflow),
120            _ => None,
121        }
122    }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
126#[serde(rename_all = "lowercase")]
127pub enum GlobalStatus {
128    /// 仅二阶段消息用:TC 收到了但还不知道该不该执行
129    Prepared,
130    /// 可以推进
131    Submitted,
132    /// 需要回滚,正在逆序补偿
133    Aborting,
134    /// 终态
135    Succeed,
136    /// 终态
137    Failed,
138}
139
140impl GlobalStatus {
141    pub fn as_str(&self) -> &'static str {
142        match self {
143            Self::Prepared => "prepared",
144            Self::Submitted => "submitted",
145            Self::Aborting => "aborting",
146            Self::Succeed => "succeed",
147            Self::Failed => "failed",
148        }
149    }
150
151    pub fn parse(s: &str) -> Option<Self> {
152        match s {
153            "prepared" => Some(Self::Prepared),
154            "submitted" => Some(Self::Submitted),
155            "aborting" => Some(Self::Aborting),
156            "succeed" => Some(Self::Succeed),
157            "failed" => Some(Self::Failed),
158            _ => None,
159        }
160    }
161
162    /// 终态不再被 cron 调度
163    pub fn is_final(&self) -> bool {
164        matches!(self, Self::Succeed | Self::Failed)
165    }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
169#[serde(rename_all = "lowercase")]
170pub enum BranchStatus {
171    Prepared,
172    Succeed,
173    Failed,
174}
175
176impl BranchStatus {
177    pub fn as_str(&self) -> &'static str {
178        match self {
179            Self::Prepared => "prepared",
180            Self::Succeed => "succeed",
181            Self::Failed => "failed",
182        }
183    }
184    pub fn parse(s: &str) -> Option<Self> {
185        match s {
186            "prepared" => Some(Self::Prepared),
187            "succeed" => Some(Self::Succeed),
188            "failed" => Some(Self::Failed),
189            _ => None,
190        }
191    }
192}
193
194/// 分支操作类型。跟 DTM 的字符串保持一致,方便客户端互通。
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
196#[serde(rename_all = "lowercase")]
197pub enum BranchOp {
198    Action,
199    Compensate,
200    Try,
201    Confirm,
202    Cancel,
203    /// XA 的二阶段提交
204    Commit,
205    Rollback,
206}
207
208impl BranchOp {
209    pub fn as_str(&self) -> &'static str {
210        match self {
211            Self::Action => "action",
212            Self::Compensate => "compensate",
213            Self::Try => "try",
214            Self::Confirm => "confirm",
215            Self::Cancel => "cancel",
216            Self::Commit => "commit",
217            Self::Rollback => "rollback",
218        }
219    }
220
221    pub fn parse(s: &str) -> Option<Self> {
222        match s {
223            "action" => Some(Self::Action),
224            "compensate" => Some(Self::Compensate),
225            "try" => Some(Self::Try),
226            "confirm" => Some(Self::Confirm),
227            "cancel" => Some(Self::Cancel),
228            "commit" => Some(Self::Commit),
229            "rollback" => Some(Self::Rollback),
230            _ => None,
231        }
232    }
233
234    /// 补偿类操作对应的**正向**操作。屏障判空回滚要用。
235    pub fn origin_op(&self) -> Option<BranchOp> {
236        match self {
237            Self::Cancel => Some(Self::Try),
238            Self::Compensate => Some(Self::Action),
239            Self::Rollback => Some(Self::Action),
240            _ => None,
241        }
242    }
243
244    pub fn is_compensating(&self) -> bool {
245        self.origin_op().is_some()
246    }
247}
248
249/// 一个 SAGA 步骤:正向动作 + 对应补偿
250#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct SagaStep {
252    pub action: String,
253    pub compensate: String,
254}
255
256/// 推进全局事务后,状态机给出的下一步指令
257#[derive(Debug, Clone, PartialEq, Eq)]
258pub enum Advance {
259    /// 调这个分支
260    Call { index: usize, op: BranchOp },
261    /// 全部完成,落终态
262    Finish(GlobalStatus),
263    /// 有分支 Ongoing/Unknown,本轮到此为止,等下次 cron
264    Wait,
265    /// 跑一遍用户的 workflow 函数(只有 [`TransType::Workflow`] 会出现)。
266    ///
267    /// 单独一个变体而不是复用 `Call`:workflow 的正向走向是**用户函数**决定的,
268    /// 不是状态机决定的。混进 `Call` 会让人以为这里也能算出「下一个分支是谁」。
269    RunWorkflow,
270}
271
272/// SAGA 推进决策。**不碰 I/O,所以可以穷举测试。**
273///
274/// `actions[i]` / `compensates[i]` 是第 i 步两个分支各自的当前状态。
275pub fn saga_advance(
276    status: GlobalStatus,
277    actions: &[BranchStatus],
278    compensates: &[BranchStatus],
279) -> Advance {
280    debug_assert_eq!(actions.len(), compensates.len());
281    match status {
282        GlobalStatus::Submitted => {
283            // 正向:按序找第一个还没成功的
284            for (i, st) in actions.iter().enumerate() {
285                match st {
286                    BranchStatus::Succeed => continue,
287                    BranchStatus::Prepared => {
288                        return Advance::Call {
289                            index: i,
290                            op: BranchOp::Action,
291                        }
292                    }
293                    // 有分支被判失败,本该已经转 aborting;防御性处理
294                    BranchStatus::Failed => return Advance::Finish(GlobalStatus::Aborting),
295                }
296            }
297            Advance::Finish(GlobalStatus::Succeed)
298        }
299        GlobalStatus::Aborting => {
300            // 逆序补偿。**所有分支都补**,不管它的 action 成没成功 ——
301            // action 超时但实际成功的情况必须靠补偿兜住,多余的补偿由屏障空转掉。
302            for i in (0..compensates.len()).rev() {
303                if compensates[i] == BranchStatus::Prepared {
304                    return Advance::Call {
305                        index: i,
306                        op: BranchOp::Compensate,
307                    };
308                }
309            }
310            Advance::Finish(GlobalStatus::Failed)
311        }
312        GlobalStatus::Prepared => Advance::Wait,
313        s => Advance::Finish(s),
314    }
315}
316
317/// 指数退避:10s → 20s → 40s → … → 上限 300s
318pub fn next_interval(cur: i64) -> i64 {
319    const MAX: i64 = 300;
320    if cur <= 0 {
321        return 10;
322    }
323    (cur * 2).min(MAX)
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329    use BranchStatus::{Failed, Prepared, Succeed};
330
331    #[test]
332    fn 超时不能当失败() {
333        // 这条错了就会造成数据不一致:对方可能已经成功了
334        assert_eq!(BranchResult::from_http(504, ""), BranchResult::Unknown);
335        assert_eq!(BranchResult::from_http(500, ""), BranchResult::Unknown);
336        // 只有明确的 409 / FAILURE 才算失败
337        assert_eq!(BranchResult::from_http(409, ""), BranchResult::Failure);
338        assert_eq!(
339            BranchResult::from_http(200, r#"{"dtm_result":"FAILURE"}"#),
340            BranchResult::Failure
341        );
342        assert_eq!(BranchResult::from_http(425, ""), BranchResult::Ongoing);
343        assert_eq!(BranchResult::from_http(200, "ok"), BranchResult::Success);
344    }
345
346    #[test]
347    fn grpc只有aborted才算失败() {
348        assert_eq!(BranchResult::from_grpc(0), BranchResult::Success);
349        // 业务明确要求回滚 —— gRPC 侧唯一能触发补偿的码
350        assert_eq!(BranchResult::from_grpc(10), BranchResult::Failure);
351        assert_eq!(BranchResult::from_grpc(9), BranchResult::Ongoing);
352
353        // 穷举 gRPC 全部 16 个标准码:除了这三个,一律是 Unknown。
354        // 这条错了就会数据不一致 —— UNAVAILABLE / DEADLINE_EXCEEDED 正是
355        // 网络抖动和超时产生的码,当成失败去回滚,对方可能其实已经成功了。
356        for code in 0..=15 {
357            let want = match code {
358                0 => BranchResult::Success,
359                10 => BranchResult::Failure,
360                9 => BranchResult::Ongoing,
361                _ => BranchResult::Unknown,
362            };
363            assert_eq!(BranchResult::from_grpc(code), want, "gRPC 码 {code} 判错了");
364        }
365        // 几个最容易写错的,单独钉一遍
366        assert_eq!(
367            BranchResult::from_grpc(1),
368            BranchResult::Unknown,
369            "CANCELLED 是我们自己放弃,不是对方拒绝"
370        );
371        assert_eq!(
372            BranchResult::from_grpc(4),
373            BranchResult::Unknown,
374            "DEADLINE_EXCEEDED 绝不能当失败"
375        );
376        assert_eq!(
377            BranchResult::from_grpc(14),
378            BranchResult::Unknown,
379            "UNAVAILABLE 绝不能当失败"
380        );
381        // 不认识的码(未来扩展 / 对方乱返)也必须是 Unknown
382        assert_eq!(BranchResult::from_grpc(99), BranchResult::Unknown);
383        assert_eq!(BranchResult::from_grpc(-1), BranchResult::Unknown);
384    }
385
386    #[test]
387    fn grpc与http的判定语义一致() {
388        // 同一个业务意图,两种协议必须得到同一个结论 —— 否则同一个服务
389        // 换协议接入就会有不同的回滚行为
390        for (http, grpc) in [(200u16, 0i32), (409, 10), (425, 9), (500, 13), (503, 14)] {
391            assert_eq!(
392                BranchResult::from_http(http, ""),
393                BranchResult::from_grpc(grpc),
394                "HTTP {http} 与 gRPC {grpc} 应当判定一致"
395            );
396        }
397    }
398
399    #[test]
400    fn 正向按序推进() {
401        let a = [Prepared, Prepared];
402        let c = [Prepared, Prepared];
403        assert_eq!(
404            saga_advance(GlobalStatus::Submitted, &a, &c),
405            Advance::Call {
406                index: 0,
407                op: BranchOp::Action
408            }
409        );
410        let a = [Succeed, Prepared];
411        assert_eq!(
412            saga_advance(GlobalStatus::Submitted, &a, &c),
413            Advance::Call {
414                index: 1,
415                op: BranchOp::Action
416            }
417        );
418        let a = [Succeed, Succeed];
419        assert_eq!(
420            saga_advance(GlobalStatus::Submitted, &a, &c),
421            Advance::Finish(GlobalStatus::Succeed)
422        );
423    }
424
425    #[test]
426    fn 补偿必须逆序() {
427        let a = [Succeed, Failed];
428        let c = [Prepared, Prepared];
429        // 先补第 1 步(后执行的先回滚)
430        assert_eq!(
431            saga_advance(GlobalStatus::Aborting, &a, &c),
432            Advance::Call {
433                index: 1,
434                op: BranchOp::Compensate
435            }
436        );
437        let c = [Prepared, Succeed];
438        assert_eq!(
439            saga_advance(GlobalStatus::Aborting, &a, &c),
440            Advance::Call {
441                index: 0,
442                op: BranchOp::Compensate
443            }
444        );
445        let c = [Succeed, Succeed];
446        assert_eq!(
447            saga_advance(GlobalStatus::Aborting, &a, &c),
448            Advance::Finish(GlobalStatus::Failed)
449        );
450    }
451
452    #[test]
453    fn 没跑过的分支也要补偿() {
454        // action 全没成功,补偿照样得发 —— 因为 action 可能超时但实际成功了。
455        // 多余的补偿由子事务屏障空转掉,这是安全的一侧。
456        let a = [Prepared, Prepared];
457        let c = [Prepared, Prepared];
458        assert_eq!(
459            saga_advance(GlobalStatus::Aborting, &a, &c),
460            Advance::Call {
461                index: 1,
462                op: BranchOp::Compensate
463            }
464        );
465    }
466
467    #[test]
468    fn 终态不再推进() {
469        for s in [GlobalStatus::Succeed, GlobalStatus::Failed] {
470            assert!(s.is_final());
471            assert_eq!(saga_advance(s, &[], &[]), Advance::Finish(s));
472        }
473    }
474
475    #[test]
476    fn 补偿操作能找到正向操作() {
477        assert_eq!(BranchOp::Compensate.origin_op(), Some(BranchOp::Action));
478        assert_eq!(BranchOp::Cancel.origin_op(), Some(BranchOp::Try));
479        assert_eq!(BranchOp::Action.origin_op(), None);
480        assert!(BranchOp::Compensate.is_compensating());
481        assert!(!BranchOp::Try.is_compensating());
482    }
483
484    #[test]
485    fn 退避有上限() {
486        assert_eq!(next_interval(0), 10);
487        assert_eq!(next_interval(10), 20);
488        assert_eq!(next_interval(200), 300);
489        assert_eq!(next_interval(300), 300);
490    }
491}
492
493/// TCC 推进决策。
494///
495/// # 跟 SAGA 的关键差别
496///
497/// SAGA 的正向分支(action)返回 FAILURE 会触发逆序补偿。
498/// **TCC 的 confirm 返回 FAILURE 绝不能触发 cancel** —— try 阶段资源已经预留成功、
499/// 全局也已经决定提交了,这时候去 cancel 会把已确认的事务撤掉,造成不一致。
500/// confirm 失败的唯一正确处理是**无限重试 + 报警等人介入**。
501///
502/// 所以这个函数在 Submitted 阶段永远不会返回 `Finish(Aborting)`。
503///
504/// Try 阶段不在这里 —— TCC 的 try 是**客户端自己驱动**的(这也是 TCC 要
505/// `registerBranch` 接口的原因),TC 只负责 confirm/cancel。
506pub fn tcc_advance(
507    status: GlobalStatus,
508    confirms: &[BranchStatus],
509    cancels: &[BranchStatus],
510) -> Advance {
511    debug_assert_eq!(confirms.len(), cancels.len());
512    match status {
513        // 客户端还在跑 try,TC 不插手
514        GlobalStatus::Prepared => Advance::Wait,
515        GlobalStatus::Submitted => {
516            for (i, st) in confirms.iter().enumerate() {
517                match st {
518                    BranchStatus::Succeed => continue,
519                    // Failed 也要继续重试 —— 见上面注释,绝不转 aborting
520                    BranchStatus::Prepared | BranchStatus::Failed => {
521                        return Advance::Call {
522                            index: i,
523                            op: BranchOp::Confirm,
524                        }
525                    }
526                }
527            }
528            Advance::Finish(GlobalStatus::Succeed)
529        }
530        GlobalStatus::Aborting => {
531            // 逆序 cancel。全部 cancel,空回滚由屏障负责
532            for i in (0..cancels.len()).rev() {
533                if cancels[i] != BranchStatus::Succeed {
534                    return Advance::Call {
535                        index: i,
536                        op: BranchOp::Cancel,
537                    };
538                }
539            }
540            Advance::Finish(GlobalStatus::Failed)
541        }
542        s => Advance::Finish(s),
543    }
544}
545
546/// 二阶段消息推进决策。
547///
548/// # 这个模式解决什么
549///
550/// "本地事务 + 可靠消息" —— 取代 RocketMQ 那类事务消息,不需要 MQ。
551/// 流程:`prepare` 落库 → 业务提交本地事务 → `submit`。
552/// 如果进程在两者之间崩了,TC 会回查业务方(`query_prepared`)问这单到底成没成。
553///
554/// # 没有补偿
555///
556/// msg 只保证"最终一定送达",分支必须最终成功(幂等 + 无限重试)。
557/// 所以正向分支返回 FAILURE **不触发补偿**(压根没有补偿分支),
558/// 只能重试。真要放弃只能靠 `query_prepared` 回答 FAILURE 让整单作废。
559pub fn msg_advance(status: GlobalStatus, actions: &[BranchStatus]) -> Advance {
560    match status {
561        // 等 cron 去回查 query_prepared
562        GlobalStatus::Prepared => Advance::Wait,
563        GlobalStatus::Submitted => {
564            for (i, st) in actions.iter().enumerate() {
565                if *st != BranchStatus::Succeed {
566                    return Advance::Call {
567                        index: i,
568                        op: BranchOp::Action,
569                    };
570                }
571            }
572            Advance::Finish(GlobalStatus::Succeed)
573        }
574        // 回查得到 FAILURE:整单作废,没有补偿可做
575        GlobalStatus::Aborting => Advance::Finish(GlobalStatus::Failed),
576        s => Advance::Finish(s),
577    }
578}
579
580#[cfg(test)]
581mod tcc_msg_tests {
582    use super::*;
583    use BranchStatus::{Failed, Prepared, Succeed};
584
585    #[test]
586    fn tcc的try阶段tc不插手() {
587        assert_eq!(
588            tcc_advance(GlobalStatus::Prepared, &[Prepared], &[Prepared]),
589            Advance::Wait
590        );
591    }
592
593    #[test]
594    fn tcc按序confirm() {
595        let c = [Prepared, Prepared];
596        let x = [Prepared, Prepared];
597        assert_eq!(
598            tcc_advance(GlobalStatus::Submitted, &c, &x),
599            Advance::Call {
600                index: 0,
601                op: BranchOp::Confirm
602            }
603        );
604        assert_eq!(
605            tcc_advance(GlobalStatus::Submitted, &[Succeed, Prepared], &x),
606            Advance::Call {
607                index: 1,
608                op: BranchOp::Confirm
609            }
610        );
611        assert_eq!(
612            tcc_advance(GlobalStatus::Submitted, &[Succeed, Succeed], &x),
613            Advance::Finish(GlobalStatus::Succeed)
614        );
615    }
616
617    #[test]
618    fn confirm失败绝不能触发cancel() {
619        // 这是 TCC 最容易写错的地方:try 已成功、已决定提交,
620        // 这时候 cancel 会把已确认的事务撤掉 —— 必须重试而不是回滚
621        let c = [Succeed, Failed];
622        let x = [Prepared, Prepared];
623        assert_eq!(
624            tcc_advance(GlobalStatus::Submitted, &c, &x),
625            Advance::Call {
626                index: 1,
627                op: BranchOp::Confirm
628            },
629            "confirm 失败要继续重试 confirm,不能转 cancel"
630        );
631        // 穷举:Submitted 阶段永远不会返回 Aborting
632        for a in [Prepared, Succeed, Failed] {
633            for b in [Prepared, Succeed, Failed] {
634                let r = tcc_advance(GlobalStatus::Submitted, &[a, b], &x);
635                assert_ne!(r, Advance::Finish(GlobalStatus::Aborting));
636                assert_ne!(r, Advance::Finish(GlobalStatus::Failed));
637            }
638        }
639    }
640
641    #[test]
642    fn tcc逆序cancel() {
643        let c = [Prepared, Prepared];
644        assert_eq!(
645            tcc_advance(GlobalStatus::Aborting, &c, &[Prepared, Prepared]),
646            Advance::Call {
647                index: 1,
648                op: BranchOp::Cancel
649            }
650        );
651        assert_eq!(
652            tcc_advance(GlobalStatus::Aborting, &c, &[Prepared, Succeed]),
653            Advance::Call {
654                index: 0,
655                op: BranchOp::Cancel
656            }
657        );
658        assert_eq!(
659            tcc_advance(GlobalStatus::Aborting, &c, &[Succeed, Succeed]),
660            Advance::Finish(GlobalStatus::Failed)
661        );
662        // cancel 失败也要重试,不能就这么算了
663        assert_eq!(
664            tcc_advance(GlobalStatus::Aborting, &c, &[Succeed, Failed]),
665            Advance::Call {
666                index: 1,
667                op: BranchOp::Cancel
668            }
669        );
670    }
671
672    #[test]
673    fn msg等回查而不是自己推() {
674        assert_eq!(
675            msg_advance(GlobalStatus::Prepared, &[Prepared]),
676            Advance::Wait
677        );
678    }
679
680    #[test]
681    fn msg只往前不补偿() {
682        assert_eq!(
683            msg_advance(GlobalStatus::Submitted, &[Prepared, Prepared]),
684            Advance::Call {
685                index: 0,
686                op: BranchOp::Action
687            }
688        );
689        // 分支失败也只能重试 —— msg 没有补偿分支
690        assert_eq!(
691            msg_advance(GlobalStatus::Submitted, &[Failed]),
692            Advance::Call {
693                index: 0,
694                op: BranchOp::Action
695            }
696        );
697        assert_eq!(
698            msg_advance(GlobalStatus::Submitted, &[Succeed, Succeed]),
699            Advance::Finish(GlobalStatus::Succeed)
700        );
701        // 回查得到 FAILURE → 整单作废,无补偿可做
702        assert_eq!(
703            msg_advance(GlobalStatus::Aborting, &[Succeed]),
704            Advance::Finish(GlobalStatus::Failed)
705        );
706    }
707}
708
709/// XA 推进决策。
710///
711/// # 跟 TCC 同一条铁律
712///
713/// 分支一旦 `PREPARE TRANSACTION` 成功、全局又决定了提交,**commit 失败绝不能
714/// 转成 rollback** —— 别的分支可能已经 COMMIT PREPARED 了,这时候回滚就是
715/// 一半提交一半回滚。只能无限重试 + 报警。
716///
717/// 所以跟 `tcc_advance` 一样,Submitted 阶段永远不返回 `Finish(Aborting)`。
718///
719/// # XA 独有的危险
720///
721/// 已 prepare 未解决的事务会**一直持有锁**,在 Postgres 里还会阻塞 VACUUM
722/// 导致事务 ID 回卷风险。所以 XA 的 commit/rollback 必须最终送达 ——
723/// 这比 SAGA/TCC 的"补偿没跑成"严重得多。运维上要监控 `pg_prepared_xacts`。
724pub fn xa_advance(
725    status: GlobalStatus,
726    commits: &[BranchStatus],
727    rollbacks: &[BranchStatus],
728) -> Advance {
729    debug_assert_eq!(commits.len(), rollbacks.len());
730    match status {
731        // 客户端还在各分支上跑业务 SQL + PREPARE,TC 不插手
732        GlobalStatus::Prepared => Advance::Wait,
733        GlobalStatus::Submitted => {
734            for (i, st) in commits.iter().enumerate() {
735                if *st != BranchStatus::Succeed {
736                    return Advance::Call {
737                        index: i,
738                        op: BranchOp::Commit,
739                    };
740                }
741            }
742            Advance::Finish(GlobalStatus::Succeed)
743        }
744        GlobalStatus::Aborting => {
745            for i in (0..rollbacks.len()).rev() {
746                if rollbacks[i] != BranchStatus::Succeed {
747                    return Advance::Call {
748                        index: i,
749                        op: BranchOp::Rollback,
750                    };
751                }
752            }
753            Advance::Finish(GlobalStatus::Failed)
754        }
755        s => Advance::Finish(s),
756    }
757}
758
759/// workflow 推进决策。
760///
761/// # 这个模式跟前四种的结构性差别
762///
763/// SAGA / TCC / msg / XA 的步骤都是**提前声明**的,所以状态机能算出「下一步调谁」。
764/// workflow 反过来:步骤是**用户函数在运行时决定**的 —— 可以有 `if`、有循环、
765/// 有依赖前一步返回值的分叉。这是它存在的全部理由,也是它没法被本函数算出来的原因。
766///
767/// 所以这里只拥有三件事(这三件仍然可以穷举测试):
768///
769/// 1. 什么时候该跑那个函数(Submitted → [`Advance::RunWorkflow`])
770/// 2. 什么时候该补偿、按什么顺序(Aborting → 逆序)
771/// 3. 什么时候落终态
772///
773/// 「下一步调谁」交给用户函数,靠**重放 + 结果记忆化**保证崩溃后不重做。
774///
775/// # 补偿为什么能复用逆序那套
776///
777/// 用户函数每跑到一个分支就**动态登记**它的补偿(跟 TCC 的 `registerBranch`
778/// 一个形状),登记的行落在 `trans_branch_op` 里。所以回滚阶段跟 SAGA 完全一样:
779/// 逆序扫补偿行。区别只是这些行是运行时长出来的,不是提交时一次性写好的。
780///
781/// # 只补偿「登记过」的分支
782///
783/// 跟 SAGA「补偿所有分支」看着不同,其实是同一条规则:没跑到的分支压根没登记,
784/// 也就没有副作用要收拾。**关键在于补偿必须先于正向动作登记** ——
785/// 这样即使正向动作超时或进程当场崩了,补偿也已经在库里了,不会漏。
786/// 这跟 TCC「必须先 registerBranch 再调 try」是同一条教训。
787pub fn workflow_advance(status: GlobalStatus, compensates: &[BranchStatus]) -> Advance {
788    match status {
789        // workflow 没有 prepare 阶段,出现就是数据有问题,别乱动
790        GlobalStatus::Prepared => Advance::Wait,
791        GlobalStatus::Submitted => Advance::RunWorkflow,
792        GlobalStatus::Aborting => {
793            // 逆序补偿。失败的也要重试 —— 补偿没跑成就是真的漏了副作用
794            for i in (0..compensates.len()).rev() {
795                if compensates[i] != BranchStatus::Succeed {
796                    return Advance::Call {
797                        index: i,
798                        op: BranchOp::Compensate,
799                    };
800                }
801            }
802            Advance::Finish(GlobalStatus::Failed)
803        }
804        s => Advance::Finish(s),
805    }
806}
807
808#[cfg(test)]
809mod workflow_tests {
810    use super::*;
811    use BranchStatus::{Failed, Prepared, Succeed};
812
813    #[test]
814    fn submitted就是去跑函数() {
815        assert_eq!(
816            workflow_advance(GlobalStatus::Submitted, &[]),
817            Advance::RunWorkflow
818        );
819        // 已经登记了几个分支也一样 —— 该不该跑下一步是函数自己的事,
820        // 重放时靠记忆化跳过已完成的
821        assert_eq!(
822            workflow_advance(GlobalStatus::Submitted, &[Succeed, Prepared]),
823            Advance::RunWorkflow
824        );
825    }
826
827    #[test]
828    fn 回滚时逆序补偿() {
829        assert_eq!(
830            workflow_advance(GlobalStatus::Aborting, &[Prepared, Prepared]),
831            Advance::Call {
832                index: 1,
833                op: BranchOp::Compensate
834            },
835            "后执行的先回滚"
836        );
837        assert_eq!(
838            workflow_advance(GlobalStatus::Aborting, &[Prepared, Succeed]),
839            Advance::Call {
840                index: 0,
841                op: BranchOp::Compensate
842            }
843        );
844        assert_eq!(
845            workflow_advance(GlobalStatus::Aborting, &[Succeed, Succeed]),
846            Advance::Finish(GlobalStatus::Failed)
847        );
848        // 补偿失败要接着重试,不能就这么算了 —— 那是真的漏了副作用
849        assert_eq!(
850            workflow_advance(GlobalStatus::Aborting, &[Succeed, Failed]),
851            Advance::Call {
852                index: 1,
853                op: BranchOp::Compensate
854            }
855        );
856    }
857
858    #[test]
859    fn 一个分支都没登记就回滚是直接失败() {
860        // 函数第一步就要求回滚,还没来得及登记任何补偿 —— 没有副作用要收拾
861        assert_eq!(
862            workflow_advance(GlobalStatus::Aborting, &[]),
863            Advance::Finish(GlobalStatus::Failed)
864        );
865    }
866
867    #[test]
868    fn 终态不再推进() {
869        for s in [GlobalStatus::Succeed, GlobalStatus::Failed] {
870            assert_eq!(workflow_advance(s, &[]), Advance::Finish(s));
871        }
872    }
873
874    #[test]
875    fn workflow是一种事务类型() {
876        assert_eq!(TransType::parse("workflow"), Some(TransType::Workflow));
877        assert_eq!(TransType::Workflow.to_string(), "workflow");
878    }
879}
880
881#[cfg(test)]
882mod xa_tests {
883    use super::*;
884    use BranchStatus::{Failed, Prepared, Succeed};
885
886    #[test]
887    fn xa的prepare阶段tc不插手() {
888        // 各分支的业务 SQL + PREPARE TRANSACTION 都是客户端自己做的
889        assert_eq!(
890            xa_advance(GlobalStatus::Prepared, &[Prepared], &[Prepared]),
891            Advance::Wait
892        );
893    }
894
895    #[test]
896    fn xa按序commit() {
897        let r = [Prepared, Prepared];
898        assert_eq!(
899            xa_advance(GlobalStatus::Submitted, &[Prepared, Prepared], &r),
900            Advance::Call {
901                index: 0,
902                op: BranchOp::Commit
903            }
904        );
905        assert_eq!(
906            xa_advance(GlobalStatus::Submitted, &[Succeed, Prepared], &r),
907            Advance::Call {
908                index: 1,
909                op: BranchOp::Commit
910            }
911        );
912        assert_eq!(
913            xa_advance(GlobalStatus::Submitted, &[Succeed, Succeed], &r),
914            Advance::Finish(GlobalStatus::Succeed)
915        );
916    }
917
918    #[test]
919    fn commit失败绝不能转rollback() {
920        // 别的分支可能已经 COMMIT PREPARED 了,这时候回滚就是一半提交一半回滚
921        let r = [Prepared, Prepared];
922        assert_eq!(
923            xa_advance(GlobalStatus::Submitted, &[Succeed, Failed], &r),
924            Advance::Call {
925                index: 1,
926                op: BranchOp::Commit
927            },
928            "commit 失败要继续重试 commit"
929        );
930        // 穷举:Submitted 阶段永远不会走向回滚或失败
931        for a in [Prepared, Succeed, Failed] {
932            for b in [Prepared, Succeed, Failed] {
933                let got = xa_advance(GlobalStatus::Submitted, &[a, b], &r);
934                assert_ne!(got, Advance::Finish(GlobalStatus::Aborting));
935                assert_ne!(got, Advance::Finish(GlobalStatus::Failed));
936            }
937        }
938    }
939
940    #[test]
941    fn xa逆序rollback且失败也要重试() {
942        let c = [Prepared, Prepared];
943        assert_eq!(
944            xa_advance(GlobalStatus::Aborting, &c, &[Prepared, Prepared]),
945            Advance::Call {
946                index: 1,
947                op: BranchOp::Rollback
948            }
949        );
950        assert_eq!(
951            xa_advance(GlobalStatus::Aborting, &c, &[Succeed, Succeed]),
952            Advance::Finish(GlobalStatus::Failed)
953        );
954        // rollback 失败也不能就这么算了 —— 那会留下永久持锁的 prepared 事务
955        assert_eq!(
956            xa_advance(GlobalStatus::Aborting, &c, &[Succeed, Failed]),
957            Advance::Call {
958                index: 1,
959                op: BranchOp::Rollback
960            }
961        );
962    }
963
964    #[test]
965    fn commit操作没有反向映射() {
966        // XA 的 commit 不是补偿类操作,屏障不该给它做空回滚判定
967        assert_eq!(BranchOp::Commit.origin_op(), None);
968        assert!(!BranchOp::Commit.is_compensating());
969        assert_eq!(BranchOp::parse("commit"), Some(BranchOp::Commit));
970        assert_eq!(BranchOp::Commit.as_str(), "commit");
971    }
972}