それでも環境依存は残っている                     起きたり起きなかったりする問題のお話Hiroki TatenoSenior Principal EngineerSustaining EngineeringOracle Ja...
ここで示されている見解は私個人のものであり、所属会社の見解を反映したものではありません       OracleとJavaは、Oracle Corporation 及びその子会社、関連会社の米国及びその他の国における登録商標です。       ...
 はじめにProgramAgenda                                                                      反則勝ち or 反則負け                    ...
はじめに4   Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
自己紹介     氏名:立野広樹     仕事 : WebLogic Server 開発     ペンシルパズル好き ニコリ好き nikoli.com会員     2000年 日本オラクル入社     Java関連 トラブルシューティ...
理想の障害調査&修正     目的が明確     問題も明確     調査手法は自明     情報は十分     ゴールの判定も明確     良ゲー6   Copyright © 2013, Oracle and/or its af...
現実の障害調査&修正 目的が不明 問題が成立してない 調査手法が謎 情報不足 もうゴールしてもいいよね… 無理ゲー 無理ゲーを解けるゲームにする!7   Copyright © 2013, Oracle and/or its af...
そんな私の愛読書は…8   Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
常識(=思い込み)が通用しなくなる瞬間     i を2で割った余りは0か1しかないのか?     i + 1 > i は常に正しいか?     12 + 2l がなぜ33にならないのか?     知らない!! 興味ある!! という方は...
転ばぬ先の… ? 「罠・落とし穴・コーナーケース」 罠はある 一つ一つは小さいけど 何度でも蘇る 但し別の場所に Java Puzzlersがおしえてくれたこと コーナーケースがどこにあるかという「知識」ではなく…… コーナーケース...
It’s just a joke, but…プログラムが動かないときの、プログラマの言い訳第5位 それ、動かないとホントに困るの? (えっうん)第4位 そのOSには対応してないんだよね (たまにある…)第3位 使い方が悪い (これも実はよくある...
Let’s start !                                                                   環境依存                                      ...
反則勝ち or 反則負け13   Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
要件      ロードされてるクラスが何なのか知りたい      -verbose:class      プログラムから知りたい…      java.lang.ClassLoaderをチェック!      getLoadedClas...
たぶんこれが正解だけど…       ClassLoaderを自作して       MBeanも自作する                                                                    ...
OpenJDK 7のClassLoaderを見てみると…public abstract class ClassLoader {  // The classes loaded by this class loader.// The only pu...
リフレクションを使ってみよう     Field classesField =       ClassLoader.class.getDeclaredField("classes");     classesField.setAccessibl...
反則勝ち     * JRockit     > java TestLoadedClasses     loaded:class oracle.jrockit.jfr.VMJFR     loaded:class TestLoadedClass...
反則負け     *IBM JDK (on AIX / Linux)       Exception in thread "main"       java.lang.NoSuchFieldException: classes         ...
リフレクションは両刃の剣 APIが用意されてないデータにアクセスできる どうしても欲しい場合がある 黒魔術 別の実装だと動かなくなるかもしれない バージョンアップしたら動かなくなるかもしれない 分かって使うしかない 分かっていても...
ピタゴラスイッチ21   Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
どっちが新しい?     File file1 = new File("file1");     file1.createNewFile();     File file2 = new File("file2");     file2.crea...
異なる実行結果     * Windows     > java TestLastModified     file1 is old.     * Linux etc...                                    ...
ファイルシステムの時刻精度      “通常、秒単位に丸められたファイル変更時刻をサ        ポートしますが、中にはもっと高い精度をサポートす        るものもあります” (from java.io.File)         ...
殆どのケースでは問題ではない      どういう時に比較したいか?      よくあるパターン                  ソースファイルを生成→コンパイルしてバイナリを生成                  ソースファイルが更新...
ヒヤリ・ハット      唐突に話題は転換する      ハインリッヒの法則「重大事故の陰に29倍の軽度事故と、300       倍のニアミスが存在する」(from wikipedia「ヒヤリ・ハット」)      1件の重大バグの裏に...
ピタゴラスイッチ(1)      よくある要件      「スクリプト」を受け取って、そこから「Javaソース」を作って、       最終的にJavaコンパイラで「クラスファイル」を生成したい。       ……大量に。         ...
ピタゴラスイッチ(2)     public void generate(List<Task> tasks) {        foreach (Task task : tasks) {            File source = gen...
ピタゴラスイッチ(3)      大量すぎるので中間ファイルはメモリに格納しよう      メモリは正義!!                                                                  ...
ピタゴラスイッチ(4)     public void generate(List<Task> tasks) {        foreach (Task task : tasks) {            byte[] source = g...
ピタゴラスイッチ(5)      1つ1つ処理するのは非効率だからまとめよう!                                       scripts                              source...
ピタゴラスイッチ(6)     public void generate(List<Task> tasks) {       foreach (Task task : tasks) {         sourceList.add(genera...
ピタゴラスイッチ(7)      まとめすぎてOutOfMemoryErrorになった……      適当なサイズでタスク分割しよう                                scripts               ...
ピタゴラスイッチ(8)     public void generate2(List<Task> tasks) {       List<List<Task>> divided =         divideTasks(tasks, JOB_...
ピダゴラスイッチ(9) 分割したコンパイルタスク間でソースファイルに依存関係があるぞ バイナリはファイルに書き出してあるし タスクをまたぐ依存関係は、書き出したファイルを使おう ソースよりバイナリのほうが古いと困るな 再コンパイルの仕...
ピタゴラスイッチ(10)     public void checkRecompile(File binary) {       File source = getSourceFile(binary);       if (source.las...
ピタゴラスイッチ(11)      後日……      『あの、ものすごく大規模なプロジェクトのタスクを日本語で書        いた時、Windowsだけちゃんと動かないんですケド……』     問題発生!!      スクリプトを日...
ピタゴラスイッチ(12) エラー「Javaソース上でダブルクォーテーションが閉じてないよ」 典型的な「非MS932のソースをMS932としてコンパイルした」問題  (昔はたまに遭遇したが最近ほとんど見ない…) -Dfile.encodin...
ピタゴラスイッチ(13) 原因1        歴史的経緯により、日本語Windows環境は、javacのデフォルト     ソースコードエンコーディングはMS932。別の文字コードで書か     れてる場合は、明示的にエンコーディングを指...
ピタゴラスイッチ(14) 原因3       巨大なタスクを分割処理する変更を加えた時に、オンメモリのク     ラスファイルだけではなく、ファイルシステム上のファイルが参     照されるようになっていた。 原因4    ソースとバイ...
ピタゴラスイッチ(15) 帰結             原因2+原因3+原因4+Windowsのファイル日付精度が              異なる現象、の複合により、Javaファイルの再コンパイル              が走るようにな...
ピタゴラスイッチ(15) 対処       どんな時も、ソースを書き出してからバイナリを書きだす 教訓       大山鳴動しても底にいるのはねずみ一匹くらいかも       一匹じゃなくてもっといるかも       小さな違いも、...
完全犯罪43   Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
よくある処理      ディレクトリを作る      その下にファイルを作る      何かする      ファイルを消す      ディレクトリを消す      →書いてみる44   Copyright © 2013, Oracl...
処理のひな型     File dir = new File("temp");     dir.mkdir();     File file = new File(dir, "file");     file.createNewFile(); ...
doSomething (バグ入り)     private static void doSomething(File file)     throws IOException {         FileOutputStream os =  ...
結果(1)     $ java TestD     (no error!)47   Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
結果(2)     > java TestD     Exception in thread "main"     java.io.IOException: failed to delete:temp¥file             at T...
結果(3)     $ java TestD     Exception in thread "main"     java.io.IOException: failed to delete:temp             at TestD....
結果まとめ                                                                 環境             結果1                                  ...
Linux と Windowsの動作の違い      UNIXは、オープンしているファイルを削除できる      というか、“delete on last close” という定型コード      削除したファイルはクローズするまで存在が...
NFSはもっと恐ろしい      UNIXは、ファイルを削除してもファイルにアクセスできる      NFSプロトコルでは、「ファイル名」がないとファイルにアクセス       できない      すなわち 「オープンされてるファイルは削...
Silly Rename      NFS上のファイルをオープン      オープンしたままファイルを削除すると                  ファイルは削除されない                  temp -> .nfsXXX...
発生していた現象      NFS上にファイル作成      NFS上でオープン中のファイルをdelete()      NFSがファイルを.nfsXXXXにリネーム      ディレクトリ削除時、ディレクトリ下にファイル.nfsXXX...
後からはわからない      再現すればそれほど難しい問題ではない      deleteに失敗する所にブレークポイントを設定      しかし他の環境で起きた場合は?      痕跡は残らないので調査が困難      close忘れ...
doSomething (バグフリー)     private static void doSomething(File file)     throws IOException {       try (FileOutputStream os...
One more thing…      しかし、この問題が本当に恐ろしいのは、バグでなく        ても起きる点である。57   Copyright © 2013, Oracle and/or its affiliates. All r...
doSomething (mmap ver)     private static void doSomething(File file)     throws IOException {       try (RandomAccessFile...
原因と結果      クローズ忘れていた時と同じ      unmapされてないfileは…      JavaのFileChannelにはmap()はあるけどunmap()がない      unmap()はどこから呼ばれる?     ...
unmap時のcall stack (java + native)     #0  0x0000003dc22d0d20 in munmap () from         /lib64/libc.so.6     #1 0x00002aaab...
sun.nio.ch.FileChannelImpl     public MappedByteBuffer map(MapMode mode, long      position, long size) throws IOException...
Direct-X-Buffer.java.templete     // For memory-mapped buffers -- invoked by     // FileChannelImpl via reflection     pro...
sun.misc.Cleaner* General-purpose phantom-reference-based cleaners. * <p> Cleaners are a lightweight and more robust alter...
mapがunmapされるとき      MappedBufferがGCされる      ReferenceHandlerスレッドが動き出す      Cleanerに登録されていたUnmapperが呼び出される      munmapシ...
邪道しかない      動かす方法はあるが……                                                                 _人人人人人人人_                        ...
蛇足      実は、本当に起きた現象では、ここでは言い尽くせない理由        のせいで、テコでもファイルディスクリプタが消えず、結局Silly        Renameのせいでファイルが消えない問題は解決しなかった      でも...
おわりに67   Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
まとめ      手元で再現しないバグはある      様々な異なる実装があっても仕様を満たしていればJava      同じインタフェースでも返り値の精度が違うことがある      同じインタフェースでも動作の意味が違うことがある  ...
Q&Aコーナー                                                                            ( ´_ゝ`)フーン69   Copyright © 2013, Oracle...
70   Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
Upcoming SlideShare
Loading in …5
×

それでも環境依存は残っている~起きたり起きなかったりする問題のお話~

4,937 views

Published on

0 Comments
9 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
4,937
On SlideShare
0
From Embeds
0
Number of Embeds
1,065
Actions
Shares
0
Downloads
15
Comments
0
Likes
9
Embeds 0
No embeds

No notes for slide

それでも環境依存は残っている~起きたり起きなかったりする問題のお話~

  1. 1. それでも環境依存は残っている 起きたり起きなかったりする問題のお話Hiroki TatenoSenior Principal EngineerSustaining EngineeringOracle Japan1 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  2. 2. ここで示されている見解は私個人のものであり、所属会社の見解を反映したものではありません OracleとJavaは、Oracle Corporation 及びその子会社、関連会社の米国及びその他の国における登録商標です。 文中の社名、商品名等は各社の商標または登録商標である場合があります。2 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  3. 3.  はじめにProgramAgenda  反則勝ち or 反則負け  ピタゴラスイッチ  完全犯罪  おわりに3 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  4. 4. はじめに4 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  5. 5. 自己紹介  氏名:立野広樹  仕事 : WebLogic Server 開発  ペンシルパズル好き ニコリ好き nikoli.com会員  2000年 日本オラクル入社  Java関連 トラブルシューティングひとすじ13年目  Sustaining Engineering =問題解析&障害修正  全世界に400+人以上  解析、解析、解析、そしてまた解析。 時々修正。5 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  6. 6. 理想の障害調査&修正  目的が明確  問題も明確  調査手法は自明  情報は十分  ゴールの判定も明確  良ゲー6 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  7. 7. 現実の障害調査&修正 目的が不明 問題が成立してない 調査手法が謎 情報不足 もうゴールしてもいいよね… 無理ゲー 無理ゲーを解けるゲームにする!7 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  8. 8. そんな私の愛読書は…8 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  9. 9. 常識(=思い込み)が通用しなくなる瞬間  i を2で割った余りは0か1しかないのか?  i + 1 > i は常に正しいか?  12 + 2l がなぜ33にならないのか?  知らない!! 興味ある!! という方は、今すぐJava PuzzlersにGO!!9 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  10. 10. 転ばぬ先の… ? 「罠・落とし穴・コーナーケース」 罠はある 一つ一つは小さいけど 何度でも蘇る 但し別の場所に Java Puzzlersがおしえてくれたこと コーナーケースがどこにあるかという「知識」ではなく…… コーナーケースがどこにありうるかという「感覚」10 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  11. 11. It’s just a joke, but…プログラムが動かないときの、プログラマの言い訳第5位 それ、動かないとホントに困るの? (えっうん)第4位 そのOSには対応してないんだよね (たまにある…)第3位 使い方が悪い (これも実はよくある…)第2位 プログラムが壊れたとき、君はどこにいたんだ?(責任転嫁!)第1位 私のマシンではちゃんと動くよ (from http://hp.vector.co.jp/authors/VA000092/jokes/ )11 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  12. 12. Let’s start ! 環境依存 コーナーケース 探求の旅へ…12 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  13. 13. 反則勝ち or 反則負け13 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  14. 14. 要件  ロードされてるクラスが何なのか知りたい  -verbose:class  プログラムから知りたい…  java.lang.ClassLoaderをチェック!  getLoadedClassNames() きっとあるよね….  存在しない….14 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  15. 15. たぶんこれが正解だけど… ClassLoaderを自作して MBeanも自作する _人人人人人人人_ > 面倒!!! <  ̄^Y^Y^Y^Y^Y^Y ̄15 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  16. 16. OpenJDK 7のClassLoaderを見てみると…public abstract class ClassLoader { // The classes loaded by this class loader.// The only purpose of this table// is to keep the classes from being GCed until// the loader is GCed.private final Vector<Class<?>> classes = new Vector<>();いまどき Vector なんて… ( ´,_ゝ`)プッprivateだけどリフレクション使えば取り出せるぞ 16 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  17. 17. リフレクションを使ってみよう Field classesField = ClassLoader.class.getDeclaredField("classes"); classesField.setAccessible(true); Vector<Class> classes = (Vector<Class>)classesField.get( ClassLoader.getSystemClassLoader()); for (Class classObj: classes) System.out.println("loaded:"+classObj);17 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  18. 18. 反則勝ち * JRockit > java TestLoadedClasses loaded:class oracle.jrockit.jfr.VMJFR loaded:class TestLoadedClasses * + 巛ヽ 〒 ! + 。 + 。 * + 。 | | ... * + / / イヤッッホォォォオオォオウ! ∧_∧ / / * HotSpot (´∀` / / + ,- / ュヘ f |* 。 + 。 + 。 + * 。 + 。 〈_} ) | > java TestLoadedClasses / !+ 。 + + * ./ ,ヘ | loaded:class TestLoadedClasses ガタン ||| j / | | ||| ――――――――――――18 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  19. 19. 反則負け *IBM JDK (on AIX / Linux) Exception in thread "main" java.lang.NoSuchFieldException: classes * + 巛ヽ 〒 ! + 。 + 。 * | 。 | | ゴツン |★ / / + 。 + 。 +  Sun JDK由来のクラスライブラリ ___|_∧ / / (´∀` / / + 。 。 * 。 ,- f 以外で動かない / ュヘ 〈_} ) |* | + 。 + 。 + / !+ 。 + + *  互換性はTCKの範囲 ./ ,ヘ | ガタン ||| j / | | ||| ――――――――――――19 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  20. 20. リフレクションは両刃の剣 APIが用意されてないデータにアクセスできる どうしても欲しい場合がある 黒魔術 別の実装だと動かなくなるかもしれない バージョンアップしたら動かなくなるかもしれない 分かって使うしかない 分かっていてもハマるときはハマる20 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  21. 21. ピタゴラスイッチ21 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  22. 22. どっちが新しい? File file1 = new File("file1"); file1.createNewFile(); File file2 = new File("file2"); file2.createNewFile(); System.out.println( file2.lastModified() > file1.lastModified() ? “file1 is old" : "file1 is NOT older than file2");22 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  23. 23. 異なる実行結果 * Windows > java TestLastModified file1 is old. * Linux etc... 何故? $ java TestLastModified file1 is NOT older than file2.23 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  24. 24. ファイルシステムの時刻精度  “通常、秒単位に丸められたファイル変更時刻をサ ポートしますが、中にはもっと高い精度をサポートす るものもあります” (from java.io.File)  Windows : NTFSは100ns単位で保持  Linux : ext3は秒単位  lastModifiedの比較は環境によって動作が違う  ….で、何が問題なわけ?24 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  25. 25. 殆どのケースでは問題ではない  どういう時に比較したいか?  よくあるパターン  ソースファイルを生成→コンパイルしてバイナリを生成  ソースファイルが更新→再コンパイルしてバイナリも更新  ソースファイルがバイナリよりも新しかったら問題になる  そういうことはない  ….で、何が問題なわけ?25 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  26. 26. ヒヤリ・ハット  唐突に話題は転換する  ハインリッヒの法則「重大事故の陰に29倍の軽度事故と、300 倍のニアミスが存在する」(from wikipedia「ヒヤリ・ハット」)  1件の重大バグの裏に29倍の軽微な不具合と300倍の勘違 いがある?  「一つ一つの仕様の勘違いは大きな問題でなくて も、たまたま複数の動作が重なったとき、不具合 になる、ことがあるかも」26 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  27. 27. ピタゴラスイッチ(1)  よくある要件  「スクリプト」を受け取って、そこから「Javaソース」を作って、 最終的にJavaコンパイラで「クラスファイル」を生成したい。 ……大量に。 Java Java Class Script Java Source Class Class Script Source File Script FileJava Source File File Class File Script Source File File File27 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  28. 28. ピタゴラスイッチ(2) public void generate(List<Task> tasks) { foreach (Task task : tasks) { File source = generateSourceCode(task); compileCode(source, sourceEncoding); } }28 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  29. 29. ピタゴラスイッチ(3)  大量すぎるので中間ファイルはメモリに格納しよう  メモリは正義!! byte[] Class Script byte[] Java Class Script byte[] Java File Class Script byte[] Source Java File Class Script Sourcebyte[] Java File Class Script Source Java File Source File Source29 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  30. 30. ピタゴラスイッチ(4) public void generate(List<Task> tasks) { foreach (Task task : tasks) { byte[] source = generateSourceCode(task); writeSource(source) compileCode(source, sourceEncoding); } }30 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  31. 31. ピタゴラスイッチ(5)  1つ1つ処理するのは非効率だからまとめよう! scripts sources classes31 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  32. 32. ピタゴラスイッチ(6) public void generate(List<Task> tasks) { foreach (Task task : tasks) { sourceList.add(generateSourceCode(task)); } compleSources(sourceList, sourceEncoding); writeSources(sourceList); }32 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  33. 33. ピタゴラスイッチ(7)  まとめすぎてOutOfMemoryErrorになった……  適当なサイズでタスク分割しよう scripts sources classes scripts sources classes scripts scripts sources sources classes classes scripts sources classes33 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  34. 34. ピタゴラスイッチ(8) public void generate2(List<Task> tasks) { List<List<Task>> divided = divideTasks(tasks, JOB_SIZE); for (List<Task> dividedTasks: divided) { generate(dividedTasks); } }34 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  35. 35. ピダゴラスイッチ(9) 分割したコンパイルタスク間でソースファイルに依存関係があるぞ バイナリはファイルに書き出してあるし タスクをまたぐ依存関係は、書き出したファイルを使おう ソースよりバイナリのほうが古いと困るな 再コンパイルの仕組みを入れておこう35 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  36. 36. ピタゴラスイッチ(10) public void checkRecompile(File binary) { File source = getSourceFile(binary); if (source.lastModified() > binary.lastModified()) doRecompile(source); // 時刻比較してる }36 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  37. 37. ピタゴラスイッチ(11)  後日……  『あの、ものすごく大規模なプロジェクトのタスクを日本語で書 いた時、Windowsだけちゃんと動かないんですケド……』 問題発生!!  スクリプトを日本語以外で書いた場合は動く  日本語で書いた場合もWindows以外なら動く37 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  38. 38. ピタゴラスイッチ(12) エラー「Javaソース上でダブルクォーテーションが閉じてないよ」 典型的な「非MS932のソースをMS932としてコンパイルした」問題 (昔はたまに遭遇したが最近ほとんど見ない…) -Dfile.encodingで明示的に文字コードを設定したら動く でも、設定しなくても99%動く コード上ではencodingを明示的に指定している ああ、それなのに、それなのに38 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  39. 39. ピタゴラスイッチ(13) 原因1  歴史的経緯により、日本語Windows環境は、javacのデフォルト ソースコードエンコーディングはMS932。別の文字コードで書か れてる場合は、明示的にエンコーディングを指定しないとエラー。 原因2  ソースをオンメモリに格納する最適化をした時に、バイナリを先 に書き出して、それからソースを書きだしていた。(バグといえば バグかもしれないが、ファイルに書き出したソースはコンパイル とは関係ないはずだった) 39 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  40. 40. ピタゴラスイッチ(14) 原因3  巨大なタスクを分割処理する変更を加えた時に、オンメモリのク ラスファイルだけではなく、ファイルシステム上のファイルが参 照されるようになっていた。 原因4  ソースとバイナリの日付を比較して、ソースが古い場合はソー スが自動的に再コンパイルされるようにした。が、ここではソー スの文字エンコードを明示的に指定できなかった(ファイルしか 見えないので当然ではある) 40 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  41. 41. ピタゴラスイッチ(15) 帰結  原因2+原因3+原因4+Windowsのファイル日付精度が 異なる現象、の複合により、Javaファイルの再コンパイル が走るようになってしまった。  原因1によりJavaファイルの文字コードとプラットフォーム デフォルトの文字コードが異なっていた。以上により、コン パイルエラーが発生した。  Windowsでしか起きない為、テストもすり抜けていた。41 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  42. 42. ピタゴラスイッチ(15) 対処  どんな時も、ソースを書き出してからバイナリを書きだす 教訓  大山鳴動しても底にいるのはねずみ一匹くらいかも  一匹じゃなくてもっといるかも  小さな違いも、複数組み合わさると、予想しない連鎖をする (ピタゴラスイッチ的)  それが環境依存の振る舞いだと、より複雑に…… 42 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  43. 43. 完全犯罪43 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  44. 44. よくある処理  ディレクトリを作る  その下にファイルを作る  何かする  ファイルを消す  ディレクトリを消す  →書いてみる44 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  45. 45. 処理のひな型 File dir = new File("temp"); dir.mkdir(); File file = new File(dir, "file"); file.createNewFile(); doSomething(file); if (!file.delete()){ throw new IOException("failed to delete:"+file); } if (!dir.delete()){ throw new IOException("failed to delete:"+dir); }45 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  46. 46. doSomething (バグ入り) private static void doSomething(File file) throws IOException { FileOutputStream os = new FileOutputStream(file); os.write("test test test¥n".getBytes()); os.flush(); // os.close(); }46 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  47. 47. 結果(1) $ java TestD (no error!)47 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  48. 48. 結果(2) > java TestD Exception in thread "main" java.io.IOException: failed to delete:temp¥file at TestD.main(TestD.java:15)48 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  49. 49. 結果(3) $ java TestD Exception in thread "main" java.io.IOException: failed to delete:temp at TestD.main(TestD.java:18) $ ls –al temp (empty!!)49 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  50. 50. 結果まとめ 環境 結果1 Linux : 正常動作 結果2 Windows : ファイル削除に失敗 結果3 Linux + NFS : ディレクトリ削除に失敗  結果が全部違う…  しかも、結果3 に至っては、空ディレクトリなのになぜ かディレクトリの削除に失敗している……50 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  51. 51. Linux と Windowsの動作の違い  UNIXは、オープンしているファイルを削除できる  というか、“delete on last close” という定型コード  削除したファイルはクローズするまで存在が保証されている  Windowsのファイルロックは悲観ロック しかも自動  ファイルをオープンしていたら削除等が出来なくなる  UNIXと同じようには書けない51 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  52. 52. NFSはもっと恐ろしい  UNIXは、ファイルを削除してもファイルにアクセスできる  NFSプロトコルでは、「ファイル名」がないとファイルにアクセス できない  すなわち 「オープンされてるファイルは削除できない」  だけどWindowsと違って「悲観ロックではない」「削除は失敗で きない」  “Silly Rename” (see also: http://nfs.sourceforge.net/ )52 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  53. 53. Silly Rename  NFS上のファイルをオープン  オープンしたままファイルを削除すると  ファイルは削除されない  temp -> .nfsXXXXに自動的に名前が変更  NFSクライアントプロセス終了時  名前変更されたファイルが自動的に削除される  .nfsXXXX -> 削除53 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  54. 54. 発生していた現象  NFS上にファイル作成  NFS上でオープン中のファイルをdelete()  NFSがファイルを.nfsXXXXにリネーム  ディレクトリ削除時、ディレクトリ下にファイル.nfsXXXXが存在  ディレクトリ削除失敗(問題発生)  IOExceptionでプログラム終了  プロセス終了時にNFS側で.nfsXXXX削除 (証拠隠滅完了!暗黙的!全自動!)54 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  55. 55. 後からはわからない  再現すればそれほど難しい問題ではない  deleteに失敗する所にブレークポイントを設定  しかし他の環境で起きた場合は?  痕跡は残らないので調査が困難  close忘れという単純なバグでも起きる  JDK7にしよう! try-with-resources を使おう!55 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  56. 56. doSomething (バグフリー) private static void doSomething(File file) throws IOException { try (FileOutputStream os = new FileOutputStream(file)) { os.write("test test test¥n".getBytes()); os.flush(); } }56 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  57. 57. One more thing…  しかし、この問題が本当に恐ろしいのは、バグでなく ても起きる点である。57 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  58. 58. doSomething (mmap ver) private static void doSomething(File file) throws IOException { try (RandomAccessFile ras = new RandomAccessFile(file,"rw")) { FileChannel fc = ras.getChannel(); MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 4096); mbb.put("test test test¥n".getBytes()); }}58 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  59. 59. 原因と結果  クローズ忘れていた時と同じ  unmapされてないfileは…  JavaのFileChannelにはmap()はあるけどunmap()がない  unmap()はどこから呼ばれる?  unmap()はいつ呼ばれる?  Reference Handler Thread上で発見59 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  60. 60. unmap時のcall stack (java + native) #0 0x0000003dc22d0d20 in munmap () from /lib64/libc.so.6 #1 0x00002aaab6870477 in Java_sun_nio_ch_FileChannelImpl_unmap0 () [2] sun.nio.ch.FileChannelImpl$Unmapper.run (FileChannelImpl.java:746) [3] sun.misc.Cleaner.clean (Cleaner.java:142) [4] java.lang.ref.Reference$ReferenceHanlder.run (Reference.java:141)60 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  61. 61. sun.nio.ch.FileChannelImpl public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { .... Unmapper um = new Unmapper(addr, mapSize, isize, mfd); .... return Util.newMappedByteBufferR( isize, addr + pagePosition, mfd, um);61 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  62. 62. Direct-X-Buffer.java.templete // For memory-mapped buffers -- invoked by // FileChannelImpl via reflection protected Direct$Type$Buffer$RW$(int cap, long addr, FileDescriptor fd, Runnable unmapper) { #if[rw] super(-1, 0, cap, cap, fd); address = addr; cleaner = Cleaner.create(this, unmapper);62 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  63. 63. sun.misc.Cleaner* General-purpose phantom-reference-based cleaners. * <p> Cleaners are a lightweight and more robust alternative to finalization. * They are lightweight because they are not created by the VM and thus do not  長々といろいろ書いてあるけど要は * require a JNI upcall to be created, and because their cleanup code is * invoked directly by the reference-handler thread rather than by the * finalizer thread. They are more robust because they use phantom references,  クラスライブラリの中からだけつかえる * the weakest type of reference object, thereby avoiding the nasty ordering * problems inherent to finalization.  ファントムリファレンスベースのFinalizerもどき * <p> A cleaner tracks a referent object and encapsulates a thunk of arbitrary * cleanup code. Some time after the GC detects that a cleaners referent has  でもFinalizerの代わりにはならない * become phantom-reachable, the reference-handler thread will run the cleaner. * Cleaners may also be invoked directly; they are thread safe and ensure that  単純で素直なクリーンアップの時だけ使える * they run their thunks at most once. * <p> Cleaners are not a replacement for finalization. They should be used →unmap()はGCされたら呼ばれる * only when the cleanup code is extremely simple and straightforward.  * Nontrivial cleaners are inadvisable since they risk blocking the * reference-handler thread and delaying further cleanup and finalization.63 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  64. 64. mapがunmapされるとき  MappedBufferがGCされる  ReferenceHandlerスレッドが動き出す  Cleanerに登録されていたUnmapperが呼び出される  munmapシステムコールが呼ばれる64 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  65. 65. 邪道しかない  動かす方法はあるが…… _人人人人人人人_ > System.gc(); <  ̄^Y^Y^Y^Y^Y^Y ̄  邪道!実装依存!  邪道!実行時依存!  邪道!タイミング依存!65 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  66. 66. 蛇足  実は、本当に起きた現象では、ここでは言い尽くせない理由 のせいで、テコでもファイルディスクリプタが消えず、結局Silly Renameのせいでファイルが消えない問題は解決しなかった  でも… プログラムが動かないときの、プログラマの言い訳 第5位 それ、動かないとホントに困るの? (えっうん)  ユーザは正常に動作させたいだけであって、ディレクトリを消 したいわけではない(場合もある)66 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  67. 67. おわりに67 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  68. 68. まとめ  手元で再現しないバグはある  様々な異なる実装があっても仕様を満たしていればJava  同じインタフェースでも返り値の精度が違うことがある  同じインタフェースでも動作の意味が違うことがある  明確なバグがなくても環境によって動かないことがある  実装の抽象化は大事!でも破綻も付き物!  どこに罠があるかという感覚を養いたい68 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  69. 69. Q&Aコーナー ( ´_ゝ`)フーン69 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  70. 70. 70 Copyright © 2013, Oracle and/or its affiliates. All rights reserved.

×