2017年10月17日 初版
この記事では、イベントウォークのアトミックリージョンによりどのような問題を解決できるかを具体例を使って説明します。
なお、アトミックリージョンはフリーエディットモードでのみ設定できます。フリーエディットモードについてはイベントウォーク作成(フリーエディットモード)を参照してください。
起こり得る問題
次のような仕事をするイベントウォークを考えます。各ステートやコネクターをGUIの画面で表現することもできますが、このように文字で表現したほうが、問題の本質が一望しやすいと思います。
main -> state2 state2 (OR) -> state8 label_1 Gmail: TRIGGER when received (advance_pointer=TRUE) label_2 Slack: TRIGGER when posted (advance_pointer=TRUE) state8 (OR) -> main_exit label_5 プッシュ通知: ACTION push message message=last.text
イベントウォーク全体の意図は「GmailまたはSlackでメッセージを受け取ったら、それをスマートフォンにプッシュメッセージで送る」というものです。
state2では、GmailとSlackの2つのコネクターの受信メソッド(TRIGGER when_received / when_posted)を起動します。advance_pointerがTRUEなので、Gmailコネクター/Slackコネクターとも、受信が完了したらカレントポインター(すでに読み込んだメッセージのうち最新のものを指すポインター)を、今受信したばかりの最新のメッセージまで移動します。カレントポインターはシナリオ毎に用意された記憶域に保存されます。したがって、シナリオ(のプログラム部であるイベントウォーク)が終了して、のちに同じシナリオが起動されると、カレントポインターは引き継がれます。
state2は、GmailまたはSlackのどちらかでメッセージを受信したときにstate8に遷移します。OR条件では、いずれかのコネクターがイベントを起こしたとき、両方のコネクターが即座に停止されます。このため、次のようなことが起こり得ます。
1. Gmailコネクターがメールを受信して、カレントポインターをそのメッセージに移動した上で、イベントウォークにイベントを送ります。 2. その頃、Slackコネクターもメッセージがポストされたことに気づいて、カレントポインターをそのメッセージに移動した上で、イベントウォークにイベントを送ります。 3. イベントウォークにはGmailのイベントが先に届きます。 4. 遷移条件がORのため、即座にGmail, Slackコネクターが停止され、state8に遷移します。
ここで、Slackコネクターが送ったイベントは無視されましたが、カレントポインターはすでに最新のメッセージまで進んでしまっています。同じシナリオ内(再度起動されたとしても)で次にSlack: TRIGGER when postedメソッドが実行されると、先ほど受信したメッセージはすでに過去のものとなっており、さらにその後にポストされたメッセージが届くまでイベントは起こりません。つまり、最初のメッセージを取りこぼしたことになります。
問題への対策1
main -> state2 state2 (CASE) label_1 Gmail: TRIGGER when received (advance_pointer=FALSE) -> state4 label_2 Slack: TRIGGER when posted (advance_pointer=FALSE) -> state6 state4 (OR) -> state8 label_3 Gmail: ACTION advance pointer gmail_history_id=last.gmail_history_id state6 (OR) -> state8 label_4 Slack: ACTION advance pointer timestamp=last.timestamp state8 (OR) -> main_exit label_5 プッシュ通知 ACTION push message message=state('state2').text
state2ではGmail, Slackコネクターを実行しますが、パラメーターadvance_pointerへの設定値がFALSEにかわり、遷移条件がCASEになっています。このためGmailからイベントを受けとるとstate4へ、 Slackから受けとるとstate6に遷移します。
state4では、GmailコネクターのACTION advance pointerをパラメーターgmail_history_idにlast.gmail_history_idを指定して実行します。lastは直前のイベント出力を参照するオブジェクトで、この場合state2でGmailコネクターが送ったイベントの中のgmail_history_idまで、カレントポインターを移動します。その後、state8に遷移します。
state6では、Slackコネクターについて、同様にカレントポインターを移動した上でstate8に遷移します。
state8は、state2でGmailまたはSlackコネクターから受け取ったイベントのtextという出力をスマートフォンに対してプッシュ通知した上で、main_exitに遷移します。
この変更により、パラメーターadvance_pointerをFALSEにして、ACTION advance pointerを必要な場合だけ実行することにより、イベントが採用されなかったコネクターについて、カレントポインターが移動してしまうことを避けられます。
これで万事解決なら良いのですが、実は、このイベントウォークは別の問題を含んでいます。
イベントウォークは、親によって中断させられる場合があります。次の2つのケースです。
1. このイベントウォークがシナリオのトップレベルのイベントウォークの場合で、シナリオの「実行を中断」ボタンが押されたり、スケジュール運用の時間帯が終了したとき。 2. このイベントウォークがサブイベントウォークで、親イベントウォークから終了指示が届いたとき。親イベントウォークは、この(サブ)イベントウォークをコネクターと同様に扱います。したがって、上の例でのstate2のように他のコネクターやサブイベントウォークと同時に起動されることがあり、親イベントウォークが他のコネクターからイベントを受け取ると、親はこのイベントウォークに(も)停止を指示します。
もしstate4からstate8への遷移の間に、またはstate6からstate8への遷移の間に、この停止指示が届くと何が起こるでしょうか。
state4またはstate6はすでに実行されていますので、カレントポインターは移動してしまっています。しかし、受け取ったメッセージをスマートフォンにプッシュ通知する前にイベントウォークが終了してしまいます。結果として、そのメッセージはスマートフォンに届かないまま(取りこぼし)になることがあります。稀なタイミングでしか起こりませんが、起こり得ることではあります。
アトミックリージョンはこの問題を解決するために用意されたものです。
問題への対策2
main -> state2 state2 (CASE) label_1 Gmail: TRIGGER when received (advance_pointer=FALSE) -> state4 label_2 Slack: TRIGGER when posted (advance_pointer=FALSE) -> state6 state4 (OR) -> state8 atomic_region_id = "アトミックリージョン1" label_3 Gmail: ACTION advance pointer gmail_history_id=last.gmail_history_id state6 (OR) -> state8 atomic_region_id = "アトミックリージョン1" label_4 Slack: ACTION advance pointer timestamp=last.timestamp state8 (OR) -> main_exit atomic_region_id = "アトミックリージョン1" label_5 プッシュ通知 ACTION push message message=state('state2').text
state4, 6, 8にatomic_regin_idとして同じ文字列を指定しています。アトミックリージョンの機能(上記)により、state4に入ると、state8の処理が終了するまでイベントウォークに対する終了指示は保留されます。state6→state8についても同様です。このため、イベントウォークの終了処理は必ず、state4(またはstate6)の前に起こるか、state8の後に起こるかのどちらかとなり、カレントポインターは移動してしまったのにプッシュ通知が行われないということは起こらなくなります。