Threadの割り込みハンドラが失敗することがある(その2)

この問題は先日のアップデートでパッチがあたりました。

最新のtrunkのThreadではこの問題は起きないようです。


前回の続きです。

問題をもう一度整理してみます。
問題点は2つあります。

1・ ステートがWAITINGの状態でイベントハンドラが実行され即座にRUNNABLEになる。
このステートが再びWAITINGになるのは次のサイクル。
つまりサイクルとサイクルの間でステータスがRUNNABLEになってしまう事があり、このとき割り込まれると割り込みハンドラは失敗する。

2・ WAITINGの状態で割り込まれると割り込みハンドラは次の実行関数として正しく設定される。
しかしこの実行関数が次のサイクルで実行される前にイベントを受け取ってしまうとイベントハンドラ内に書かれた処理内容によっては実行関数を上書きしてしまう。

まず2から取りかかります。

割り込みハンドラとイベントハンドラは排他的な関係のように思えます。
なぜならイベント待ちをしているThreadに割り込みをかけるという事は、もうイベントを受け取りたくないという事だからです。
これをヒントに次のような方針でパッチを当てます。

「イベントハンドラがイベントを受け取ったとき、実行関数が割り込みハンドラであれば(このThreadは割り込まれたので)、そのイベントは無視する。」

以下がイベントハンドラの部分のパッチコードです。(1026行目)

private function eventHandler(e:Event, handler:EventHandler):void

{

    //実行関数が割り込みハンドラならイベントハンドラをリセットして何もしない

    if (_interruptedHandler != null && _runHandler == _interruptedHandler) 

    {

        // イベントハンドラをリセット

        resetEventHandlers();

        return;

    }

    // 既にイベントが起こっていれば何もしない

 

つぎに1のほうですが以下のような方針でパッチを当てる事にします。

ステータスがRUNNABLEになる瞬間を避けて、WAITINGの時に確実に割り込みの内部処理を実行する

割り込み処理はinterrup()メソッド内に書かれています。interrupt()が呼ばれた時にすぐにこの処理を実行してしまわないで、ステータスがWAITINGの時にこの処理を行いたい。ではステータスが確実にWAITINGなのはどんなタイミングでしょうか?サイクルが終わった直後なら確実にWAITINGのはずです。サイクルが終わった直後に、イベントに先を越されないようすぐに割り込み処理を実行するようにします。

割り込み処理の遅延をするために、キュー用の変数を用意します。(138行目)

public class Thread extends Monitor

{

    private static var _executor:IThreadExecutor;

    private static var _threadIndex:uint = 0;

    private static var _currentThread:Thread = null;

    private static var _toplevelThreads:Array = [];

    private static var _uncaughtErrorHandler:Function = null;

    private static var _defaultErrorHandlers:Dictionary = null;

    private static var _reserveInterrupt : Array = [];

 

interrupt()が呼び出された時、呼び出されたThreadインスタンスをこのキューに格納します。
また実際の割り込み処理の部分を、新しいメソッドinternalInterrupt()を作って丸ごと移動します。
以下がinterrupt()部分のパッチコードです。(791行目)

public function interrupt():void

{

    //割り込みを予約し、次のサイクルの最後に実行する

    _reserveInterrupt.push(this);

}

internal function internalInterrupt():void

{

    if (_state == ThreadState.WAITING || _state == ThreadState.TIMED_WAITING) {

 

最後に、サイクルの最後に割り込み処理を実行するように、executeAllThreads()メソッドの最後に以下のコードを追加します。(350行目)

public static function executeAllThreads():void

{

    // 全てのトップレベルスレッドを呼び出す

    

〜〜 省略 〜〜

    

    //予約された割り込みハンドラがあれば実行する

    _reserveInterrupt.forEach(

        function(thread:Thread, …param):void

        {

            thread.internalInterrupt();

        }

    );

    _reserveInterrupt = [];

}

 

テストを実行すると無事にテストをパスします

CYCLE 0--------------
@ internalExecute (RUNNABLE
# run() (RUNNABLE
@ register event  (RUNNABLE
〜〜 省略 〜〜
CYCLE 8--------------
@ internalExecute (WAITING
[DISPATCH]
@ eventHandler  (WAITING
@ internalExecute (RUNNABLE
# received event (RUNNABLE
!!!!!!!!!!!!!!!!!!! CALL INTERRUPT !!!!!!!!!!!!!!!!!!!
@ interrupt  (RUNNABLE
CYCLE 9--------------
@ internalExecute (RUNNABLE
# run() (RUNNABLE
@ register event  (RUNNABLE
[DISPATCH]
@ eventHandler  (RUNNABLE
CYCLE 10--------------
@ internalExecute (RUNNABLE
# INTERRUPTED
CYCLE 11--------------
@ internalExecute (TERMINATING
# finalize() (TERMINATING
CYCLE 0--------------
@ internalExecute (RUNNABLE
# run() (RUNNABLE
@ register event  (RUNNABLE
〜〜 省略 〜〜
CYCLE 6--------------
@ internalExecute (WAITING
!!!!!!!!!!!!!!!!!!! CALL INTERRUPT !!!!!!!!!!!!!!!!!!!
@ interrupt  (WAITING
[DISPATCH]
@ eventHandler  (WAITING
@ internalExecute (RUNNABLE
# received event (RUNNABLE
CYCLE 7-------------- (※このサイクルで割り込みハンドラが割り当てられる)
@ internalExecute (RUNNABLE
# run() (RUNNABLE
@ register event  (RUNNABLE
[DISPATCH]
@ eventHandler  (RUNNABLE
CYCLE 8--------------
@ internalExecute (RUNNABLE
# INTERRUPTED
CYCLE 9--------------
@ internalExecute (TERMINATING
# finalize() (TERMINATING
..
Time: 0.659
OK (2 tests)

シーケンス図です

fixed the problem


今回のテストと合わせて既存のすべてのテストをパスしたので、このパッチを当てる事で大きな問題は多分起きないんじゃないかと思いますが、何か問題が起きるようであれば教えてください。オフィシャルなパッチじゃないのでクレームはSparkProjectではなくimajuk@mac.comまで

追記:sparkProjectのThreadのbranchesにcommitしました。http://www.libspark.org/svn/as3/Thread/branches/imajuk

カテゴリー: ActionScript3 パーマリンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です