Upgrade to Pro — share decks privately, control downloads, hide ads and more …

分散システム、本当に「正しく」開発できますか? #JTF2018 / July Tech Fe...

分散システム、本当に「正しく」開発できますか? #JTF2018 / July Tech Festa 2018

July Tech Festa 2018 で使用したスライドです。二相コミットを例として、分散アルゴリズムの検証にモデル検査を使用する手法について解説しています。また、代表的なモデル検査ツールである SPIN、TLA+、P について、同じシステムを各ツールで記述してみることでその特定の違いについて学びます。

イベント概要:https://2018.techfesta.jp/speakers#C40
ブログ記事:http://ccvanishing.hateblo.jp/entry/2018/08/01/055608

y_taka_23

July 29, 2018
Tweet

More Decks by y_taka_23

Other Decks in Technology

Transcript

  1. 目次 • 数理的手法による検証とは ◦ 特にモデル検査の長所と短所 • 検証のための理論と枠組み ◦ システムの挙動をどうやって表現するのか ◦

    検査したい仕様をどうやって表現するのか • 検証ツール各種の記述方法の比較 ◦ SPIN、TLA+、P それぞれの特徴
  2. 分散システムはテストしづらい • 各プロセスが非同期的に動く ◦ 考えるべきパターンが多すぎる • プロセス間で通信が発生 ◦ パケットの遅延・消失・破損が起こりうる •

    仕様そのものが複雑になりがち ◦ 瞬間ではなく、一連の挙動が問題になる ◦ もし仕様バグがあっても発見しづらい
  3. 形式手法の分類 • 定理証明 ◦ 数学的な証明を人の手でプログラムとして表現 ◦ 代表的なツール : Coq、Agda、Isabelle •

    モデル検査 ◦ システムが取りうる状態を総当たり探索 ◦ 有限のパターンしか保証できないが自動化できる ◦ 代表的なツール : SPIN、TLA+、P、Alloy
  4. Fixpoint rev (xs : list bool) : list bool :=

    match xs with | nil => nil | x :: xs’ => rev xs’ ++ (x :: nil) end. 実装とテストしたい仕様
  5. Fixpoint rev (xs : list bool) : list bool :=

    match xs with | nil => nil | x :: xs’ => rev xs’ ++ (x :: nil) end. (* rev (rev xs) = xs ? *) 実装とテストしたい仕様
  6. Fixpoint rev (xs : list bool) : list bool :=

    match xs with | nil => nil | x :: xs’ => rev xs’ ++ (x :: nil) end. (* rev (rev xs) = xs ? *) rev (rev []) = [] rev (rev [true]) = [true] rev (rev [true; false]) = [true; false] rev (rev [true; true; false]) = [true; true; false] ... 実装とテストしたい仕様
  7. Theorem rev_involutive : forall xs : list bool, rev (rev

    xs) = xs. Proof. Qed. 数学的帰納法による証明
  8. Theorem rev_involutive : forall xs : list bool, rev (rev

    xs) = xs. Proof. intro. induction xs [| x xs’ xs’_is_ok]. simpl. apply eq_refl. Qed. 数学的帰納法による証明 xs = nil の場合、 直接計算すれば = が成り立つ
  9. Theorem rev_involutive : forall xs : list bool, rev (rev

    xs) = xs. Proof. intro. induction xs [| x xs’ xs’_is_ok]. simpl. apply eq_refl. simpl. rewrite rev_app_distr. simpl. rewrite xs’_is_ok. apply eq_refl. Qed. 数学的帰納法による証明 xs = x :: xs’ の場合、 xs’ では = が成り立つとして 式変形を行う
  10. [] [true] [false] [true; true] [true; false] [false; true] [false;

    false] [true; true; true] [true; true; false] [true; false; true] [false; true; true] [true; false; false] [false; true; false] [false; false; true] [false; false; false] 長さ 3 以下の範囲で自動全探索
  11. rev (rev []) rev (rev [true]) rev (rev [false]) rev

    (rev [true; true]) rev (rev [true; false]) rev (rev [false; true]) rev (rev [false; false]) rev (rev [true; true; true]) rev (rev [true; true; false]) rev (rev [true; false; true]) rev (rev [false; true; true]) rev (rev [true; false; false]) rev (rev [false; true; false]) rev (rev [false; false; true]) rev (rev [false; false; false]) 長さ 3 以下の範囲で自動全探索
  12. rev (rev []) = [] rev (rev [true]) = [true]

    rev (rev [false]) = [false] rev (rev [true; true]) = [true; true] rev (rev [true; false]) = [true; false] rev (rev [false; true]) = [false; true] rev (rev [false; false]) = [false; false] rev (rev [true; true; true]) = [true; true; true] rev (rev [true; true; false]) = [true; true; false] rev (rev [true; false; true]) = [true; false; true] rev (rev [false; true; true]) = [false; true; true] rev (rev [true; false; false]) = [true; false; false] rev (rev [false; true; false]) = [false; true; false] rev (rev [false; false; true]) = [false; false; true] rev (rev [false; false; false]) = [false; false; false] 長さ 3 以下の範囲で自動全探索
  13. 第 1 章のまとめ • 分散システムには特有の難しさがある ◦ 考えるべきパターンが多く、再現性がない • 形式手法による検証は大きく分けて二種類 ◦

    定理証明:手動・無限のパターンに対して保証 ◦ モデル検査:自動・特定の範囲に絞り全探索 • 今回はモデル検査に焦点を当てる ◦ 分散システムの状態を「上手く」探索したい
  14. 二相コミットのアルゴリズム (1) • TM (Transaction Manager) の視点 ◦ RM から

    prepared 報告を受け取ったら記録する ◦ RM に対し Commit 命令または Abort 命令を出す ◦ Commit 命令は全 RM からの prepared 報告後のみ ◦ Abort 命令はいつでも出せる ◦ 命令はブロードキャストで、相手 RM は選べない ◦ RM との通信は到達に時間がかかる
  15. 二相コミットのアルゴリズム (2) • RM (Resource Manager) の視点 ◦ 他の RM

    とは一切通信できない ◦ 変更をコミットしたい時は TM に prepared を報告 ◦ コミット前に突然 aborted 状態になりうる ◦ TM からのCommit 命令を受け取ると変更をコミット ◦ TM からの Abort 命令を受け取ると変更を破棄 ◦ TM との通信は到達に時間がかかる
  16. TM RM1 RM1 : -- RM2 : -- RM3 :

    -- prepared RM2 RM3
  17. TM RM1 RM1 : prepared RM2 : -- RM3 :

    prepared prepared prepared RM2 RM3
  18. TM RM1 RM1 : prepared RM2 : prepared RM3 :

    prepared prepared prepared prepared RM2 RM3
  19. TM RM1 RM1 : prepared RM2 : prepared RM3 :

    prepared prepared prepared prepared RM2 RM3
  20. TM : Commit RM1 RM1 : prepared RM2 : prepared

    RM3 : prepared prepared prepared prepared RM2 RM3
  21. TM : Commit RM1 RM1 : prepared RM2 : prepared

    RM3 : prepared prepared committed prepared RM2 RM3
  22. TM : Commit RM1 RM1 : prepared RM2 : prepared

    RM3 : prepared prepared committed committed RM2 RM3
  23. TM : Commit RM1 RM1 : prepared RM2 : prepared

    RM3 : prepared committed committed committed RM2 RM3
  24. TM : Commit RM1 RM1 : prepared RM2 : prepared

    RM3 : prepared committed prepared aborted RM2 RM3 inconsistency!
  25. RM1: working RM2: working RM1: working RM2: prepared RM1: aborted

    RM2: working RM1: working RM2: aborted RM1: prepared RM2: working
  26. RM1: working RM2: working RM1: working RM2: prepared RM1: aborted

    RM2: working RM1: working RM2: aborted RM1: prepared RM2: working RM1 : working -> prepared
  27. RM1: working RM2: working RM1: working RM2: prepared RM1: aborted

    RM2: working RM1: working RM2: aborted RM1: prepared RM2: working RM2 : working -> aborted
  28. オートマトンの非同期積 • 二つのオートマトンを並列に動かす ◦ 分散システムの「妥当な」形式化になっている • 積も再びオートマトンになる ◦ 状態:両方のオートマトンの状態の組 ◦

    遷移:どちらか片方のオートマトンの遷移 • システムをモジュール化して扱える ◦ 部分を次々と合成することで全体の挙動が得られる
  29. TM: init RM1: working RM2: working RM3: working TM: init

    RM1: prepared RM2: working RM3: working TM: committed RM1: committed RM2: working RM3: aborted
  30. TM: init RM1: working RM2: working RM3: working TM: init

    RM1: prepared RM2: working RM3: working TM: committed RM1: committed RM2: working RM3: aborted
  31. TM: init RM1: working RM2: working RM3: working TM: init

    RM1: prepared RM2: working RM3: working TM: committed RM1: committed RM2: working RM3: aborted
  32. 線形時相論理(LTL) • 通常の論理式に記号を追加した体系 ◦ [] A : その時点以降、常に A が成立

    ◦ <> A : その時点以降、いつかは A が成立 ◦ X A : その時点のすぐ次の時点で A が成立 • 状態の「列」に対して真偽を判定する ◦ システムの実行パスに対して条件が記述できる • Büchi オートマトンに変換して検査できる
  33. 第 2 章のまとめ • オートマトンによるシステム記述 ◦ それぞれのプロセスをオートマトンで表す ◦ さらにその非同期積を取る ◦

    積も一つのオートマトンになる ◦ 有効グラフの探索問題として定式化できる • 時相論理によってあいまい性を排除 ◦ 瞬間ではなく一連の挙動を定義できる
  34. SPIN の特徴 • C 言語風の文法でプロセスを記述 ◦ オートマトンを意識する必要性はあまりない ◦ ただしデータ型などはだいぶ貧弱 •

    チャンネルによる通信をサポート ◦ 完全な FIFO 型の通信に特化 • 検証専用の C 言語コードを出力 ◦ スケール性能を性能を追求
  35. mtype = { PREPARED, COMMIT, ABORT }; bool committed[3]; bool

    aborted[3]; proctype TM(...) { ... } proctype RM(...) { ... } init { ... } SPIN による記述(全体)
  36. mtype = { PREPARED, COMMIT, ABORT }; bool committed[3]; bool

    aborted[3]; proctype TM(...) { ... } proctype RM(...) { ... } init { ... } SPIN による記述(全体) 通信のタイプ
  37. mtype = { PREPARED, COMMIT, ABORT }; bool committed[3]; bool

    aborted[3]; proctype TM(...) { ... } proctype RM(...) { ... } init { ... } SPIN による記述(全体) グローバルな条件
  38. mtype = { PREPARED, COMMIT, ABORT }; bool committed[3]; bool

    aborted[3]; proctype TM(...) { ... } proctype RM(...) { ... } init { ... } SPIN による記述(全体) プロセスの定義
  39. mtype = { PREPARED, COMMIT, ABORT }; bool committed[3]; bool

    aborted[3]; proctype TM(...) { ... } proctype RM(...) { ... } init { ... } SPIN による記述(全体) 初期化シーケンス
  40. init { chan id_gen = [3] of { byte };

    chan rm_to_tm = [3] of { mtype, byte }; chan tm_to_rm1 = [1] of { mtype }; chan tm_to_rm2 = [1] of { mtype }; chan tm_to_rm3 = [1] of { mtype }; atomic { id_gen ! 0; id_gen ! 1; id_gen ! 2; run TM(rm_to_tm, tm_to_rm1, tm_to_rm2, tm_to_rm3); run RM(rm_to_tm, tm_to_rm1, id_gen); run RM(rm_to_tm, tm_to_rm2, id_gen); run RM(rm_to_tm, tm_to_rm3, id_gen); } } SPIN による記述(初期化処理)
  41. init { chan id_gen = [3] of { byte };

    chan rm_to_tm = [3] of { mtype, byte }; chan tm_to_rm1 = [1] of { mtype }; chan tm_to_rm2 = [1] of { mtype }; chan tm_to_rm3 = [1] of { mtype }; atomic { id_gen ! 0; id_gen ! 1; id_gen ! 2; run TM(rm_to_tm, tm_to_rm1, tm_to_rm2, tm_to_rm3); run RM(rm_to_tm, tm_to_rm1, id_gen); run RM(rm_to_tm, tm_to_rm2, id_gen); run RM(rm_to_tm, tm_to_rm3, id_gen); } } SPIN による記述(初期化処理) チャンネルの生成
  42. init { chan id_gen = [3] of { byte };

    chan rm_to_tm = [3] of { mtype, byte }; chan tm_to_rm1 = [1] of { mtype }; chan tm_to_rm2 = [1] of { mtype }; chan tm_to_rm3 = [1] of { mtype }; atomic { id_gen ! 0; id_gen ! 1; id_gen ! 2; run TM(rm_to_tm, tm_to_rm1, tm_to_rm2, tm_to_rm3); run RM(rm_to_tm, tm_to_rm1, id_gen); run RM(rm_to_tm, tm_to_rm2, id_gen); run RM(rm_to_tm, tm_to_rm3, id_gen); } } SPIN による記述(初期化処理) 不可分実行
  43. init { chan id_gen = [3] of { byte };

    chan rm_to_tm = [3] of { mtype, byte }; chan tm_to_rm1 = [1] of { mtype }; chan tm_to_rm2 = [1] of { mtype }; chan tm_to_rm3 = [1] of { mtype }; atomic { id_gen ! 0; id_gen ! 1; id_gen ! 2; run TM(rm_to_tm, tm_to_rm1, tm_to_rm2, tm_to_rm3); run RM(rm_to_tm, tm_to_rm1, id_gen); run RM(rm_to_tm, tm_to_rm2, id_gen); run RM(rm_to_tm, tm_to_rm3, id_gen); } } SPIN による記述(初期化処理) プロセスの生成
  44. proctype TM(chan ch_from, ch_to1, ch_to2, ch_to3) { byte id; bool

    prepared[3]; end: do :: ch_from ? PREPARED(id) -> prepared[id] = true; :: prepared[0] && prepared[1] && prepared[2] -> ch_to1 ! COMMIT; ch_to2 ! COMMIT; ch_to3 ! COMMIT; :: ch_to1 ! ABORT; ch_to2 ! ABORT; ch_to3 ! ABORT; od } SPIN による記述(TM)
  45. proctype TM(chan ch_from, ch_to1, ch_to2, ch_to3) { byte id; bool

    prepared[3]; end: do :: ch_from ? PREPARED(id) -> prepared[id] = true; :: prepared[0] && prepared[1] && prepared[2] -> ch_to1 ! COMMIT; ch_to2 ! COMMIT; ch_to3 ! COMMIT; :: ch_to1 ! ABORT; ch_to2 ! ABORT; ch_to3 ! ABORT; od } SPIN による記述(TM) 非決定的に選択 = オートマトンが分岐
  46. proctype TM(chan ch_from, ch_to1, ch_to2, ch_to3) { byte id; bool

    prepared[3]; end: do :: ch_from ? PREPARED(id) -> prepared[id] = true; :: prepared[0] && prepared[1] && prepared[2] -> ch_to1 ! COMMIT; ch_to2 ! COMMIT; ch_to3 ! COMMIT; :: ch_to1 ! ABORT; ch_to2 ! ABORT; ch_to3 ! ABORT; od } SPIN による記述(TM) チャンネルからの受信
  47. proctype TM(chan ch_from, ch_to1, ch_to2, ch_to3) { byte id; bool

    prepared[3]; end: do :: ch_from ? PREPARED(id) -> prepared[id] = true; :: prepared[0] && prepared[1] && prepared[2] -> ch_to1 ! COMMIT; ch_to2 ! COMMIT; ch_to3 ! COMMIT; :: ch_to1 ! ABORT; ch_to2 ! ABORT; ch_to3 ! ABORT; od } SPIN による記述(TM) prepared が揃っているか
  48. proctype TM(chan ch_from, ch_to1, ch_to2, ch_to3) { byte id; bool

    prepared[3]; end: do :: ch_from ? PREPARED(id) -> prepared[id] = true; :: prepared[0] && prepared[1] && prepared[2] -> ch_to1 ! COMMIT; ch_to2 ! COMMIT; ch_to3 ! COMMIT; :: ch_to1 ! ABORT; ch_to2 ! ABORT; ch_to3 ! ABORT; od } SPIN による記述(TM) Commit 命令を一斉送信
  49. proctype TM(chan ch_from, ch_to1, ch_to2, ch_to3) { byte id; bool

    prepared[3]; end: do :: ch_from ? PREPARED(id) -> prepared[id] = true; :: prepared[0] && prepared[1] && prepared[2] -> ch_to1 ! COMMIT; ch_to2 ! COMMIT; ch_to3 ! COMMIT; :: ch_to1 ! ABORT; ch_to2 ! ABORT; ch_to3 ! ABORT; od } SPIN による記述(TM) Abort 命令を一斉送信
  50. proctype RM(chan ch_to, ch_from, id_gen) { byte id; id_gen ?

    id; if :: ch_to ! PREPARED(id) -> if :: ch_from ? COMMIT -> committed[id] = true; :: ch_from ? ABORT -> aborted[id] = true; fi :: aborted[id] = true; fi } SPIN による記述(RM)
  51. proctype RM(chan ch_to, ch_from, id_gen) { byte id; id_gen ?

    id; if :: ch_to ! PREPARED(id) -> if :: ch_from ? COMMIT -> committed[id] = true; :: ch_from ? ABORT -> aborted[id] = true; fi :: aborted[id] = true; fi } SPIN による記述(RM) 自分の id を TM に送信
  52. proctype RM(chan ch_to, ch_from, id_gen) { byte id; id_gen ?

    id; if :: ch_to ! PREPARED(id) -> if :: ch_from ? COMMIT -> committed[id] = true; :: ch_from ? ABORT -> aborted[id] = true; fi :: aborted[id] = true; fi } SPIN による記述(RM) 受信結果で対応が変わる
  53. CONSTANT RM VARIABLES rmState, tmState, tmPrepare, msgs Message == [type

    : {“Prepared”}, rm : RM] \union [type : {“Commit”, “Abort”}] Init == /\ rmState = [rm \in RM |-> “working”] /\ tmState = “init” /\ tmPrepare = {} /\ msgs = {} TLA+ による記述(全体)
  54. CONSTANT RM VARIABLES rmState, tmState, tmPrepare, msgs Message == [type

    : {“Prepared”}, rm : RM] \union [type : {“Commit”, “Abort”}] Init == /\ rmState = [rm \in RM |-> “working”] /\ tmState = “init” /\ tmPrepare = {} /\ msgs = {} TLA+ による記述(全体) (個数を限定しない)RM の集合
  55. CONSTANT RM VARIABLES rmState, tmState, tmPrepare, msgs Message == [type

    : {“Prepared”}, rm : RM] \union [type : {“Commit”, “Abort”}] Init == /\ rmState = [rm \in RM |-> “working”] /\ tmState = “init” /\ tmPrepare = {} /\ msgs = {} TLA+ による記述(全体) 状態はすべてグローバル
  56. CONSTANT RM VARIABLES rmState, tmState, tmPrepare, msgs Message == [type

    : {“Prepared”}, rm : RM] \union [type : {“Commit”, “Abort”}] Init == /\ rmState = [rm \in RM |-> “working”] /\ tmState = “init” /\ tmPrepare = {} /\ msgs = {} TLA+ による記述(全体) 初期状態
  57. Next == /\ TMCommit /\ TMAbort /\ \E rm \in

    RM : \/ TMRcvPrepared(rm) \/ RMPrepare(rm) \/ RMChooseToAbort(rm) \/ RMRcvCommitMsg(rm) \/ RMRcvAbortMsg(rm) TLA+ による記述(全体)
  58. Next == /\ TMCommit /\ TMAbort /\ \E rm \in

    RM : \/ TMRcvPrepared(rm) \/ RMPrepare(rm) \/ RMChooseToAbort(rm) \/ RMRcvCommitMsg(rm) \/ RMRcvAbortMsg(rm) TLA+ による記述(全体) 可能な遷移の全体
  59. TMRcvPrepared(rm) == /\ tmState = “init” /\ [type |-> “Prepared”,

    rm |-> rm] \in msgs /\ tmPrepared’ = tmPrepared \union {rm} /\ UNCHANGED <<tmState, rmState, msgs>> TMCommit == /\ rmState = “init” /\ tmPrepared = RM /\ rmState’ = “committed” /\ msgs’ = msgs \union {[type |-> “Commit”]} /\ UNAHGEND <<rmState, tmState>> TMAbort == ... TLA+ による記述(TM)
  60. TMRcvPrepared(rm) == /\ tmState = “init” /\ [type |-> “Prepared”,

    rm |-> rm] \in msgs /\ tmPrepared’ = tmPrepared \union {rm} /\ UNCHANGED <<tmState, rmState, msgs>> TMCommit == /\ rmState = “init” /\ tmPrepared = RM /\ rmState’ = “committed” /\ msgs’ = msgs \union {[type |-> “Commit”]} /\ UNAHGEND <<rmState, tmState>> TMAbort == ... TLA+ による記述(TM) TM が Commit 命令を送信
  61. TMRcvPrepared(rm) == /\ tmState = “init” /\ [type |-> “Prepared”,

    rm |-> rm] \in msgs /\ tmPrepared’ = tmPrepared \union {rm} /\ UNCHANGED <<tmState, rmState, msgs>> TMCommit == /\ rmState = “init” /\ tmPrepared = RM /\ rmState’ = “committed” /\ msgs’ = msgs \union {[type |-> “Commit”]} /\ UNAHGEND <<rmState, tmState>> TMAbort == ... TLA+ による記述(TM) 「‘」をつけると遷移先を意味する
  62. RMPrepare(rm) == /\ rmState[rm] = “working” /\ rmState’ = [rmState

    EXCEPT ![rm] = “prepared”] /\ msgs’ = msgs \union {[type |-> “Prepared”, rm |-> rm]} /\ UNCHANGED <<tmState, tmPrepared>> RMChooseAbort(rm) == ... RMRcvCommitMsg(rm) == /\ [type |-> “Commit”] \in msgs /\ rmState’ = [rmState EXCEPT ![rm] = “committed”] /\ UNCHANGED <<tmState, tmPrepared, msgs>> RMRcvAbortMsg(rm) == ... TLA+ による記述(RM)
  63. P 言語の特徴 • 状態とハンドラの組でプロセスを記述 ◦ Web フレームワークの Controller に近い •

    各プロセスが入力用のキューを持つ ◦ SPIN と異なりキューはプロセスの持ち物 ◦ 他のプロセスからのメッセージがキューに積まれる • 実行可能な C 言語のコードを出力
  64. event PREPARE: machine; event COMMIT; event ABORT; machine TM {

    ... } machine RM { ... } P による記述(全体)
  65. event PREPARE: machine; event COMMIT; event ABORT; machine TM {

    ... } machine RM { ... } P による記述(全体) メッセージの種類
  66. event PREPARE: machine; event COMMIT; event ABORT; machine TM {

    ... } machine RM { ... } P による記述(全体) プロセスの定義
  67. machine TM { var rms: seq[machine]; var prepared: map[machine, bool];

    start state Init { on PREPARE (rm: machine) { prepared[rm] = true; if (sizeof(prepared) == sizeof(rms)) { SendCommitMsg(); goto Committed; } } } state Committed {} state Aborted {} } P による記述(TM)
  68. machine TM { var rms: seq[machine]; var prepared: map[machine, bool];

    start state Init { on PREPARE (rm: machine) { prepared[rm] = true; if (sizeof(prepared) == sizeof(rms)) { SendCommitMsg(); goto Committed; } } } state Committed {} state Aborted {} } P による記述(TM) 状態の定義
  69. machine TM { var rms: seq[machine]; var prepared: map[machine, bool];

    start state Init { on PREPARE (rm: machine) { prepared[rm] = true; if (sizeof(prepared) == sizeof(rms)) { SendCommitMsg(); goto Committed; } } } state Committed {} state Aborted {} } P による記述(TM) PREPARE メッセージをデキュー
  70. machine TM { var rms: seq[machine]; var prepared: map[machine, bool];

    start state Init { on PREPARE (rm: machine) { prepared[rm] = true; if (sizeof(prepared) == sizeof(rms)) { SendCommitMsg(); goto Committed; } } } state Committed {} state Aborted {} } P による記述(TM) 関数を実行して次の状態へ
  71. machine TM { ... start state Init { ... }

    state Committed {} state Aborted {} fun SendCommitMsg() { var i: int; i = 0; while (i < sizeof(rms)) { send rms[i], COMMIT; i = i + 1; } } } P による記述(TM)
  72. machine TM { ... start state Init { ... }

    state Committed {} state Aborted {} fun SendCommitMsg() { var i: int; i = 0; while (i < sizeof(rms)) { send rms[i], COMMIT; i = i + 1; } } } P による記述(TM) すべての RM に COMMIT 命令
  73. machine RM { start state Working { ... } state

    Prepare { ... } state Committed {} state Aborted {} } P による記述(RM)
  74. machine RM { start state Working { entry (tm: machine)

    { if ($) { send tm, PREPARE, this; goto Prepare; } else { goto Aborted; } } on Commit { goto Committed; } } } P による記述(RM)
  75. machine RM { start state Working { entry (tm: machine)

    { if ($) { send tm, PREPARE, this; goto Prepare; } else { goto Aborted; } } on Commit { goto Committed; } } } P による記述(RM) 非決定的な真偽値 = オートマトンの分岐
  76. machine RM { start state Working { entry (tm: machine)

    { if ($) { send tm, PREPARE, this; goto Prepare; } else { goto Aborted; } } on Commit { goto Committed; } } } P による記述(RM) メッセージが送信され、 TM のキューに積まれる
  77. LTL による検査のサポート • SPIN ◦ 可能だが、検査したい式の否定を入力する必要あり • TLA+ ◦ 完全なサポート、システム定義側でも使用可

    • P 言語 ◦ サポートなし、検査したい内容を machine で表現 ◦ 場合によっては複雑な変換が必要になる
  78. 第 3 章のまとめ • SPIN ◦ チャンネルを介して通信するプロセスを定義 • TLA+ ◦

    システム全体の状態遷移の条件を明示的に定義 ◦ +CAL を使用すると手続き的な記述もできる • P 言語 ◦ 各状態とイベントに対するリアクションを定義
  79. 本日のまとめ • 分散システムの検証に形式手法が有効 ◦ 考えるパターンが多く人の手では扱えない • オートマトンを基礎としたモデル検査 ◦ 形式化・抽象化による体系的な状態の列挙 ◦

    時相論理により、表現しづらい仕様を明示 • 各モデル検査ツールの特徴 ◦ どれもクセがあるのでシステムの性質に合わせて選ぶ