ひしだまの変更履歴

ひしだまHPの更新履歴。
主にTRPGリプレイの元ネタ集、プログラミング技術メモと自作ソフト、好きなゲームや音楽です。

Spark or SerializationUtils or HashMapのバグ

2016-07-31 15:19:48 | PG(Java)

すべて伝聞なんだけど、面白い(興味深い)バグの話を聞いたので、メモしておく。
バグに関する話なので、本来はバージョンをちゃんと書くべきなんだけど、伝聞なので具体的なバージョンは書けないので悪しからず。

事の発端は、Sparkのジョブがたまに止まる(返ってこない)という事象が起きたことらしい。
この状態のStackTraceを見るとjava.util.HashMapのputメソッドで起きていたらしい。実行環境はJava7。これが起きるとCPU使用率が異様に高くなる。
つまり、@ITの『ThreadとHashMapに潜む無限回廊は実に面白い?』と同様の無限ループが起きていた模様。

で、Sparkはcommons-lang(lang3)のSerializationUtils.clone()を呼び出しており、そこでHashMapがスレッドセーフでない使われ方をしていたのが原因らしい。
(SerializationUtilsのソースはたぶんこれ。cloneメソッドの中でClassLoaderAwareObjectInputStreamインスタンスを作ってるんだけど、そのコンストラクター内でstaticフィールドのHashMap(primitiveTypesという変数名)にputしている。SerializationUtilsクラスには#ThreadSafe#というコメントがあるんだけど、スレッドセーフじゃないやんけ!(苦笑)
(ちなみに既に修正のプルリクエストは送られている)
(SerializationUtils.clone()は、最初の頃は別の実装(serializeしてdeserializeする)だったので、スレッドセーフだった模様)


で、個人的興味の1番目としては、「これは何のバグ(と言われるのか)?」ということ。

Spark使用中に起きたことなので、Spark利用者から見るとSparkのバグである。
しかし実際には、Sparkから利用しているユーティリティーのバグである。
StackTraceを見るとHashMapのバグに見えるかもしれない。

実はjava.util.HashMapのソースは、Java7とJava8では大幅に異なっている。
putメソッドを見た感じでは、今回のような無限ループがJava8で起きる可能性はかなり低いと思う。(スレッドセーフでないことに変わりはないので、変な結果にはなり得る。特にputし終わった後のsizeメソッドの結果は実際にかなりの頻度で変になる。無限ループにはなりにくい(ならない?)だろう、というだけ)
なので、もし実行環境をJava8に変えて試したとしたら同様の事象はほぼ起きなかったと思われるわけで、Java7のバグと見做されたかもしれない。

(なお、HashMapは明確にスレッド非セーフと謳われているので、スレッドセーフでない使い方をする方が問題である。HashMapで変な挙動をしていると思ったら、マルチスレッドで呼ばれてないかを確認するのが常道)


もうひとつの興味は、バージョンアップの問題。

SeralizationUtilsの変更履歴を見ると、最初はスレッドセーフだったのが、途中でスレッドセーフでなくなった(バグが入り込んだ)ようだ。
SparkがいつからSerializationUtilsを使い始めたのかは知らないが、最初は問題なかったのに、ライブラリーのバージョンを上げたら問題になった、ということなのかもしれない。
マルチスレッドの問題だけに、Spark側で少々テストを行ったくらいでは発覚しない可能性も高い。

やはりバージョンアップは難しいことだ、と改めて認識させられた。


あと、commons-lang内でのレビュー方法はどうだったのか、という問題もありそうではある。

マルチスレッドの問題が起きてからこのソースを見ると一目で問題あると分かるけれど、そうでないときにこの修正を見て問題があると分かるのは相当熟練者のような気も。
てゆーか、commons-langくらいのものなら、相当の熟練者にレビューして欲しい^^;


(追記1)こういう調査が出来るので、オープンソースは素晴らしいw
(追記2)@ITの記事を昔見ていたおかげで、HashMapで無限ループという事象がすんなり納得できた。障害の事例を公開してくれるのはほんとありがたいです。 

コメント

AsakusaFW 0.8.1 チュートリアル

2016-07-31 13:28:55 | PG(分散処理)

2016/7/26にAsakusa Framework0.8.1がリリースされた。→リリース情報
あわせて、Asakusa on Spark 0.3.1とAsakusa on M3BP 0.1.2もリリースされている。
従来のAsakusaFW(Asakusa on MapReduce相当)とon Spark・on M3BPは別バージョンで管理されているので、ちょっと違和感があるけど、要するにどれも新しくなったということ^^;

とりあえず目新しいのは、ドキュメントとして、Asakusa Frameworkチュートリアルが作られたこと。
Asakusaアプリケーションを作る手順に沿って、サンプルを実際に作るという態で書かれている。
自分のWordCountのサンプルもそれに近いんだけど、さすが公式の方が綺麗・丁寧(笑)

AsakusaFW 0.8.1としては、Direct I/Oで出力レコード数やバイト数がログに出るようになったのが嬉しい。
今まではMapReduceやM3BPのログを見る必要があった。

あと、Direct I/O line(0.7.5で導入されたがsandboxの機能だった)が正式機能になった。
使い方は特に変わっていないけれど、build.gradleにsandboxのdependenciesを記述する必要が無くなった。

Asakusa on Spark・Asakusa on M3BPについては、バグ修正。
特にon M3BPについてはcom.asakusafw.m3bp.output.buffer.sizeが反映されないというバグがあって地味に困っていたが、直ったw 

コメント