tokuhirom's blog

memcached の conn_yields について

memcached の conn_yields が上がっていた場合、conn_yield がでなくなるまで起動時オプションの -R を上げろって主張している人がいます。

実際問題、結局のところどういう主張なのかわかりづらくて、調べた。

コードを検索すると、1つのコネクションでコマンドを発行しまくっている場合にここに到達するようだ。 https://github.com/memcached/memcached/blob/d9dfbe0e2613b9c20cb3c4fdd3c55d1bf3a8c8bd/memcached.c#L4667

「1つのクライアントがリクエストを投げすぎている場合に、スレッドを専有してしまってそれにともなって memcached にアクセスしているクライアント全体がパフォーマンス劣化するという問題に対応する」ために導入されているようだ。

1つのコネクションでコマンドを投げまくっていると、conn_yields が発生する。 eventsperrequest(-R で渡してる値) が低すぎると、バッチリクエストの最中に yield 発生しまくって非効率なので、conn_yields が上がっている場合には適宜増やした方がいい。

ただし、上げすぎると、安全弁としての役割を果たさなく鳴るので、すごい勢いで memcached を呼びまくるクライアントが一台いただけで、全体的なパフォーマンスが悪化するかも。という気もする。


Created: 2017-02-03 05:27:51 +0000
Updated: 2017-02-03 05:27:51 +0000

cursor.executemany(operation, seq_of_params) の挙動

python の cursor.executemany(operation, seq_of_params) について、

data = [
  ('Jane', date(2005, 2, 12)),
  ('Joe', date(2006, 5, 23)),
  ('John', date(2010, 10, 3)),
]
stmt = "INSERT INTO employees (first_name, hire_date) VALUES (%s, %s)"
cursor.executemany(stmt, data)

みたいなケースでは、いい感じに

INSERT INTO employees (first_name, hire_date)
VALUES ('Jane', '2005-02-12'), ('Joe', '2006-05-23'), ('John', '2010-10-03')

のようなクエリが実行される。

この機構は以下のような regexp にマッチした場合にのみ動作する、とのこと。

restr = r"""
    \s
    values
    \s*
    (
        \(
            [^()']*
            (?:
                (?:
                        (?:\(
                            # ( - editor hightlighting helper
                            .*
                        \))
                    |
                        '
                            [^\\']*
                            (?:\\.[^\\']*)*
                        '
                )
                [^()']*
            )*
        \)
    )
"""

https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-executemany.html

Created: 2017-01-30 14:01:29 +0000
Updated: 2017-01-30 14:01:29 +0000

vagrant を centos linux で動かしたい

http://qiita.com/Itomaki/items/9a6a314a853cdcd00f80

↑のエントリどおりに動かしたら動いた。

Created: 2017-01-24 05:17:00 +0000
Updated: 2017-01-24 05:17:00 +0000

MySQLでタイムゾーンがどう設定されているか確認したい

mysql> select @@system_time_zone, @@session.time_zone;
+--------------------+---------------------+
| @@system_time_zone | @@session.time_zone |
+--------------------+---------------------+
| UTC                | SYSTEM              |
+--------------------+---------------------+
1 row in set (0.00 sec)

で。

Created: 2017-01-07 02:52:44 +0000
Updated: 2017-01-07 02:52:44 +0000

/dev/random のエントロピーが足りないときは Haveged を使う

/dev/random を利用した乱数生成器を利用する場合、VPS などの場合にはエントロピーが足りなくて stuck する場合があります。

たとえば以下のような感じ。

"localhost-startStop-1" #10 daemon prio=5 os_prio=0 tid=0x00007f63e8001800 nid=0x2697 runnable [0x00007f640251f000]
   java.lang.Thread.State: RUNNABLE
    at java.io.FileInputStream.readBytes(Native Method)
    at java.io.FileInputStream.read(FileInputStream.java:255)
    at sun.security.provider.SeedGenerator$URLSeedGenerator.getSeedBytes(SeedGenerator.java:539)
    at sun.security.provider.SeedGenerator.generateSeed(SeedGenerator.java:144)
    at sun.security.provider.SecureRandom$SeederHolder.<clinit>(SecureRandom.java:203)
    at sun.security.provider.SecureRandom.engineNextBytes(SecureRandom.java:221)
    - locked <0x00000000f8368610> (a sun.security.provider.SecureRandom)
    at java.security.SecureRandom.nextBytes(SecureRandom.java:468)
    - locked <0x00000000f8369080> (a java.security.SecureRandom)
    at java.security.SecureRandom.next(SecureRandom.java:491)
    at java.util.Random.nextInt(Random.java:329)
    at org.apache.catalina.util.SessionIdGenerator.createSecureRandom(SessionIdGenerator.java:246)
    at org.apache.catalina.util.SessionIdGenerator.getRandomBytes(SessionIdGenerator.java:183)
    at org.apache.catalina.util.SessionIdGenerator.generateSessionId(SessionIdGenerator.java:153)
    at org.apache.catalina.session.ManagerBase.startInternal(ManagerBase.java:573)
    at org.apache.catalina.session.StandardManager.startInternal(StandardManager.java:485)
    - locked <0x00000000f82d88f0> (a org.apache.catalina.session.StandardManager)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    - locked <0x00000000f82d88f0> (a org.apache.catalina.session.StandardManager)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5501)
    - locked <0x00000000fadf5850> (a org.apache.catalina.core.StandardContext)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    - locked <0x00000000fadf5850> (a org.apache.catalina.core.StandardContext)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1575)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1565)
    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)

http://stackoverflow.com/questions/26227344/oracle-java-8-x64-for-linux-and-randomsource

このような場合、haveged を使うのが簡単です。 https://www.digitalocean.com/community/tutorials/how-to-setup-additional-entropy-for-cloud-servers-using-haveged

apt-get install haveged
update-rc.d haveged defaults

とかでOK。簡単。

Created: 2017-01-06 08:56:41 +0000
Updated: 2017-01-06 08:56:41 +0000

Keynote の数式エディタは何が良いのか問題

色々派閥があるようだ。。 とりあえず latexit 入れてみるか。。

Created: 2016-12-12 05:22:55 +0000
Updated: 2016-12-12 05:22:55 +0000

DBD::mysql のメンテナンス体制が変わっていた

レポジトリが https://github.com/perl5-dbi/DBD-mysql ここに変わっていた。

https://github.com/CaptTofu/DBD-mysql/issues/51 この issue は buffer overflow なので、ちょっとまあアレなんだけど2年前から放置されてて困ったなーと思っていたが、なんか新体制で別のレポジトリで解消されていた。

具体的には以下のようなコードで、バッファオーバーフロー発生していた。sprintf でエラーメッセージを生成している部分で、固定バッファ使ってるというわりとよくあるISSUE。 perl -MDBI -e 'DBI->connect("dbi:mysql:dbname=test", "root")->prepare("?")->bind_param(1, "2014-01-01 00:00", 4)'

2016-10-03 Patrick Galbraith, Michiel Beijen, DBI/DBD community (4.037)
* Security release to patch possible buffer overflow in unsafe sprintf with
  variable length. Reported and fixed by Pali Rohár. This vulnerability
  is present in all releases at least back to versions 3.0 of the
  driver, which were released in 2005.
  The CVE identifier for this vulnerability is CVE-2016-1246.

というわけで、4.037 以後にしておくのがオススメ。

Created: 2016-12-12 02:14:44 +0000
Updated: 2016-12-12 02:14:44 +0000

YAPC Hokkaido で「Perl6 と Web 開発と」というタイトルで発表しました #yapcjapan

Created: 2016-12-10 03:03:13 +0000
Updated: 2016-12-10 03:03:13 +0000

RxJava で無理やり MDC を使うライフハック

import java.util.Map;

import org.slf4j.MDC;

import rx.functions.Action0;
import rx.functions.Func1;

/**
 * Propagate MDC contexts between Observables
 */
public class MdcPropagator implements Func1<Action0, Action0> {
    @Override
    public Action0 call(Action0 original) {
        Map<String, String> context = MDC.getCopyOfContextMap();
        return () -> {
            final Map<String, String> originalMdc = MDC.getCopyOfContextMap();

            if (context != null) {
                MDC.setContextMap(context);
            }

            try {
                original.call();
            } finally {
                if (originalMdc != null) {
                    MDC.setContextMap(originalMdc);
                }
            }
        };
    }
}
Created: 2016-11-25 07:37:58 +0000
Updated: 2016-11-25 07:37:58 +0000

Jet Brains Night 行ってきた

Yahoo! Japan さんのオフィスで Jet Brains Night というミートアップイベントがあったので行ってきた。

大きめのセミナールームを2個ぶち抜いた感じで、スクリーンが前面4面とサイド2面あってとにかく広くてきれいな開場でした。

IntelliJ の tips とか一個ぐらい学べればいいなーと思って行ったけど結構いろいろあって良かったです。


JetBrains 製品全般の紹介という感じで良かった。

JS については以下の tips 知らなかった! なるほどなー。

↑これが目からうろこというか、なるほどそういう考え方あるのか、という話でした。IDEA のプラグインとかに設定を寄せると、IDEA 以外を使ってる人との間で設定の共有が難しくなるので、grunt/gulp/eslint 等に設定を寄せておいて File Watchers というプラグインで処理するという話。

http://448.jp/blog/article/1459/

↑これが便利。IDEA のショートカットが windows 用と mac 用のショートカットがいい感じに表示されて、良い。IDEA のデモをやるときには便利。

これは、たしかにいいなぁ。今まで SQL については language injection 使ってたんですが、JSON については気にしてなかった。JSON も今後は language injection 使う人いそうなんで。

↓structual に replace めっちゃ便利っぽい。が、実際使う段になったらやっぱり regexp で置換するんだろうな、という気もする。

↓これもたまに見ると楽しい。

というのもあって、github enterprise との連携できるならわりと有りな選択肢なんじゃないかなーと思ってみたり。実際にたてて触ってみたい。が、触る時間あるかなー。 という感じ。触ってみないとわからないけど一回手元で動かしてみないとさわれないのがネックか。

Java だと Java 言語を理解してよしなに理解してくれるのと IDE との連携が良さそうなので、試してみたいぞ!

Created: 2016-11-24 04:41:36 +0000
Updated: 2016-11-24 04:41:36 +0000

Vert.x で access log を出したい

router.route().handler(LoggerHandler.create(LoggerFormat.DEFAULT));

でいいのだが、ドキュメントを見ていてもわかりづらいね。

Created: 2016-11-15 05:54:11 +0000
Updated: 2016-11-15 05:54:11 +0000

netty で http server

RxNetty 触ってると、素の Netty だとどうなんだっけ? というところも把握しておかないと厳しい面もありますので、一応ひさびさに netty で素の http server を書いてみる。

keep-alive まわりの処理とか chunked の処理とか手で全部書かないといけないからやはり生で使うものではないな、という感じ。

package com.example;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;

import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;

/**
 * もっとも基本的な HTTP server の実装。
 */
@Slf4j
public class HttpServer {
    public static void main(String[] args) throws InterruptedException {
        int port = 3000;

        NioEventLoopGroup group = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(group)
                .channel(NioServerSocketChannel.class)
                .localAddress(new InetSocketAddress(port))
                .handler(new LoggingHandler(LogLevel.DEBUG))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(
                                new HttpServerCodec(),
                                new HttpObjectAggregator(512 * 1024),
                                new HttpServerHandler());
                    }
                });
        try {
            ChannelFuture f = bootstrap.bind().syncUninterruptibly();
            log.info("Listening: {}", f.channel().localAddress());
            f.channel().closeFuture().syncUninterruptibly();
        } finally {
            group.shutdownGracefully().syncUninterruptibly();
        }
    }

    @Slf4j
    public static class HttpServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            FullHttpRequest request = (FullHttpRequest) msg;
            log.info("[{}] {} {} {}",
                    ctx.channel().remoteAddress(),
                    request.protocolVersion(),
                    request.method(),
                    request.uri());
            byte[] content = "Hello".getBytes(StandardCharsets.UTF_8);
            DefaultHttpResponse defaultHttpResponse = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK,
                    Unpooled.buffer()
                            .writeBytes(content),
                    new DefaultHttpHeaders()
                            .add(HttpHeaderNames.CONTENT_TYPE, "text/plain")
                            .add(HttpHeaderNames.CONTENT_LENGTH, content.length),
                    EmptyHttpHeaders.INSTANCE);

            if (!HttpUtil.isKeepAlive(request)) {
                ctx.writeAndFlush(defaultHttpResponse)
                        .addListener(ChannelFutureListener.CLOSE);
            } else {
                defaultHttpResponse.headers()
                        .add(HttpHeaderNames.CONNECTION, KEEP_ALIVE);
                ctx.writeAndFlush(defaultHttpResponse);
            }
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx,
                                    Throwable cause) {
            log.warn("Caught unhandled exception", cause);
            ctx.close();
        }
    }
}

一通り書いてから気づいたけど https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/http/helloworld に実装例が載っているわ。

Created: 2016-11-13 15:08:55 +0000
Updated: 2016-11-13 15:08:55 +0000

RxNetty 使ってるときに access log を取得する方法

RxNetty を利用しているときに、access log を取得しておくと便利です。

RxNetty 自体には access log を取得するフックがないので以下のようなフィルタを用意してみました。 DateTimeFormatter の速度を考慮して、1秒以内の場合にはキャッシュしているところがおしゃれです。

利用方法としては、以下のように acccess log 取得用の ロガーに流すだけです。簡単ですね。

        Logger accessLogLogger = LoggerFactory.getLogger("access_log");
        RequestHandler<ByteBuf, ByteBuf> handler = new RxNettyAccessLogFilter((request, response) -> {
            response.writeString("Hello");
            return response.close();
        }, request -> "-", accessLogLogger::info);
       RxNetty.createHttpServer(8080, handler).startAndWait();
Created: 2016-11-13 03:36:42 +0000
Updated: 2016-11-13 03:36:42 +0000

RxNetty のメトリクス取得について

RxNetty でサーバー運用するにあたっては、メトリクスを取得したくなるとおもいますが、デフォルトでは spectator がサポートされています。 spectator は netflix 製のライブラリですが netflix 以外ではほとんど利用されていない様子。情報が極めて少ないです。

コード例などは見当たらないのですが、以下のように Global の metrics に Dropwizard の MetricRepository を紐付けてやれば良いです。

RxNetty.useMetricListenersFactory(new SpectatorEventsListenerFactory());

MetricRegistry metricRegistry = new MetricRegistry();
MetricsRegistry registry = new MetricsRegistry(Clock.SYSTEM, metricRegistry);
Spectator.globalRegistry().add(registry);

JmxReporter.forRegistry(metricRegistry).build().start();

依存は以下のような感じ

        <dependency>
            <groupId>io.reactivex</groupId>
            <artifactId>rxnetty-spectator</artifactId>
            <version>0.4.19</version>
        </dependency>
        <dependency>
            <groupId>com.netflix.spectator</groupId>
            <artifactId>spectator-reg-metrics3</artifactId>
            <version>0.43.0</version>
        </dependency>
        <dependency>
            <groupId>com.netflix.spectator</groupId>
            <artifactId>spectator-api</artifactId>
            <version>0.43.0</version>
        </dependency>

取得されるデータは以下のような感じです。一応データとして取得はできていますが、ちょっとコレジャナイ感じもありますね。

https://gyazo.com/afee897ec9e9f919f9155d41f439a55b

Created: 2016-11-12 09:54:53 +0000
Updated: 2016-11-12 09:54:53 +0000

RxJava2 と reactor どっちがいいのか問題

なるほど!!!!!!!!

Created: 2016-11-10 07:48:53 +0000
Updated: 2016-11-10 07:48:53 +0000

PROXY protocl なサーバーに対して接続テストしたい

PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535
GET /is/alive HTTP/1.0

とか telnet で話かければつながるかわかるって kazuho さんが言ってた。

Created: 2016-10-31 11:00:35 +0000
Updated: 2016-10-31 11:00:35 +0000

hotspot の NullPointerException 時の表示をわかりやすくできないかなあと思って調べたけど挫折した

解決したいこと

foo.bar(baz.boz()); みたいなケースで、NPE が発生した場合に、foo が null で例外発生したのか baz が null で例外発生したのかわからん

やってみる

bash configure --with-native-debug-symbols=internal --enable-headless-only --with-debug-level=slowdebug  --disable-warnings-as-errors
make

動かしてみると以下のような出力を得られる。

Exception in thread "main" java.lang.NullPointerException
        at NPE.main(NPE.java:4)

これは以下のソースから出力されている。Thread 単位で uncaught exception が発生させられている。

jdk/src/java.base/share/classes/java/lang/ThreadGroup.java の uncaughtException だ。

uncaughtException は jdk/src/java.base/share/classes/java/lang/Thread.java の dispatchUncaughtException から呼ばれているようだ。

これは hotspot/src/share/vm/runtime/thread.cpp の JavaThread::exit から呼ばれている。

ここには hotspot/src/share/vm/prims/jni.cpp の jni_DetachCurrentThread から来ていることがわかる。

更にたどるとjdk/src/java.base/share/native/libjli/java.c のJavaMain がエントリポイントになっていて

/* Invoke main method. */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);


/*
 * The launcher's exit code (in the absence of calls to
 * System.exit) will be non-zero if main threw an exception.
 */
ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;

のあたりで処理が行われる。つまり、psvm 実行された後で、例外が設定されているかどうかを見て、例外がVMに設定されていれば、例外を処理するという形。

問題は、NullPointerException のインスタンスが作成される箇所がどこかという点なので、そこまで追う。

ここで、env は JNIEnv で、これの定義は hotspot/src/share/vm/prims/jni.h にある。

void CallStaticVoidMethodV(jclass cls, jmethodID methodID,
                           va_list args) {
    functions->CallStaticVoidMethodV(this,cls,methodID,args);
}

であって、functions の定義は 

const struct JNINativeInterface_ *functions;

なので、hotspot/src/share/vm/prims/jni.cpp に実装がある。で、それは同じファイルの jniinvokestatic にいく。

hotspot/src/share/vm/runtime/javaCalls.cpp の JavaCalls::call に行くが、ここでまさかの os::osexceptionwrapper に。。

os::osexceptionwrapper は、Win32 対応で必要なだけの存在らしく、実際にはこれだけのことです。linux だと素でただのラッパです。

void
os::os_exception_wrapper(java_call_t f, JavaValue* value, const methodHandle& method,
                         JavaCallArguments* args, Thread* thread) {
  f(value, method, args, thread);
}

つまり、JavaCalls::call は実際には以下と同じ。

call_helper(result, method, args, THREAD);

call_helper の中身は、色々やっているが実際にコールしているのは以下の部分。

  // do call
  { JavaCallWrapper link(method, receiver, result, CHECK);
    { HandleMark hm(thread);  // HandleMark used by HandleMarkCleaner


      StubRoutines::call_stub()(
        (address)&link,
        // (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
        result_val_address,          // see NOTE above (compiler problem)
        result_type,
        method(),
        entry_point,
        args->parameters(),
        args->size_of_parameters(),
        CHECK
      );


      result = link.result();  // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
      // Preserve oop return value across possible gc points
      if (oop_result_flag) {
        thread->set_vm_result((oop) result->get_jobject());
      }
    }
  } // Exit JavaCallWrapper (can block - potential return oop must be preserved)

で、StubRoutines::call_stub が実際にメソッドを呼んでいる部分なわけですが、 その実装は hotspot/src/share/vm/runtime/stubRoutines.hpp にあります。

こいつは、マクロを使ったハックで pointer から function pointer に warnings 無しでキャストするということをやっていたりして、ちょっと読みにくいですが、実際には StubRoutines::callstub_entry をコールしているだけということになります。

で、 StubRoutines::callstub_entry はマシン語生成していて大変読みにくいということがわかった。

で、色々調べていくと TemplateTable::resolvecacheand_index でメソッド呼び出し分コードは JIT されていることがわかった。

で、これは

InterpreterRuntime::resolvefromcache を呼んでいる。

IRT_ENTRY(void, InterpreterRuntime::resolve_from_cache(JavaThread* thread, Bytecodes::Code bytecode)) {
  switch (bytecode) {
  case Bytecodes::_getstatic:
  case Bytecodes::_putstatic:
  case Bytecodes::_getfield:
  case Bytecodes::_putfield:
    resolve_get_put(thread, bytecode);
    break;
  case Bytecodes::_invokevirtual:
  case Bytecodes::_invokespecial:
  case Bytecodes::_invokestatic:
  case Bytecodes::_invokeinterface:
    resolve_invoke(thread, bytecode);
    break;
  case Bytecodes::_invokehandle:
    resolve_invokehandle(thread);
    break;
  case Bytecodes::_invokedynamic:
    resolve_invokedynamic(thread);
    break;
  default:
    fatal("unexpected bytecode: %s", Bytecodes::name(bytecode));
    break;
  }
}
IRT_END

InterpreterRuntime::resolve_invoke では、LinkResolver::resolve_invoke を呼んでいる

void InterpreterRuntime::resolve_invoke(JavaThread* thread, Bytecodes::Code bytecode) {
  Thread* THREAD = thread;
  // extract receiver from the outgoing argument list if necessary
  Handle receiver(thread, NULL);
  if (bytecode == Bytecodes::_invokevirtual || bytecode == Bytecodes::_invokeinterface) {
    ResourceMark rm(thread);
    methodHandle m (thread, method(thread));
    Bytecode_invoke call(m, bci(thread));
    Symbol* signature = call.signature();
    receiver = Handle(thread,
                  thread->last_frame().interpreter_callee_receiver(signature));
    assert(Universe::heap()->is_in_reserved_or_null(receiver()),
           "sanity check");
    assert(receiver.is_null() ||
           !Universe::heap()->is_in_reserved(receiver->klass()),
           "sanity check");
  }

  // resolve method
  CallInfo info;
  constantPoolHandle pool(thread, method(thread)->constants());

  {
    JvmtiHideSingleStepping jhss(thread);
    LinkResolver::resolve_invoke(info, receiver, pool,
                                 get_index_u2_cpcache(thread, bytecode), bytecode,
                                 CHECK);

LinkResolver::resolve_invoke からは resolve_invokevirtual に遷移している。

void LinkResolver::resolve_invoke(CallInfo& result, Handle recv, const constantPoolHandle& pool, int index, Bytecodes::Code byte, TRAPS) {
  switch (byte) {
    case Bytecodes::_invokestatic   : resolve_invokestatic   (result,       pool, index, CHECK); break;
    case Bytecodes::_invokespecial  : resolve_invokespecial  (result,       pool, index, CHECK); break;
    case Bytecodes::_invokevirtual  : resolve_invokevirtual  (result, recv, pool, index, CHECK); break;
    case Bytecodes::_invokehandle   : resolve_invokehandle   (result,       pool, index, CHECK); break;
    case Bytecodes::_invokedynamic  : resolve_invokedynamic  (result,       pool, index, CHECK); break;
    case Bytecodes::_invokeinterface: resolve_invokeinterface(result, recv, pool, index, CHECK); break;
  }
  return;
}

LinkResolver::resolve_invokevirtual から resolvevirtualcall へ。

void LinkResolver::resolve_invokevirtual(CallInfo& result, Handle recv,
                                          const constantPoolHandle& pool, int index,
                                          TRAPS) {

  LinkInfo link_info(pool, index, CHECK);
  KlassHandle recvrKlass (THREAD, recv.is_null() ? (Klass*)NULL : recv->klass());
  resolve_virtual_call(result, recv, recvrKlass, link_info, /*check_null_or_abstract*/true, CHECK);
}

LinkResolver::resolvevirtualcall は runtimeresolvevirtual_method を呼ぶ

void LinkResolver::resolve_virtual_call(CallInfo& result, Handle recv, KlassHandle receiver_klass,
                                        const LinkInfo& link_info,
                                        bool check_null_and_abstract, TRAPS) {
  methodHandle resolved_method = linktime_resolve_virtual_method(link_info, CHECK);
  runtime_resolve_virtual_method(result, resolved_method,
                                 link_info.resolved_klass(),
                                 recv, receiver_klass,
                                 check_null_and_abstract, CHECK);
}

LinkResolver::runtimeresolvevirtual_method に至って、ついに NullPointerException を投げている!!

void LinkResolver::runtime_resolve_virtual_method(CallInfo& result,
                                                  const methodHandle& resolved_method,
                                                  KlassHandle resolved_klass,
                                                  Handle recv,
                                                  KlassHandle recv_klass,
                                                  bool check_null_and_abstract,
                                                  TRAPS) {

  // setup default return values
  int vtable_index = Method::invalid_vtable_index;
  methodHandle selected_method;

  assert(recv.is_null() || recv->is_oop(), "receiver is not an oop");

  // runtime method resolution
  if (check_null_and_abstract && recv.is_null()) { // check if receiver exists
    THROW(vmSymbols::java_lang_NullPointerException()); ←←←← ここ!!
  }

で、この THROW の実体は以下の通り

#define THROW(name)                                 \
  { Exceptions::_throw_msg(THREAD_AND_LOCATION, name, NULL); return;  }

THREADANDLOCATION は 以下

#define THREAD_AND_LOCATION                      THREAD, __FILE__, __LINE__

THREAD は以下。 要するに、各メソッドの引数に TRAPS をつけておいて、THREAD 変数で参照するってだけ。

// The THREAD & TRAPS macros facilitate the declaration of functions that throw exceptions.
// Convention: Use the TRAPS macro as the last argument of such a function; e.g.:
//
// int this_function_may_trap(int x, float y, TRAPS)

#define THREAD __the_thread__
#define TRAPS  Thread* THREAD

であって、Exceptions::throwmsg は、以下の通り

void Exceptions::_throw_msg(Thread* thread, const char* file, int line, Symbol* name, const char* message,
                            Handle h_loader, Handle h_protection_domain) {
  // Check for special boot-strapping/vm-thread handling
  if (special_exception(thread, file, line, name, message)) return;
  // Create and throw exception
  Handle h_cause(thread, NULL);
  Handle h_exception = new_exception(thread, name, message, h_cause, h_loader, h_protection_domain);
  _throw(thread, file, line, h_exception, message);
}

ここまで調べた結果

なかなか目的を達成するのは難しそうなのでまた今度。

Created: 2016-10-09 13:10:22 +0000
Updated: 2016-10-09 13:10:22 +0000

openjdk をコンパイルして gdb で実行すると sigsegv しちゃうんだけど、って時の対処方法

Java で開発していると openjdk を自分でコンパイルして gdb でステップ実行して内部動作をトレースしたいなーという時があると思います。

そんな時には以下のように、openjdk の configure 時に debug option つけて symbol つけましょう、みたいな情報は調べればすぐ出てくるんです。実際以下のようにオプションつければ OK

bash ./configure --with-debug-level=slowdebug --with-target-bits=64 --disable-zip-debug-info
make all

そして、いざ実行してみると以下のように SIGSEGV だという宣告が!!

(gdb) r
Starting program: /home/tokuhirom/jdk9/./build/linux-x86_64-normal-server-slowdebug/jdk/bin/java NPE
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7ffff7fd3700 (LWP 19431)]

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff7fd3700 (LWP 19431)]
0x00007fffe1000513 in ?? ()

どうしたらいいんだ、とおもって色々情報を探していたんですがまったく情報が見つからず、printf デバッグしていたんですが、ふとした拍子に情報を見つけました。

http://stackoverflow.com/questions/35611119/how-to-debug-openjdk-using-gdb

An information worth mentioning that finally when you start the debugging session with gdb and you run the program you may see a SEGFAULT ignore it and continue till gdb stops at your breakpoint.

ええええええ。SIGSEGV って出てもその後そのまま c してけばいつかブレークポイントにたどり着くので気にしなくていいんだってさ!

めっちゃ時間を無駄にしたわー。

Created: 2016-10-08 12:26:20 +0000
Updated: 2016-10-08 12:26:20 +0000

activemq を動かしてみる

brew install apache-activemq
activemq start

で起動完了。

以下のようにして実行開始。

for i in {1..1000}; curl -u admin:admin -d 'body="Hello World"' "http://localhost:8161/api/message/test$i?type=queue"

以下のようにして消費させる。

for i in {1..1000}; curl -u admin:admin "http://localhost:8161/api/message/test$i?type=queue"

しかし、1000 個ぶっこもうとすると途中で止まるので謎い。 (rabbitmq 使うので別にいいんだけど)

Created: 2016-10-05 22:19:38 +0000
Updated: 2016-10-05 22:19:38 +0000

orelang を Java で実装してみた

http://qiita.com/shuetsu@github/items/ac21e597265d6bb906dc

わりとよくある JSON ベースの lisp っぽいインタープリタの実装ですが、コードを見ていてもよくわからなかったので自分で実装しなおしてみました。

package com.example;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.ObjectMapper;

public class OreLang {
    HashMap<String, Object> vars = new HashMap<>();

    private Object eval(Object o) {
        if (o instanceof List) {
            System.out.println("Running: " + o);
            Object result = doRun((List<?>) o);
            System.out.println("Result: " + o + " : " + result + " : " + vars);
            return result;
        } else {
            return o;
        }
    }

    private Object doRun(List<?> list) {
        String op = (String) list.get(0);
        List<?> args = list.subList(1, list.size());
        switch (op) {
            case "set":
                vars.put((String) args.get(0), eval(args.get(1)));
                return null;
            case "get":
                return vars.get(args.get(0));
            case "+":
                return (Integer) eval(args.get(0)) + (Integer) eval(args.get(1));
            case "=":
                return eval(args.get(0)) == eval(args.get(1));
            case "until":
                while (!(Boolean) eval(args.get(0))) {
                    eval(args.get(1));
                }
                return null;
            case "step":
                List<Object> collect = args.stream()
                                           .map(this::eval)
                                           .collect(Collectors.toList());
                return collect.get(collect.size() - 1);
        }
        throw new RuntimeException("Unknown operation: " + op);
    }

    public static void main(String[] args) throws IOException {
        String source = "[\"step\",\n"
                        + "  [\"set\", \"i\", 10],\n"
                        + "  [\"set\", \"sum\", 0],\n"
                        + "  [\"until\", [\"=\", [\"get\", \"i\"], 0], [\n"
                        + "    \"step\",\n"
                        + "    [\"set\", \"sum\", [\"+\", [\"get\", \"sum\"], [\"get\", \"i\"]]],\n"
                        + "    [\"set\", \"i\", [\"+\", [\"get\", \"i\"], -1]]\n"
                        + "  ]],\n"
                        + "  [\"get\", \"sum\"]\n"
                        + "]\n";
        ObjectMapper objectMapper = new ObjectMapper();
        Object script = objectMapper.readValue(source, Object.class);
        Object result = new OreLang().eval(script);
        System.out.println("RESULT: " + result);
    }
}
Created: 2016-09-30 01:43:58 +0000
Updated: 2016-09-30 01:43:58 +0000

@RequestMapping の produces を指定するとどうなるか

@RequestMappingproduces = MediaType.APPLICATION_JSON_UTF8_VALUE を指定したときの効果について。指定すべきかどうなのか

結論

  • つけなくても実害はない
  • XML シリアライザが依存に入っている場合、Accept: application/xml のようにヘッダが入っている場合、XML でレスポンスが返却される。
  • ほとんどの場合は produces 指定するかどうか指定するよりも HttpMessageConverter を明示するようにしたほうが良い。
  • 公開される API の場合には HttpMessageConverter を明示的に JSON 以外出力されないように設定しておくほうが良いかもしれない
    • Accept:application/xml を利用していてそれに依存する利用者がいると困る、という程度だが……

produces を指定した場合としない場合で実際どう挙動が変わるのか

以下のようなコントローラを実装する。


    @ResponseBody
    @GetMapping(value = "/json", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public Map<String, String> json() {
        return ImmutableMap.of("a", "b");
    }

    @ResponseBody
    @GetMapping(value = "/musitei")
    public Map<String, String> musitei() {
        return ImmutableMap.of("c", "d");
    }

produces 指定がある場合、許可されていない Acccept ヘッダでリクエストすると 406 Not Acceptable が返却される。

$ curl -v http://localhost:8080/json
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /json HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Tue, 27 Sep 2016 21:47:24 GMT
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< 
* Connection #0 to host localhost left intact
{"a":"b"}

$ curl -v -H 'Accept: application/xml' http://localhost:8080/json
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /json HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: application/xml
> 
< HTTP/1.1 406 Not Acceptable
< Date: Tue, 27 Sep 2016 21:47:02 GMT
< Content-Type: application/xml;charset=UTF-8
< Transfer-Encoding: chunked
< 
* Connection #0 to host localhost left intact
<Map><timestamp>1475012826311</timestamp><status>406</status><error>Not Acceptable</error><exception>org.springframework.web.HttpMediaTypeNotAcceptableException</exception><message>Not Acceptable</message><path>/json</path></Map>

無指定な場合、Accept: application/xml を指定すると、XML が返却される。

$ curl -v http://localhost:8080/musitei
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /musitei HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Tue, 27 Sep 2016 21:48:51 GMT
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< 
* Connection #0 to host localhost left intact
{"c":"d"
$ curl -v -H 'Accept: application/xml' http://localhost:8080/musitei
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /musitei HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: application/xml
> 
< HTTP/1.1 200 OK
< Date: Tue, 27 Sep 2016 21:48:30 GMT
< Content-Type: application/xml;charset=UTF-8
< Transfer-Encoding: chunked
< 
* Connection #0 to host localhost left intact
<Map><c>d</c></Map>

どのように判別されているのか

以下のあたりで、Accept ヘッダで許容可能と指定されている content-type とサーバーの設定で利用可能な HttpMessageConverter の突き合わせが行われ、利用可能ならシリアライザが使われる。

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse) とかにデバッグポイントしかけて見てみると、そのへんの挙動がわかる。

        HttpServletRequest request = inputMessage.getServletRequest();
        List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
        List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);

        if (outputValue != null && producibleMediaTypes.isEmpty()) {
            throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
        }

        Set<MediaType> compatibleMediaTypes = new LinkedHashSet<>();
        for (MediaType requestedType : requestedMediaTypes) {
            for (MediaType producibleType : producibleMediaTypes) {
                if (requestedType.isCompatibleWith(producibleType)) {
                    compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
                }
            }
        }
        if (compatibleMediaTypes.isEmpty()) {
            if (outputValue != null) {
                throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
            }
            return;
        }

利用可能な HttpMessageConverters はどこで決定されているのか

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addDefaultHttpMessageConverters です。

    protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
        stringConverter.setWriteAcceptCharset(false);

        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(stringConverter);
        messageConverters.add(new ResourceHttpMessageConverter());
        messageConverters.add(new SourceHttpMessageConverter<Source>());
        messageConverters.add(new AllEncompassingFormHttpMessageConverter());

        if (romePresent) {
            messageConverters.add(new AtomFeedHttpMessageConverter());
            messageConverters.add(new RssChannelHttpMessageConverter());
        }

        if (jackson2XmlPresent) {
            ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build();
            messageConverters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper));
        }
        else if (jaxb2Present) {
            messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        }

        if (jackson2Present) {
            ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build();
            messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));
        }
        else if (gsonPresent) {
            messageConverters.add(new GsonHttpMessageConverter());
        }
    }

AtomFeedHttpMessageConverter と RssChannelHttpMessageConverter については無視して良い。以下のように、特定のクラスのインスタンスの場合のみ動作するからだ。

public class AtomFeedHttpMessageConverter extends AbstractWireFeedHttpMessageConverter<Feed> {

    public AtomFeedHttpMessageConverter() {
        super(new MediaType("application", "atom+xml"));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return Feed.class.isAssignableFrom(clazz);
    }

}

そういうわけで、問題は XML シリアライザが依存に入ってきたケースに挙動が変わるという点のみが問題となる。

produces を指定すべきか

そもそも produces を指定すべきかという点についてだが、atom, xml, rss 等を明示的に返却したい場合については指定したほうが良いかもしれない(別にしなくてもいい気もする)。 String などの raw type を JSON で返したい場合、StringHttpMessageConverter が優先されてしまうため明示的に指定しないといけない。

しかし大枠でいうと、JSON で返す API 全てに produces 属性を指定するとコードがゴチャゴチャするので HttpMessageConverter から除外する、などの対応を取ったほうがいいと思う。

Created: 2016-09-27 21:37:04 +0000
Updated: 2016-09-27 21:37:04 +0000
Next page