読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり、所属会社の見解ではありません。 GitHub: takahashikzn

dracutが古いカーネルのinitramfsを勝手に作る

いつからかそうなったのかは覚えていないのですが…

新しいバージョンのカーネルをインストールすると、なぜか古いカーネルバージョン(しかもアンインストール済み)のinitramfsも作ろうとするようになっていました。

で、そのうち/bootに壊れたinitramfsファイルを大量に書き込み過ぎて容量を使い果たし、dracutが強制停止します。


それはまだいいとしても、今まさにインストールしたバージョンのinitramfsを作る前に止まってしまうので、OSがブートできなくなるという大問題が起こるわけです。

回避法→古いカーネルモジュールを手動で削除

dracutがターゲットにするカーネルのバージョンをどこで判定しているのか調べてみたのですが、 設定ファイル等ではないようです。

  • /etc/dracut
  • /usr/lib/dracut
  • /usr/system/dracut

この辺をgrepしてみたのですが何も出てこず。 もしやと思い、/lib/modulesを見てみるとそこには古いカーネルモジュール(のディレクトリ)が大量に残っている。こいつか犯人は。


というわけで、手動で古いモジュールを消すと、勝手にinitramfsを作る問題は解消されました。

EclEmma-3.0.0に更新したらエラー発生

本日、EclEmmaのアップデートが来ていたので更新してEclipseを再起動すると、意味不明なエラーが出てEclEmmaを使用できなくなりました。

次のようなエラーが発生します。

!MESSAGE Invalid input url:platform:/plugin/com.mountainminds.eclemma.ui/icons/full/eview16/coverage.gif
!STACK 0
java.io.IOException: Unable to resolve plug-in "platform:/plugin/com.mountainminds.eclemma.ui/icons/full/eview16/coverage.gif".
    at org.eclipse.core.internal.runtime.PlatformURLPluginConnection.parse(PlatformURLPluginConnection.java:61)
    at org.eclipse.core.internal.runtime.FindSupport.find(FindSupport.java:290)
    at org.eclipse.core.runtime.FileLocator.find(FileLocator.java:152)
    at org.eclipse.jface.resource.URLImageDescriptor.getFilePath(URLImageDescriptor.java:216)
    at org.eclipse.jface.resource.URLImageDescriptor.access$2(URLImageDescriptor.java:208)
    at org.eclipse.jface.resource.URLImageDescriptor$URLImageFileNameProvider.getImagePath(URLImageDescriptor.java:53)
    at org.eclipse.swt.internal.DPIUtil.validateAndGetImagePathAtZoom(DPIUtil.java:407)
    at org.eclipse.swt.graphics.Image.<init>(Image.java:684)
        ...

プラグインソースコードを読んでみたのですが原因は分からず。


その次にworkspace/.metadataの全ファイルを"com.mountainminds.eclemma.ui"でgrepしたら答えがわかりました。


解決方法

EclEmmaのタブを閉じて開き直せばOK。

EclEmma3.0から、プラグインのIDが変更になった(com.mountainminds.eclemma→org.eclipse.eclemma)ことにより、Eclipse内部のUIキャッシュの整合性が崩れたことが原因でした。

Tomcat-8.5.8以降ではHTTPパイプラインが動作しない

弊社はSeleniumを用いたガチなE2Eテストを全ての受託案件で実施しており、 CI環境では24H365Dで膨大なSeleniumスクリプトが動いています。


つい先日、CI環境のTomcatのバージョンを8.5.8に上げた所、意味不明なエラーが出てテストが失敗するようになりました。 こんな感じ。

10-Dec-2016 15:54:56.761 INFO [http-apr-8080-exec-2] org.apache.coyote.http11.Http11Processor.service Error parsing HTTP request header
 Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level.
 java.lang.IllegalArgumentException: Invalid character found in method name. HTTP method names must be tokens
    at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:417)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:667)
    ...

はい?何?と思ってHTTPログを見てみると、当該エラーが発生したHTTPリクエストは次のようになっています。

127.0.0.1 - - [10/Dec/2016:15:54:56] "GET /foo.js HTTP/1.1" 200 123  ←普通の応答
127.0.0.1 - - [10/Dec/2016:15:54:56] "-" 400 -    ←なにこれ??

で、現象としてはJSファイルのダウンロードのHTTPリクエストが失敗しているために、画面が正しく動作せずテスト失敗になるようです。


結論→FirefoxのHTTP PipeliningをOFFにしたら治った

色々試行錯誤してみたのですが、結果としてはFirefoxのHTTP PipeliningをOFFにしたら治まりました。

HTTP PipeliningはデフォルトでOFFになっているのですが、 「もしかしたらテスト実行が早くなるかな―」と淡い期待とともにONにしていたものです。

Tomcat-8.5.6までは何の問題もなく動作していたわけなので、「Tomcat-8.5.8以降ではHTTPパイプラインは動作しない」ということになります。(8.5.9でもダメだったので)


誰もHTTPパイプラインなんて使っていない?

Wikipediaによると(wikipedia:HTTPパイプライン)、

近年のウェブブラウザでは、Prestoを搭載していた時代のOperaのみがパイプライン化を完全に実装し既定で有効としていた。他のブラウザは、パイプライン化を実装しているものの問題があることから既定では無効化していた、あるいは実装していない。

だそうです。

自分の精神をコピーしたら何が起きるのか

こんな増田が話題になってます。

anond.hatelabo.jp

僕も先月に「ディアスポラ」を読み終わったところだったのでちょうどよいネタ。

ディアスポラ (ハヤカワ文庫 SF)

ディアスポラ (ハヤカワ文庫 SF)

この作品はマジで面白い。序盤はやや難解ですが、傑作だと思います。

  • 一人の「個人」であることを満たす要件とは何か?
  • 誰かが「存在する」とはどういうことなのか?

についての一つの考え方を垣間見ることが出来ました。


もし機械の脳が実現したとして

あなたは不治の病に侵されて余命半年とします。「なぜ俺がこんな目に…」と世界を呪う日々。

そんなある日、聞いたこともないような小難しい名称の研究所から、一人の研究者がやってきました。

「機械の脳を実用化しました。精神をコピーすれば不老不死も可能ですし、コピー先は外見があなたと瓜二つの生体ボディです。ぜひ被験者になって下さい。」

あなたは大喜び。

自分の精神をコピーすれば病を克服!むしろ永遠の命がこの手に!やったー!!

ぜひ俺を被験者にしてくれ、と二つ返事をするわけです。


しかし現実は甘くない

そんなハッピーエンドにはならないわけです。

精神をコピーしたら何が起きると思いますか?


…さて、コピー作業が終わり、麻酔が切れて目覚めたあなたが見たものは2つ。


  • 「相変わらず余命半年のままの哀れな自分」
  • 「他者から見たら完全に自分そのものに見える、自分ではない誰か」


ただそれだけ。


外見、立ち振舞い、記憶が完全に自分と同じなら、他者から見たらそれは実質的に、あなた以外の何者でもありません。

ここにあなたは病気を克服し、永遠の命を得ました。おめでとうございます。

ただし他者から見たら。


あなたはそのコピーを恨むでしょう。

「あいつは俺が過ごすはずだった幸せな日常を取り戻してやがる…本当の俺はここだ!!あいつはニセモノなんだ!!」

という具合に。


さて、ではこのコピーは何者なのでしょうか?


こんな観点の作品、誰か教えてください

ディアスポラはそういう話ではないんですよね―。

色々探してみたのですが、僕のグーグル力では見つけられませんでした。


個人的に近いと思っている作品

SOMA

Steamのホラーゲームです。 ややネタバレですが、物語の終盤で、上記のような葛藤に主人公は直面することになります。

store.steampowered.com

弟者兄者のプレイ動画はこちら。 www.youtube.com

AIの遺伝子

山田胡瓜さんのマンガです。ITmediaの記者から漫画家へ転身したという変わった経歴の持ち主です。

試し読みできる第1話がそんな感じの、自我同一性に関する話です。

追記

この日記を書いた2時間後にふと「機械の脳」でググったら即出てきました。

なんかすいませんでした。

detail.chiebukuro.yahoo.co.jp

更に追記

順列都市」を読み始めたのですが正にそんな話でした。

順列都市〈上〉 (ハヤカワ文庫SF)

順列都市〈上〉 (ハヤカワ文庫SF)

struts-2.5.5でI18nInterceptorの初期化失敗を無理やり回避する

Struts2

struts-2.5.5がリリースされました。

このリリースはかなり大きなインパクトのある変更がいくつか入っています。


最大のインパクトは、ActionContext#getParameters()の戻り値がjava.util.Mapからorg.apache.struts2.dispatcher.HttpParametersに変更されたこと。 これにより、これまで動作していたコードがコンパイルエラーになります。いわゆるbreaking changeというやつです。

詳しくはこちらの議論を参照。


いやお前、せめて前のAPIをDeprecatedで残すとかしろよ…コレはちょっとやり過ぎ。しかもHttpParametersクラス、APIが使いづらいし。

例えば値を追加する際、Mapのままだったら

ActionContext.getParameters().put("foo", new String[]{ "値" });

で済むところが、

Map<String, Parameter> params = new HashMap<>();
params.put("foo", new Parameter.Request("foo", "値"));

ActionContext.getParameters().appendAll(params);

としなければならなくなりました。単一の値をputする手段がないし、いちいちParameterに変換してやる必要があります。メンドクサイ。

起動すると謎のエラー発生

さて、上記API変更に伴い既存コードを修正して、いざ起動してみると今度は次のような例外が発生。

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.apache.struts2.interceptor.I18nInterceptor': Unsatisfied dependency expressed through bean property 'localeProvider'

localeProviderというBeanが未定義なのかと思ってstruts2-core-2.5.5.jar内のstruts-default.xmlをみてみると、きちんと

<bean type="com.opensymphony.xwork2.LocaleProvider" name="struts" class="com.opensymphony.xwork2.DefaultLocaleProvider" scope="singleton" />

は定義されている。なのに何故依存性解決に失敗するのだ。。。

ログをよく見てみると、次のようにも出力されています。

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.opensymphony.xwork2.LocaleProvider] is defined: expected single matching bean but found 543: jp.root42.r42fw_web.export.web.action.error.ErrorAction,...

これで答えがわかりました。犯人はstruts2-spring-pluginでした。いや、struts2-coreかな?どちらが悪いとは決めづらい微妙な感じです。

Struts2のActionクラスの基底クラスであるcom.opensymphony.xwork2.ActionSupportの定義は次のようになっており、

public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable {
    ...
}

LocaleProviderインタフェースを実装しています。そして、ActionクラスはSpringのコンテキストが初期化されたときにロードされてしまいます。 従ってI18nInterceptorの依存性解決のときに、struts-default.xmlで定義されているDefaultLocaleProviderと競合してLocaleProviderのBeanを一意に特定できないため失敗するわけです。

ワークアラウンド

解決方法ですが、僕は次のようにしました。

import java.util.Map;

import javax.servlet.ServletContext;

import com.opensymphony.xwork2.DefaultLocaleProvider;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.interceptor.I18nInterceptor;
import org.apache.struts2.spring.StrutsSpringObjectFactory;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;


public class WorkaroundStrutsSpringObjectFactory
    extends StrutsSpringObjectFactory {

    // 親クラスからコピペ
    @Inject
    public WorkaroundStrutsSpringObjectFactory(
        @Inject(value = StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE, required = false) final String autoWire,
        @Inject(value = StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE_ALWAYS_RESPECT, required = false) final String alwaysAutoWire,
        @Inject(value = StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_USE_CLASS_CACHE, required = false) final String useClassCacheStr,
        @Inject(value = StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_ENABLE_AOP_SUPPORT, required = false) final String enableAopSupport,
        @Inject final ServletContext servletContext, @Inject(StrutsConstants.STRUTS_DEVMODE) final String devMode,
        @Inject final Container container) {

        super(autoWire, alwaysAutoWire, useClassCacheStr, enableAopSupport, servletContext, devMode, container);
    }

    @Override
    public Object buildBean(final Class clazz, final Map<String, Object> extraContext) throws Exception {

        try {
            return super.buildBean(clazz, extraContext);
        } catch (final Exception e) {

            // I18nInterceptorの依存性解決失敗の場合、手動でオブジェクトを作って返す
            if ((clazz == I18nInterceptor.class)
                && (0 <= ExceptionUtils.indexOfType(e, NoUniqueBeanDefinitionException.class))) {

                final I18nInterceptor ret = new I18nInterceptor();
                ret.setLocaleProvider(new DefaultLocaleProvider());
                return ret;
            } else {
                throw e;
            }
        }
    }
}

struts.xmlにこのように記述してデフォルトのBeanを置き換えます。

<constant name="struts.objectFactory" value="[YOUR PACKAGE HERE].WorkaroundStrutsSpringObjectFactory" />

全くエレガントでない手法ですが、そのうち対策されるまでのツナギなので一先ずはこれで。

ついでに、親クラス側でエラーログが出てウザいので

<logger name="com.opensymphony.xwork2.spring.SpringObjectFactory" level="off" />

でログをOFFにしておきました。

Googleからスカウトされました

数日前、いきなりGoogleのリクルーターからメールが来ました。


メールの全文掲載はマズいと思うので要点だけ書くと、

  • ブログ(このブログのこと)とGithubを見て、特にJavaに関するプログラミングの能力が高いとお見受けした
  • Site Reliablity Engineers(SRE)チームで働く気はありませんか?
  • もしこの話に興味があるなら、チャットで話しましょう

とのこと。

メールに載っていたリクルーターの氏名でググってみたところ、アイルランドの方でした。


メールを見て最初に思ったのは「ホントにGoogleから来たメールなのか?」。

もちろんメールヘッダも確かめましたが、怪しい箇所は無し。本物のようです。


いったい僕のどの辺を評価頂けたのか良くわかりませんが、Googleからオファー頂けたことは素直に嬉しいと思いました。

ま、面接を受けたら、間違いなく落ちるんですけどね。。。その厳しさはこの辺が参考になります。

http://shiumachi.hatenablog.com/entry/20090122/1232574613

この記事の応募者の方は僕と同じくSREのようですね。


「僕は自分で会社を持ってますし、従業員もいますので」ということで、できるだけ丁寧に辞退のメールを返したのですが。

もしフツーの会社員だったら。。。果たして応募していたかな?

2016-10-24追記

辞退のメールに、リクルーターの方は次の通り返信をくれました。

「もし気が変わって応募したくなったら、いつでもいいから教えてね」

とのことです。ご丁寧にありがとうございます!万一ウチが倒産したらそうします :)

"永続記憶装置からセッションをロード中の例外です"がウザったいのでセッション永続化をOFFにする

Tomcat

Tomcatを停止して再起動すると、こんな例外が発生することがあります。

java.lang.ClassCastException: java.lang.StackTraceElement cannot be cast to java.lang.String
    at java.io.ObjectInputStream.readTypeString(ObjectInputStream.java:1421)
    at java.io.ObjectStreamClass.readNonProxy(ObjectStreamClass.java:719)
    at java.io.ObjectInputStream.readClassDescriptor(ObjectInputStream.java:833)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1609)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
    at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2018)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1942)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1808)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
    at java.util.ArrayList.readObject(ArrayList.java:791)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1909)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1808)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
    at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2018)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1942)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1808)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
    at org.apache.catalina.session.StandardSession.doReadObject(StandardSession.java:1611)
    at org.apache.catalina.session.StandardSession.readObjectData(StandardSession.java:1077)
    at org.apache.catalina.session.StandardManager.doLoad(StandardManager.java:218)
    at org.apache.catalina.session.StandardManager.load(StandardManager.java:162)
    at org.apache.catalina.session.StandardManager.startInternal(StandardManager.java:356)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5196)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1403)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1393)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

Tomcatを停止した時にセッションを永続化し、再起動時に再ロードしているわけなんですがそれに失敗しています。 なにゆえStackTraceElementをStringにキャストしようとしているのかは謎ですが。。。そんなコード書いたっけ?


さて、とりあえず僕はセッションの永続化が不要なのでこの機能をOFFにしたい。 調べてみたところ、context.xmlを書く必要があるようです。

<?xml version="1.0" encoding="UTF-8"?>
<Context>
  <!-- pathnameがnullの場合、永続化は無効 -->
  <Manager className="org.apache.catalina.session.StandardManager" pathname="" />
</Context>

マニュアルによると、

This persistence may be disabled by setting this attribute to an empty string.

だそうですが、あてにならないのでソースを読みます。

以下はorg.apache.catalina.session.StandardManagerのそれっぽい箇所を抜粋したもの。

protected void doUnload() throws IOException {

    if (log.isDebugEnabled())
        log.debug(sm.getString("standardManager.unloading.debug"));

    if (sessions.isEmpty()) {
        log.debug(sm.getString("standardManager.unloading.nosessions"));
        return; // nothing to do
    }

    // Open an output stream to the specified pathname, if any
    File file = file();
    if (file == null) { // nullなら何もせず終了
        return;
    }

    ...
}

...

protected File file() {
    if (pathname == null || pathname.length() == 0) {
        return null; // pathnameがnullか空文字ならnullを返す
    }
    File file = new File(pathname);
    if (!file.isAbsolute()) {
        Context context = getContext();
        ServletContext servletContext = context.getServletContext();
        File tempdir = (File) servletContext.getAttribute(ServletContext.TEMPDIR);
        if (tempdir != null) {
            file = new File(tempdir, pathname);
        }
    }
    return file;
}

見ての通り、pathnameがnullなら永続化をしないことが確認できたのでこれで正しいようです。

なお、context.xmlの配置方法については過去記事を参照して下さい。