250 likes | 326 Views
アスペクト指向言語による 例外処理の記述方法の改善. 数理・計算科学専攻 千葉研究室 熊原奈津子 指導教員:千葉滋. 例外処理とは 異常時の処理をまとめて記述したもの 正常時には実行されないコード ロジックとは分離して 書くべき Java には try-catch がある 分離はできるが正常時の処理のすぐ下に書かなければならない. 例外処理の分離の重要性. try { // ファイルへの操作 File file = new File(); file.open(); file.read(); file.write(); :
E N D
アスペクト指向言語による例外処理の記述方法の改善アスペクト指向言語による例外処理の記述方法の改善 数理・計算科学専攻 千葉研究室 熊原奈津子 指導教員:千葉滋
例外処理とは 異常時の処理をまとめて記述したもの 正常時には実行されないコード ロジックとは分離して書くべき Javaにはtry-catchがある 分離はできるが正常時の処理のすぐ下に書かなければならない 例外処理の分離の重要性 try { //ファイルへの操作 File file = new File(); file.open(); file.read(); file.write(); : } catch(IOException e){ IOException が発生した 場合の例外処理内容 } 修士論文発表会
頻繁に例外処理を差し替えたい- 実験プログラム • 分散環境で動くサーバの性能をテスト • 最初は小規模なので例外処理なし • 問題(故障)が起きたら必要に応じて追加 故障 命令 コンソール マシンの絵 命令 負荷 コンソールマシン 命令 負荷 制御 プログラム サーバマシン クライアントマシン 修士論文発表会
元のプログラムを編集 class Sender{ public void sendCommand(String host, String command) throws Exception{ : Socket s = new Socket(host, port); DataOutputStream out = new DataOutputStream(s.getOutputStream()); out.write(command); out.close(); s.close(); System.out.println(command + “ has been sent to ” + host); } try{ } catch(SocketException e){ // 故障を見つけたらログを出力 } クライアントに ソケットを張る 命令を 送信 複数のホストに 命令を送信 for (int i = 0; i < hostName.length; i++){ new Sender().sendCommand(hostName[i], “./client.sh”); } 修士論文発表会
catch 節の中から try ブロックの先頭へ戻る ホスト故障時には、自動的に復旧処理 他のマシンを使って再試行(リカバリ) try{ Socket s = new Socket(host, port); DataOutputStream out = new DataOutputStream( s.getOutputStream()); out.write(command); out.close(); s.close(); } catch(SocketException e) { ホストを変更して再試行 } Java には再試行を直接実現する 構文はない 修士論文発表会
GluonJ/Rの提案- Recover アドバイスをもつ AOP System • 例外処理をアスペクトとして記述 • 例外処理を分離 • もとのロジックを壊さずに追加・削除が可能 • 例外処理に特化した pointcut 指定子を提供 • Java バイトコード変換で実現 • 再試行するための特殊メソッド(retry)を用意 • アドバイス内で利用可能 • リカバリ処理を容易に記述できる 修士論文発表会
GluonJ/Rの特徴:アスペクトとして分離 • block ポイントカット指定子 • 範囲をポイントカットできる • ジョインポイントのペアを指定(try ブロック指定に相当) • recover アドバイス • catch 節に相当 @Glue class SenderRecovery { @Recover( etype = “SocketException”, advice = “{ $1 = getAnotherHost(); GluonJR.retry(); }”) Pointcut p = Pcd .block(Pcd.call(“Socket#new(..)”), Pcd.call(“PrintStream#plintln()”));} Socket s = new Socket( host, port); DataOutputStream out = newDataOutputStream( s.getOutputStream()); out.write(command); out.close(); s.close(); System.out.println(“done”); 分離して記述 修士論文発表会 アスペクト 元のプログラム
GluonJ/R のプログラム例 @Glue class SenderRecovery { @Refine static class Diff extends Sender{ public String getAnotherHost(){ // 他のホスト名を返す } } @Recover( etype = “SocketException”, advice = “{ $1 = getAnotherHost(); GluonJR.retry(); }”) Pointcut p = Pcd.block( Pcd.call(“Socket#new(..)”), Pcd.call(“PrintStream#plintln()”)) } アドバイス 例外処理を 追加したい 範囲を指定 修士論文発表会 ウィーブ
GluonJ/Rの特徴:再試行 • 特殊メソッド GluonJR.retry() • アドバイスの中で利用可能 • block で指定した範囲の先頭に戻る @Glue class SenderRecovery { @Recover( etype = “SocketException”, advice = “{ $1 = getAnotherHost(); GluonJR.retry(); }”) Pointcut p = Pcd .block(Pcd.call(“Socket#new(..)”), Pcd.call(“PrintStream#println()”));} Socket s = new Socket(host, port); DataOutputStream out = newDataOutputStream( s.getOutputStream()); out.write(command); out.close(); s.close(); System.out.println(“done”); アスペクト 元のプログラム 修士論文発表会
ジョインポイントの一種 GluonJ/Rの文法拡張 block による範囲指定に利用 将来指定されそうな場所にジョインポイントがないとき、あらかじめ書いておく 行アノテーション if(・・・){ : } else { : } @Line(begin) for(・・; ・・; ・・){ : } @Line(end) : 自由に名前がつけられる 自由に名前がつけれる Pointcut p = Pcd.block(Pcd.line(“begin”), Pcd.line(“end”)); 修士論文発表会
GluonJ/R の実装 • GluonJ 1.3 を拡張して実装 • 拡張はGluonJに対するアスペクトのみで記述 • GluonJ 自体は全く変更していない • プラグイン同様、追加削除が容易 • GluonJ の総コード数5000行に対して拡張部分は400行程度 • バイトコード変換には Javassist を利用 • 行アノテーションのプリプロセッサを用意 修士論文発表会
・・ 例外ハンドラの追加 メソッドを実装している バイトコード ユーザから与えられる情報 • 始点・終点(ソースコード) • 処理したい例外の型 • 例外が生じた場合に実行したいコード ←始点 クラスファイル block で指定した範囲 ←終点 ・・・ メソッドの情報 ←例外ハンドラの先頭 アドバイス メソッドの属性 Exception Table に含まれる情報 Exception Table ・・・ • 例外ハンドラがアクティブとなるバイトコードの範囲(始点・終点) • 例外ハンドラがキャッチする例外のクラス • 例外ハンドラの先頭 範囲・例外の種類・飛び先 修士論文発表会
GluonJR.retry() の実装 • GluonJ/R 内でソースコードをコンパイルし、バイトコードへ変換 • invokestatic 命令を命令長が同じ3バイトのgoto 命令に置換 メソッドのバイトコード ←始点 block 範囲の 始点に戻る アドバイス : GluonJR.retry(); : : invokestatic : : goto : 変換 コンパイル 修士論文発表会
始点・終点が同一メソッド内に存在 最も近いペア(始点・終点)を選択 始点となるソースコードが実行される直前から終点となるソースコードが実行される直前までを選択 範囲を選択するアルゴリズム Pointcut p = Pcd.block( Pcd.call(“foo(..)”), Pcd.call(“bar(..)”)); 始点候補 終点候補 foo(); : foo(); : hoge(); bar(); : bar(); block 修士論文発表会 if (終点に一致 && 始点候補が存在 && 両者が同一メソッド内に存在){ このペアを範囲として選択; } if (始点に一致){ ジョインポイントを上書き保存; }
実装の要点:blockで指定する範囲の始点 • ジョインポイント・シャドーではない • ジョインポイントに直接対応するバイトコード命令ではない • Invokevirtual, getfield 等 • その命令を含むソースコード行の最初のバイトコード命令 • retry() の実現のため • アドバイス内から goto で戻っても bytecode verifier をパスする 先頭の命令 new Socketdupaload_1iload 4invokespecial Socket()astore 5 Socket s = new Socket(host, port); コンパイル JP Shadow 修士論文発表会
JVMのスタックの状態遷移図 例 new Socketdupaload_1iload 4invokespecial Socket()astore 5 Socket s = new Socket(host, port); コンパイル int invokespecial new dup ・・・・・ str スタックは空の状態 ref ref ref ref ref × ○ retry() で戻ってくる 修士論文発表会
プリプロセッサで空のスタティックメソッド呼び出しに変換プリプロセッサで空のスタティックメソッド呼び出しに変換 プログラムの挙動には影響なし 性能に対する影響については後述 行アノテーションのプリプロセッサ if(・・・){ : } else { : } @Line(begin) for(・・; ・・; ・・){ : } @Line(end) : LineAnnotation.begin(); class LineAnnotation{ public static void begin(){} public static void end(){} } LineAnnotation.end(); 修士論文発表会
現在の実装の限界 • 異なるブロックをまたぐ範囲の選択 • メソッドをまたぐ範囲は選択できない • for、while 文等のブロックをまたいでも選択可能 • finally 節の扱い • 選択範囲外の finally 節が範囲に含まれてしまう可能性がある(コンパイラによる) • Recover アドバイス内で例外を投げると、元の finally 節が無視される 修士論文発表会
finally 節に関する問題 例3 例2 例1 try { : : } catch (){ : } finally { : } try { : if (・・){ return; } : } catch (){ : } finally { (finally 節) } try { : } catch (){ : } finally { : } 例外発生 アドバイス throw e; アドバイス 追加した方の優先順位が 高いため finally 節が 実行されない 展開された finally 節も 含んでしまう finally 節が実行されない 修士論文発表会
実験について • 実験環境 • CPU : Intel Pentium 4 CPU 2.8GHz • メモリ : 1GB • OS : Microsoft Windows XP Professional SP2 • JVM バージョン : 1.5.0_06 • 目的 • GluonJ/Rで例外処理をアスペクトとして追加した場合のオーバーヘッドを測定 修士論文発表会
実験:try-catch文との実行速度の比較 public class Test { public void m() throws Exception{ a(); b(); } public void a() throws Exception{} public void b() {} } • メソッド m() を10億回 • try-catch文を追加 • アスペクトをウィーブ try{ } catch(Exception e){} @Glue class InsertTryCatch { @Recover(etype = "java.lang.Exception", advice = "") Pointcut p = Pcd.block(Pcd.call("test.Test#a(..)"), Pcd.call("test.Test#b(..)")); } 修士論文発表会
実験:行アノテーションの性能実験 public class Test { public void m() throws Exception{ @Line(begin) a(); @Line(end) b(); } public void a() throws Exception{} public void b() {} } @Glueclass InsertTryCatch { @Recover(etype = "java.lang.Exception", advice = "") Pointcut p = Pcd.block(Pcd.line("begin"), Pcd.line("end")); } 修士論文発表会
AspectJ との比較 • handler ポイントカット • 既に try-catch 文がプログラムに含まれている場合、catch 節の実行時をジョインポイントとして選択 • after throwing アドバイス • 選択されたジョインポイントが例外を投げて異常終了した場合に実行される • 例外を補足する範囲の粒度がメソッドボディ • Java の throws も同様の問題 • リカバリ処理の実装には使えない • 最後に必ず例外を投げなければならない 修士論文発表会
関連研究 • Eiffel や Ruby の retry 機構 • Java には存在しなかった • 範囲を選択するポイントカット • ループ処理のためのジョインポイント[Harbulot ら ‘06] • for 文等のブロック全体をポイントカット • ポイントカットできないループも数多く存在 • block ポイントカットと行アノテーションは任意の範囲を選択可能 修士論文発表会
まとめ・今後の課題 • GluonJ/R • 例外処理をロジックと分離して記述可能 • 範囲をポイントカットできる pointcut 指定子 • 容易にリカバリできる • アドバイスの中で再試行可能なメソッド • オーバーヘッドはほとんどなし • 今後の課題 • 現実のアプリケーションに適用 • うまく記述できるか • コードサイズをどのくらい減らすことができるか 修士論文発表会