miauのブログ

はてなダイアリー「miauの避難所」をはてなブログに移行しました

スレーブ側でのビルドでハマった

Hudson の分散ビルド環境を構築しているところなのですが、スレーブ側で Maven を使ったビルドを行うと、Javadoc の生成後のタイミングで、

[HUDSON] Archiving aggregated javadoc
2010/10/01 11:11:59 hudson.remoting.Channel$ReaderThread run
致命的: I/O error in channel channel
java.io.InvalidClassException: hudson.remoting.Pipe$ConnectCommand; local class incompatible: stream classdesc serialVersionUID = -6288356765470642528, local class serialVersionUID = -9128735897846418140
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:562)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
	at hudson.remoting.Channel$ReaderThread.run(Channel.java:856)

こんなエラーになってしまっていました。

原因はマスターとスレーブで hudson.remoting.Pipe のバージョンが食い違っていたことのようで。マスターを 1.378 にバージョンアップしたら、スレーブ側でも 1.378 にしておかないとエラーになるケースがある、ということみたいです。

以降ハマっぷりをいろいろ書いていきますが、あまり意味のある情報はないかもです。

ソースコードはどうなってるの?

serialVersionUID が食い違ってるらしいけど、ソースコード上ではどのように指定されてるのかな?ということで、hudson.remoting.Pipe のソースコード を確認してみます。

どうも内部クラスである ConnectCommand には serialVersionUID は指定されていない様子。

Java オブジェクト直列化仕様: 4 - クラス記述子 によると、

注 - 内部クラスである直列化可能クラス、または内部クラスを持つ直列化可能クラスが serialVersionUID データメンバを宣言することを、強くお勧めします。これは、コンパイラの実装が異なる場合、内部クラスの実装のために生成される合成メンバに異なる名前を付けることができ、これらの名前が SUID の現在の計算に使用されるためです。

とのことで、明示的に指定しない場合、コンパイラの実装が違うと異なる SUID が生成されてしまうとか。これが原因かな?

JRE のバージョン確認したりとか(無駄足)

ここで、「コンパイラが異なる場合〜」というのを「JVM が異なる場合〜」という意味合いに勘違いして、マスターとスレーブの JRE のバージョンをあわせたりしてました。

まあ見当違いだったのでどうでもいいんですが、マスター側は「"java" -Xrs 〜」みたいに起動されてるのに、スレーブ側は「"C:\Program Files\Java\jre6\bin\java.exe" -Xrs 〜」として起動されてるんだなー、とか気づいたりしました。

serialVersionUID を調べてみる

によると、JDK に入っている serialver というのを使えば確認できるとのこと。

スレーブ側

まずはスレーブ側の slave.jar で hudson.remoting.Pipe$ConnectCommand の serialVersionUID を確認してみます。

>serialver -classpath slave.jar hudson.remoting.Pipe$ConnectCommand
hudson.remoting.Pipe$ConnectCommand:    static final long serialVersionUID = -9128735897846418140L;

エラーメッセージによると

  • stream classdesc serialVersionUID = -6288356765470642528
  • local class serialVersionUID = -9128735897846418140

とのことだったので、これは期待どおりですね。

念のためスレーブ側にあった maven2.1-interceptor.jar、maven-agent.jar、maven-interceptor.jar の 3 ファイルも確認しましたが、これらでは hudson.remoting.Pipe$ConnectCommand は定義されていないようでした。

マスター側

hudson.war を解凍して、

>for /r %I in (*.jar) do @(serialver -classpath "%I" hudson.remoting.Pipe$ConnectCommand 2>nul && echo   -- %I)

のようにして .jar ファイルを classpath に渡しながらループしてやります。(.jar がたくさんあって見づらいので、取得できたものだけを表示しています。)

すると、

hudson.remoting.Pipe$ConnectCommand:    static final long serialVersionUID = -9128735897846418140L;
  -- E:\tmp\hudson\WEB-INF\hudson-cli.jar
hudson.remoting.Pipe$ConnectCommand:    static final long serialVersionUID = -9128735897846418140L;
  -- E:\tmp\hudson\WEB-INF\remoting.jar
hudson.remoting.Pipe$ConnectCommand:    static final long serialVersionUID = -9128735897846418140L;
  -- E:\tmp\hudson\WEB-INF\slave.jar
hudson.remoting.Pipe$ConnectCommand:    static final long serialVersionUID = -9128735897846418140L;
  -- E:\tmp\hudson\WEB-INF\lib\hudson-core-1.353.jar
hudson.remoting.Pipe$ConnectCommand:    static final long serialVersionUID = -9128735897846418140L;
  -- E:\tmp\hudson\WEB-INF\lib\remoting-1.353.jar

とのことで。slave.jar 以外のどれかで serialVersionUID = -6288356765470642528 になってると疑ってたんだけど、ハズレのようで。どこかのバージョンでコンパイラを変えたとかあるのかな?ということで 1.211、1.323 みたいに遡ってみたけど、どちらも同じ値でした。

普通に考えたらコンパイル環境変えるわけないし、ストリームが壊れてるほうを疑ったほうがいいかもなぁ。でもクラス名が一致してるのに serialVersionUID が食い違うなんて可能性はないよなぁ・・・。

・・・とかうんうん唸ってて、ふと気づいたんですが。バージョンを 1.378 に上げたはずなのに、hudson-core-1.353.jar になってる。さっき調べた hudson.war は C:\TracLight\hudson\hudson.war から持ってきたやつだったんですが、TracLightning で実際に稼働している Hudson は C:\TracLight\projects\hudson\.hudson\hudson.war のほうみたいですね。

ということで、実際に動いている 1.378 を解凍して serialVersionUID を確認すると、

hudson.remoting.Pipe$ConnectCommand:    static final long serialVersionUID = -6288356765470642528L;
  -- E:\tmp\hudson\WEB-INF\hudson-cli.jar
hudson.remoting.Pipe$ConnectCommand:    static final long serialVersionUID = -6288356765470642528L;
  -- E:\tmp\hudson\WEB-INF\remoting.jar
hudson.remoting.Pipe$ConnectCommand:    static final long serialVersionUID = -6288356765470642528L;
  -- E:\tmp\hudson\WEB-INF\slave.jar
hudson.remoting.Pipe$ConnectCommand:    static final long serialVersionUID = -6288356765470642528L;
  -- E:\tmp\hudson\WEB-INF\lib\hudson-core-1.378.jar
hudson.remoting.Pipe$ConnectCommand:    static final long serialVersionUID = -6288356765470642528L;
  -- E:\tmp\hudson\WEB-INF\lib\remoting-1.378.jar

とのことで。ソースコードを改めて確認すると、コンパイラを変えたとかじゃなくて、単純にバージョンアップでクラスのメンバが増えたということみたい。

いろいろ調べたけど、結局のところ単純にマスターとスレーブでバージョンが食い違ってたってことか。ここに来るまでにずいぶん時間食っちゃったなぁ・・・。

スレーブのバージョンアップ

によると hudson.war に含まれる slave.jar に差し替えたとのことなので、WEB-INF\slave.jar を取り出してスレーブの C:\Program Files\hudson\slave.jar を上書き。その後 Hudson Slave を再起動すると、ようやくビルドが通りました。

Hudson slave agent を起動しても「もう登録されてるよ」とか言われてしまったし(※勘違いかも。sc delete hudsonslave でサービス削除して、再度サービスとして登録すれば OK?)、ノードの再登録は面倒だしで .war からの取り出しを行いましたが、Hudson の操作にしてはちょっと面倒ですね。管理画面からバージョンアップできたり、マスターのバージョンアップ時にスレーブのバージョンアップも一緒にできたりするとうれしいんですけど。

・・・と思ったけど(追記)

私が Windows のサービスとして登録しているから面倒だっただけで、javaws とかで起動してる人は毎回 .jar のダウンロードが行われるから、ノードの再起動だけで済むのかな。もしかして。

どのバージョンで変更されたかというと・・・

1.378 で

の対応として入ったみたいです。

ただチケットが reopen されていて、この変更では問題あったので revert して別のパッチを当てて〜ということも行われているみたいなので、また近々バージョンアップが必要になるかもしれません。