【耐故障並列ソフトウェアライブラリ】


by

杉野栄二、横田治夫 at JAIST (北陸先端科学技術大学院大学)



[用途]

一般の並列計算機上で、KL1プログラムの耐故障実行を実現する。

[機能]

サンプルプログラム ft_queen.kl1 のように 耐故障ソフトウェア化したKL1プログラムとリンクすることで、 耐故障実行を行う。

述語 distribution(Primary,Backup) : watchdog.kl1 で提供される

は、すべてのノードを二つに分け、それぞれで監視プロセス起動する。 それぞれのプロセスへのストリームをリストにしてPrimary, Backup へ返す。

述語 copy(Args,Args1,Args2,Interrupt) : copy.kl1 で提供される

は、与えられた引数(変数を含む)Argsを複製し、Args1, Args2へ返す。 変数に対してはそれぞれプロセスが生成される、Argsの変数に 動的にユニファイされる値に対しては、対応するArgs1,Args2の変数に ブロードキャストされる。 逆にArgs1, Args2へは同じ値がユニファイされる ものとし、そのいずれかをArgsの対応する変数へ送る。 変数に対して生成されているプロセスは、 ストリーム Interruptへ fault が送られると ArgsとArgs2とをユニファイして 終了する。

[使い方]

ライブラリ watchdog.kl1、copy.kl1 および、 使用する環境に応じてncube.kl1 または sparc.kl1 、 それとユーザプログラム (サンプルプログラムならば main.kl1 と ft_queen.kl1 ) をKLICでコンパイル、リンクして起動する。

% klic -v -dp -o dofts watchdog.kl1 copy.kl1 sparc.kl1 main.kl1 ft_queen.kl1
注)使用可能なノード数の総数が3個以上なければならない。

[実行環境]

[ファイル構成]

fts/
README.j
% 日本語版 README
README-j.html
% 本ファイル
README.e
% 英語版 README
README-e.html
% 英語版 html
watchdog.kl1
% 監視プロセス
copy.kl1
% プロセス複製
ncube.kl1
% nCUBE2向けライブラリ
sparc.kl1
% SPARC向けライブラリ
=== サンプルプログラム ===
main.kl1
% サンプルプログラム 主ルーチン
queen.kl1
% N-Queenプログラム
ft_queen.kl1
% 耐故障化 N-Queenプログラム
ft_queen_faulty.kl1
% 耐故障化 N-Queenプログラム エラーあり
main-sim.kl1
% サンプルプログラム(その2)主ルーチン
f_sim.kl1
% サンプルプログラム(その2)
f_sim_faulty.kl1
% サンプルプログラム(その2)エラーあり

[実行例]

分散版KLICのトレース機能が利用できないため、バグが完全に とりきれていない。 よって現在の版では、故障に対して完全に 実行継続できない。 サンプルプログラム(その2)については、ある程度の 継続動作が見られる。

1) コンパイル

% make sparc

2) エラー無しの実行

% ./dofts-s -p5 10 100d
Leader [1]
Leader [2]
Backup Site Group : 2 4
Primary Site Group : 1 3
30 [67,1]
<== プログラム出力 A (30), B ([67,1])
Response time is 1359 msec

3) エラーありの実行

amethyst[300]% ./dofts-sf -p5 10 100d
Leader [1]
Leader [2]
Primary Site Group : 1 3
Backup Site Group : 2 4
BOMB! 3
<== Node 3 でexit
30 FAULT was detected on PRIMARY SITE!
<== 出力 A の後、故障検出
FAULT was informed to BACKUP!
BACKUP change to PRIMARY !!! REBIRTH (1) [4]
REBIRTH (2) [4]
... REBIRTH ndet_replay [117,1]
<== 内部メッセージの後、出力 B
^Ckill tasks from io_server
<== 正常終了しないため ^C

耐故障化変換 プログラムは、未完成であるので、以下に耐故障化変換の指針を記す。

[耐故障化の指針]

1) 耐故障実行させたい部分を分離する。

ユーザプログラムは、main.kl1 と queen.kl1 に分離されており、 述語 queen:queen(N,Result) を耐故障実行する。
注) オリジナルのプログラムは、main.kl1 queen.kl1 だけで動作する。

2)耐故障実行させたい部分を次の手順で変更する。

2-1) 呼び出される一番上の述語を次の例のように変更する。

% オリジナルの述語呼び出し
queen(N,R) :-
% 監視プロセス生成
watchdog:distribution(Primary,Backup),
% 次の述語へ引数を渡す
queen_1({N,R},Primary,Backup) @ lower_priority(10).
% プライマリとバックアップの主プロセスへのストリームを取り
queen_1(Args,[PTop|Primary],[BTop|Backup]) :-
% 引数を複製し、
copy:copy(Args,Args1,Args2,Interrupt),
% 割り込み線をマージし、
Interrupt = {Interrupt1,Interrupt2}, Log = ack(Log1),
% プライマリ、バックアップそれぞれへ
% トップのゴールをフォークする
PTop = {primary,queen,queen,Args1,Log,Signal,Interrupt1},
BTop = {backup ,queen,queen,Args2,Log1,Signal,Interrupt2}.

2-2) トップゴールの呼び出し口を用意する。

呼び出し口は、モジュール exgoal に定義される。
上では、モジュール queen の述語 queen は、オリジナルのアリティが2 となっているので、以下の =-=-=-=- の間の2クローズが定義される。
:- module exgoal.
call_goal(Site,Module,Predicate,Args,Log,GSig,Raise)-SC :-
call_goal_0(Site,Module,Predicate,Args,Log,GSig,Raise)-SC @ lower_priority.
% =-=-=-=-
call_goal_0(primary,queen,queen,{A,B},Log,GSig,Raise)-SC :-
queen:queen_record(A,B,Log)+GSig+Raise-SC.
call_goal_0(backup ,queen,queen,{A,B},Log,GSig,Raise)-SC :-
queen:queen_replay(A,B,Log)+GSig+Raise-SC.
% =-=-=-=-
otherwise.
call_goal_0(Type,Module,Method,Arguments,Log,GSignal,Raise)-SC :-
klicio:klicio([stdout(normal(Out))]),
variable:wrap((Type::Module:Method/Arguments), G),
Out = [fwrite("Illegal goal invocation : "), putwt(G), nl,fflush(_)],
Raise = [].

2-3) 2-3 で定義した トップゴール に対応する述語を用意する。

(1) もとの述語に対して Instant Replay 変換する

% Record Version
queen_record(N,X,Log) :-
current_node(_,All),
queen_0_record(N,X,~(All-1),Log)@node(1).
queen_0_record(4,X,A,Log) :- queen_record([1,2,3,4],[],[],X,A,Log).
....
queen_record([P|U],C,L,I,PE,Log) :-
Log = c1(Log1,Log2,Log3),
TO:= (P mod PE)+1,
throw_record(U,[P|C],L,I2,PE,TO,Log1),
merge_record(I1,I2,I,Log2),
append(U,C,N),
c1_record(P,1,N,L,L,I1,PE,Log3).
% Replay Version
queen_replay(N,X,Log) :-
current_node(_,All),
queen_0_replay(N,X,~(All-1),Log)@node(1).
queen_0_replay(4,X,A,Log) :- queen_replay([1,2,3,4],[],[],X,A,Log).
....
queen_replay([P|U],C,L,I,PE,Log) :-
Log = c1(Log1,Log2,Log3) |
TO:= (P mod PE)+1,
throw_replay(U,[P|C],L,I2,PE,TO,Log1),
merge_replay(I1,I2,I,Log2),
append(U,C,N),
c1_replay(P,1,N,L,L,I1,PE,Log3).

(2) すべてのユーザ述語について引数を追加する

引数GSigは上位からの割り込み用であり、全ゴールにブロードキャストされる ように、サブゴールにそのまま渡す。
引数Raiseはゴールからの割り出し用なので、サブゴールが複数あるような 場合には、mergeされるようにサブゴールのRaiseストリームから成る ベクタをユニファイする。 サブゴールが一つだけなら、そのまま渡す。 サブゴールが存在しなければ、 [] とユニファイする。
引数組 SC は、ショートサーキットするだけのためなので、そのまま各ゴール につけるだけである。
% Record Version
queen_record(N,X,Log)+GSig+Raise-SC :-
current_node(_,All),
queen_0_record(N,X,~(All-1),Log)+GSig+Raise-SC @node(1).
queen_0_record(4,X,A,Log)+GSig+Raise-SC :-
queen_record([1,2,3,4],[],[],X,A,Log)+GSig+Raise-SC.
....
queen_record([P|U],C,L,I,PE,Log)+GSig+Raise-SC :-
Raise = {Raise1,Raise2,Raise3,Raise4},
Log = c1(Log1,Log2,Log3),
TO:= (P mod PE)+1,
throw_record(U,[P|C],L,I2,PE,TO,Log1)+GSig+Raise1-SC,
merge_record(I1,I2,I,Log2)+GSig+Raise2-SC,
append_record(U,C,N)+GSig+Raise3-SC,
c1_record(P,1,N,L,L,I1,PE,Log3)+GSig+Raise4-SC.

(3) Record Versionでは、非決定的述語で Logのack待ち部分を挿入する

queen_record([P|U],C,L,I,PE,ack(Log))+GSig+Raise-SC :-
Raise = {Raise1,Raise2,Raise3,Raise4},
Log = c1(Log1,Log2,Log3),
TO:= (P mod PE)+1,
throw_record(U,[P|C],L,I2,PE,TO,Log1)+GSig+Raise1-SC,
merge_record(I1,I2,I,Log2)+GSig+Raise2-SC,
append_record(U,C,N)+GSig+Raise3-SC,
c1_record(P,1,N,L,L,I1,PE,Log3)+GSig+Raise4-SC.
queen_record([],[_|_],_,I,ack(Log))+Sig+Raise-SC:-
Raise=[],
Log=c2(Ack),
I=[].

(4) Record Version では、非決定的述語で ボディユニファイのところに ack待ちを挿入する (出力コミット)

queen_record([],[_|_],_,I,ack(Log))+Sig+Raise-SC:-
Raise=[],
Log=c2(Ack),
output(Ack,I, [])-SC.
output(Ack,X,Y)-SC :- wait(Ack) | X = Y.

(5) Replay Version では各述語の前に 割り込みチェック処理用の クローズを挿入する

% Replay Version
queen_replay(A, B, Log)+GSig+Raise-SC :-
(wait(Log) -> queen_replay_0(A, B, Log)+GSig+Raise-SC ;
alternatively;
GSig = [rebirth|GSig1] -> queen_record(A, B, _)+GSig1+Raise-SC).
queen_replay_0(N,X,Log)+GSig+Raise-SC :-
current_node(_,All),
queen_0_replay(N,X,~(All-1),Log)+GSig+Raise-SC @node(1).
queen_0_replay( 4,X,A,Log)+GSig+Raise-SC :-
queen_replay([1,2,3,4],[],[],X,A,Log)+GSig+Raise-SC.
....
queen_record([P|U],C,L,I,PE,Log)+GSig+Raise-SC :-
Log = c1(Log1,Log2,Log3) |
Raise = {Raise1,Raise2,Raise3,Raise4},
TO:= (P mod PE)+1,
throw_replay(U,[P|C],L,I2,PE,TO,Log1)+GSig+Raise1-SC,
merge_replay(I1,I2,I,Log2)+GSig+Raise2-SC,
append_replay(U,C,N)+GSig+Raise3-SC,
c1_replay(P,1,N,L,L,I1,PE,Log3)+GSig+Raise4-SC.
queen_replay([],[_|_],_,I,ack(Log))+Sig+Raise-SC:-
Log=c2(Ack) |
Raise=[],
I=[].

(6) throw goal ('goal @ node(N)')は、すべて次のような割り出しに変える

throw_record(A,B,C,D,E,F,Log)+GSig+Raise-SC :-
Raise = [goal(primary,queen,queen,{A,B,C,D,E},Log)].
throw_replay(A,B,C,D,E,F,Log)+GSig+Raise-SC :-
Raise = [goal(primary,queen,queen,{A,B,C,D,E},Log)].
現在のバージョンでは、隣のノードへ勝手に投げるのでノード番号は 必要ない。

(7) throw goalするゴールに対応する入口を モジュールexgoalに追加する。

call_goal_0(primary,queen,queen,{A,B,C,D,E},Log,GSig,Raise)-SC :-
queen:queen_record(A,B,C,D,E,Log)+GSig+Raise-SC.
call_goal_0(backup ,queen,queen,{A,B,C,D,E},Log,GSig,Raise)-SC :-
queen:queen_replay(A,B,C,D,E,Log)+GSig+Raise-SC.
....
otherwise.
call_goal_0(Type,Module,Method,Arguments,Log,GSignal,Raise)-SC :-
klicio:klicio([stdout(normal(Out))]),
variable:wrap((Type::Module:Method/Arguments), G),
Out = [fwrite("Illegal goal invocation : "), putwt(G), nl,fflush(_)],
Raise = [].
注)ft_queen.kl1 は、throw goalするゴールをc1に変え、 その他不必要な部分をオプティマイズしたものである。

本文章について


sugino@jaist.ac.jp