Jackson で OffsetDateTime を ISO8601 形式にシリアライズする

JavaTimeModule を使って シリアライズする場合に OffsetDateTime などが変な数字になる場合がある。

@Data
class MyObject {
    @JsonProperty("datetime")
    private OffsetDateTime dateTime;
}

ObjectMapper mapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());

MyObject paramObject = new MyObject();
paramObject.setDateTime(OffsetDateTime.now());

Map<String, String> map = objectMapper.convertValue(paramObject, new TypeReference<Map<String, String>>() {});

map.get("datetime"); // ここがタイムスタンプになる

下記のようにシリアライズされる際に WRITE_DATES_AS_TIMESTAMPS かどうかを確認している箇所がある。

https://github.com/FasterXML/jackson-datatype-jsr310/blob/jackson-datatype-jsr310-2.8.4/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/InstantSerializerBase.java#L81

https://github.com/FasterXML/jackson-datatype-jsr310/blob/jackson-datatype-jsr310-2.8.4/src/main/java/com/fasterxml/jackson/datatype/jsr310/ser/JSR310FormattedSerializerBase.java#L165...L174

https://github.com/FasterXML/jackson-databind/blob/jackson-databind-2.8.4/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java#L162...L184

ObjectMapper がデフォルトで WRITE_DATES_AS_TIMESTAMPS が有効になっているのでこれを解除する。

@Data
class MyObject {
    @JsonProperty("datetime")
    private OffsetDateTime dateTime;
}

ObjectMapper mapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

MyObject paramObject = new MyObject();
paramObject.setDateTime(OffsetDateTime.now());

Map<String, String> map = objectMapper.convertValue(paramObject, new TypeReference<Map<String, String>>() {});

map.get("datetime"); // ISO8601 形式の文字列となる

apache HttpClient の backoff について

この記事の目的

コードリーディング後の理解用メモ

対象

概要

apache http client には 動的にコネクション数を調整できる機能がある。用途としてはリクエスト先のサーバが高負荷になった場合などにコネクション数を減らして負荷を一時的に減らせるようにするような目的で使えそう。

正確な記述などはすっとばして理解の範囲で記載

f:id:nise_nabe:20170103141336p:plain

関連クラスやメンバ

  • org.apache.http.impl.client.HttpClientBuilder
    • backoffManager
    • connectionBackoffStrategy
  • org.apache.http.impl.execchain.BackoffStrategyExec
  • org.apache.http.client.ConnectionBackoffStrategy
  • org.apache.http.client.BackoffManager
  • org.apache.http.impl.client.AIMDBackoffManager
  • org.apache.http.impl.client.DefaultBackoffStrategy

簡単な使い方

もっとも簡単なコード

PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();

CloseableHttpClient client = HttpClientBuilder.create()
                                              .setBackoffManager(new AIMDBackoffManager(manager))
                                              .setConnectionBackoffStrategy(new DefaultBackoffStrategy())
                                              .build()

コネクション数制御。 HttpClientBuilder で設定したコネクション数設定は初期値として使われる。もちろん BackoffManager で調整されていくので運用中の数字とはことなる場合がある。

PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();

AIMDBackoffManager backoffManager = new AIMDBackoffManager(manager);
backoffManager.setPerHostConnectionCap(10);

CloseableHttpClient client = HttpClientBuilder.create()
                                              .setBackoffManager(backoffManager)
                                              .setConnectionBackoffStrategy(new DefaultBackoffStrategy())
                                              .build();

メモ

  • HttpClientBuilder で BackoffStrategyExec を作る
  • BackoffStrategyExec が ConnectionBackoffStrategy や BackoffManager を利用する
  • コネクション数の増え方や減らし方は BackoffManager で実装する
  • Backoff する条件は ConnectionBackoffStrategy で実装する
  • Exec は Strategy の shouldBackoff() を見てどうするかきめる
    • true -> Manager の backoff() を実行
    • false -> Manager の probe() を実行

HttpClient 内の実装について

  • AIMDBackoffManager について

    • AIMD = Additional increase, multiplicative decrease (加算増加乗算減少)
    • デフォルトでは最大2コネクション
      • 初期設定でプールの方の設定を大きくしていても次の更新でこの数字になる
      • 正確にはホストごとの最大コネクション数
    • 増加や減少を抑える時間(クールダウン)はデフォルト 5 秒
      • 最後の増加と減少から 5 秒ごとにプールの最大接続数が 1 ふえる
      • 最後の減少から 5 秒ごとにプールの最大接続数が半分になる
    • BackoffStrategyExec が shouldBackoff() の true/false で呼び出し変えてるだけなので最大で消費してなくても最大値は増えてく
  • DefaultBackoffStrategy について

    • SocketTimeoutException や ConnectException のような例外が発生した場合に backoff すべきと返す
    • http status が 503 の場合に backoff すべきと返す
      • 注意としては ServiceUnavailableRetryExec などを利用したときに backoff せずにリトライが優先される場合がある
        • HttpClientBuilder#build() の実装でそういう順番でチェインしている 参考

参考文献

Additive increase/multiplicative decrease - Wikipedia

apache HttpClient の eviction について

この記事の目的

apache HttpClinent の eviction について コードリーティングした内容のメモ。 コネクションの有効期限(expire?)についても書いているがプールから取り除く処理(eviction?) について調べるときに必要だったので書いている。混同してたらごめんなさい。動作確認してたりしてなかったりするので鵜呑みにしないように。

対象

以下 http components を hc と略していたりする。文言が統一されてないのは殴り書きなので。

内容

概念

抽象クラスや正確な記述などはすっとばして理解の範囲で記載

f:id:nise_nabe:20161229123419p:plain

関連クラスやメンバ

  • org.apache.http.impl.client.HttpClientBuilder
    • connManagerShared
    • evictExpiredConnections
    • evictIdleConnections
    • maxIdleTime, maxIdleTimeUnit
    • connTimeToLive, connTimeToLiveTimeUnit
  • org.apache.http.impl.client.IdleConnectionEvictor
  • org.apache.http.impl.conn.CPool
  • org.apache.http.impl.conn.CPoolEntry
  • org.apache.http.pool.PoolEntry
    • validityDeadline
    • expiry
  • org.apache.http.impl.conn.PoolingHttpClientConnectionManager
    • timeToLive

メモ内容

概念としては以下の二つ

  • プールされたコネクションの有効期間 -> PoolingHttpConnectionManager (あるいは HttpClientBuilder)に設定
    • 作成されてからの有効期間 -> 設定可能
    • 最後に使用されてからの有効期間 -> 基本的に HTTP/1.1 の keepalive
  • プールされたコネクションが生存期間内かどうかチェックする間隔 -> HttpClientBuilder に設定

IdleConnectionEvictor のコンストラクタでスレッドを立てて、一定時間ごとに失効したコネクションあるいはアイドル時間が一定時間を超過したコネクションをクローズする。

  1. 設定する利点は?
  2. 設定しない場合有効期限が切れたコネクションが Pool に残ったままになるので、定期的に掃除できれば取得時に余計な探索をするコストを減らせる。

失効の判定設定は PoolingHttpConnectionManager の timeToLive を設定する。 設定場所は HttpClientBuilder の使い方によって以下の二つのどちらになる。

  • HttpClientBuilder で各種設定をする
  • HttpClientBuilder#setConnectionManager() に PoolingHttpClientConnectionManager を設定する。
    • こちらを使う場合はこちらが優先される

自分で timeToLive を指定せずに生成した場合は -1 となる。これは CPool で無限(正確には Long.MAX_VALUE)として扱われる。

PoolingHttpClientConnectionManager のコンストラクタに timeToLive を指定する。この中で Pool インタフェースの実装である CPool が生成され、CPoolEntry が生成されるたびに生成時刻から expire を計算する。 そのため HttpClientBuilder に自身で生成したものを設定した時、このあたりの挙動が変化する可能性があるので注意。

失効の判定設定は HttpClientBuilder の evictExpiredConnections() または evictIdleConnections() でおこなう。 チェック間隔は evictExpiredConnections() の場合は 10 秒、間隔を自分で設定したい場合は evictIdleConnections() を使う。


スレッドは基本は HttpClient 毎に生成される。正確には HttpClientBuilder#build() されるたびに生成される。 ただし ConnectionManager を共有する場合は行われない。

  1. ConnectionManager を共有するユースケースは?
  2. わからん。 Pooling を共有する場合はアウトゴーイングなコネクションで消費するローカルポートを hc 全体で管理したい場合に使えば良いのかも。この場合コネクションのシャットダウンもちゃんと管理しないとダメそう。 管理しないとっていうのは ConnectionManager が shared になってる場合は CloseableHttpClient を close してもプールされたコネクションはクローズされない。(そりゃ他の CloseableHttpClient で使ってる可能性があるのに勝手にクローズされちゃこまるよねと)なので自分で ConnectionManager の close を使う必要がありそう。

HttpClient では Closeable のリストを管理しており、各 HttpClient で close() 時動作を追加することが出来る。この設計を使って IdleConnetionEvictor で生成されたスレッドを終了させる。

コード(動作確認など)

基本的な使い方

    // 特に制限なしなので繋いだら Connection ヘッダに従う。 Keep-Alive の場合は繋ぎっぱなし。
    CloseableHttpClient client = HttpClientBuilder.create().build();

    // 10 分後に使用不能になる。
    // keepalive があれば min(10分, 生存期間 + keepalive 時間) に使用不能になる。
    // 取得時に Pool からの取得時に使えないものとして取得し直す
    CloseableHttpClient client = HttpClientBuilder.create()
                                         .setConnectionTimeToLive(10, TimeUnit.MINUTES)
                                         .build();

    // 条件は前項と一緒。
    // 10 秒ごとにチェックしてプールから取り除く。取得時のコネクション取得コストを軽減できる可能性がある。
    CloseableHttpClient client = HttpClientBuilder.create()
                                         .setConnectionTimeToLive(10, TimeUnit.MINUTES)
                                         .evictExpiredConnections()
                                         .build();

    // 10 分ごとにチェックする
    // 基本的にリクエスト先で keep alive で時間が設定されている場合に意味がある
    CloseableHttpClient client = HttpClientBuilder.create()
                                         .evictIdleConnections(10, TimeUnit.MINUTES)
                                         .build();

    // 10 分ごとにチェックする(evictExpiredConnections() は意味ない)
    CloseableHttpClient client = HttpClientBuilder.create()
                                         .evictExpiredConnections()
                                         .evictIdleConnections(10, TimeUnit.MINUTES)
                                         .build();

以下動作確認

手っ取り早く Spring Boot で Bean 登録して確認する。

確認:本当にスレッドは生成されるのか?

確認方法: evictExpiredConnections() を呼ぶ

    @Bean
    public HttpClient httpClient() {
        return HttpClientBuilder.create().evictExpiredConnections().build();
    }

jconsole で起動したアプリを選択してスレッドを見て「Connection Evictor」でフィルタする。

f:id:nise_nabe:20161229133137p:plain

確認:Connection Evictor スレッドがリークする可能性がある?

動作確認: HttpClient を close() せずに何度も build() してみる。

    @Bean
    public HttpClient httpClient() {
        HttpClientBuilder.create().evictExpiredConnections().build();
        HttpClientBuilder.create().evictExpiredConnections().build();
        HttpClientBuilder.create().evictExpiredConnections().build();
        return null;
    }

スレッド起動したままだった。

f:id:nise_nabe:20161229133243p:plain

ちゃんと close() するとスレッドはなくなっている。

    @Bean
    public HttpClient httpClient() throws IOException {
        HttpClientBuilder.create().evictExpiredConnections().build().close();
        HttpClientBuilder.create().evictExpiredConnections().build().close();
        HttpClientBuilder.create().evictExpiredConnections().build().close();
        return null;
    }

f:id:nise_nabe:20161229160323p:plain

ClientExecChain あたりの挙動メモ

実際に処理する順番

  1. MainExcec
  2. decorateMainExec()
  3. ProtocolExec
  4. decorateProtocolExec()
  5. RetryExec
  6. RedirectExec
  7. ServiceUnavailableRetryExec
  8. BackoffStrategyExec

MainExec

たぶん実際にリクエストを行なう部分

decorateMainExec()

HttpClientBuilder を継承したクラスで実装する部分っぽい。

実装例としては httpcomponent-cache の CachingHttpClientBuilder がある。

ProtocolExec

正直よくわからん

decorateProtocolExec()

HttpClientBuilder を継承したクラスで実装する部分っぽい。 が、ProtocolExec がわからんので正直よくわからん。

RetryExec

何らかの IOException レベルの http protocol が返ってこないような例外を検知した後 再度リクエストをし直す。

リトライ条件は HttpRequestRetryHandler を介して設定する。

DefaultHttpRequestRetryHandler では下記の例外の場合はリトライしない。

  • InterruptedIOException.class
  • UnknownHostException.class
  • ConnectException.class
  • SSLException.class

RedirectExec

リクエスト内でリダイレクトが発生する場合にリダイレクト先に変更してまたリクエストし直す部分っぽい。

ServiceUnavailableRetryExec

HTTP ステータスが返ってきたあとにリトライする場合の処理を記述するらしい。

http://stackoverflow.com/questions/24220324/httpclient-what-is-the-difference-between-serviceunavailableretrystrategy-and-h

BackoffStrategyExec

コネクション数を動的に制御する場合にコネクション数を減らさなければならない条件を表す部分っぽい。

コネクション数を動的に制御する部分は BackoffManager が行っている。 HttpClient がデフォルトで実装してあるのは AIDM (Additional increase, multiplicative decrease) というアルゴリズムであり、コネクションは加算増加乗算減少を行なうらしい。

decrease 条件は ConnectionBackoffStrategy で記述して 設定する。 デフォルトの DefaultBackoffStrategy では 503 Servie Unavailable になった場合にコネクション数が減少する挙動をする。

Competitive Programming (その2) Advent Calendar 2016 6日目

Competitive Programming (その2) Advent Calendar 2016 - Adventar

Competitive Programming (その2) Advent Calendar 2016 6日目の記事です。

万年灰色コーダーの nise_nabe です。

今回は思い出ポエムを書きます。ちょっとアルゴリズム以外の競プロ役立ち事例のような何かも入っています。(万年灰色コーダの書く記事なので、レッドコーダーや div1 などの上位陣向けではないということはわかっていただけるかと思います。)

おまえはなんなのか

競技プログラミング歴 2009年 〜 らしいです。 最初に出たコンテストは Topcoder SRM 450 のようです。

Topcoder SRM min 517 max 1291

f:id:nise_nabe:20161205224343p:plain

Codeforces min 751 max 1657

f:id:nise_nabe:20161205224346p:plain

min がひどいですね。実際当時は堪えていましたが結局作り直したところでそういう実力なら同じ結果になるだろうと同じアカウントで続けています。某レッドコーダーの方には自分の SRM のグラフをみて「数だけはやってるね」という評価を頂いております(又聞き)。

まあ見たとおりまったく良い成績でないです。高い所がまあ時間が合った時期です。時間があるので問題を解けます。掛ける時間が多くなるとレートが上がります。時間がなくなると問題が解けません。掛ける時間が少なくなるとレートが下がります。

ICPC には出たことがありません。というより競技プログラミングを初めた時点ではそういったイベントに参加するにはいろいろと遅すぎたようです。 tomerun さんとはじめて会ったときには社会人になってから初めたことについて伺い、興味深く聞いた覚えがあります。

初めた当時の微笑ましい姿

自分の競技プログラミングは基本的に一人でした。複数人で競技プログラミングをやるという話はよくみますが自分にとってはよくわからない世界という感じがします。周りの人を誘ってみましたがまあ興味がなかったり長続きしなかったりという感じですね。 twitter をやっていた理由も、 当時 twitter 上にたくさん競技プログラマがいたからです。彼らのリプライを見ながら、自分もこういう話ができるようになりたいと思ったものです。

昔話、あるいは役立ち事例。

昔話です。あるいはどのようにして役に立てるかという話ではなくいい成績を持たない競技プログラマでもそれを通じて得たなにかみたいなものです。

初めた当時は貪欲に参加できるものに参加していたので下記のようなサイトに参加していました。(よくみると tanakh さんや nodchip さん、 tsukuno さんがいますね)

http://www.itmedia.co.jp/news/articles/1003/31/news004.html

まあこれ単に なんか文字列が与えられて Excel のセルの範囲内かどうかを出力するという感じの div2 easy よりちょっと下ぐらいの難易度の問題1問だけが出されたものですね。

nise_nabe は 4 位にいますが、これ単に正規表現とか使って 2, 3 行で書いただけの簡単なやつです。これ自体は本当に大したことないですが、実はこのサイトの参加を通じて、(このサイト直接経由ではないけど)インターンシップに行ったりしています。まあなんでも参加してみるもんですね。最近だといろいろな企業がちゃんとコンテストを開いているようなのでとりあえず参加したらいいと思います。思わぬ繋がりができるかもしれません。

そして就職に関しても基本的には nise_nabe という名前でやっていました。実は他にもいろいろプッシュできる内容を持っていたんですが、試しに競技プログラミング一点で突破しようとしてみました。基本的にはレートが高い低いということは話題にはならず、コードを書いているという点が評価されました。 一部「お金にならないコードを書いて意味があるのか」と言ったような意見ももらいました。当時は何を言っているのかわからなかったですが今はちょっと分かる気もしなくもないです。本当にちょっとだけ。

下記のものは非常に遠回しな言い方をしていますが、まあそういうことです。

また、実は有名な某サイトを使った転職の事例でもあります。これも実際にはランクとかそのとき書いたコードなんかはあまり参考にはされていなかったらしく、基本的には貼っていた ブログの URL に書いてあるコードを見て、コードが書ける人間と判断してくれていた感じらしいです。

まあ以上のように、実は総じて競技プログラミングを通じて仕事にありついている現実があります(実務で競技プログラミングが直接影響してるかどうかとは別の話)。 そのような経験から書くと、別に 上位陣でなくても競技プログラミングをやっていることは評価されることはされます。ただし競技プログラマだからというよりコードをかいているからという点でです。なので、書いたコードはやったらとりあえずブログに貼っとけばいいです。いつかプログラミング関係の仕事をしようとしたときに自分が書いたコードが有ることがアドバンテージになることがあります。上位のコードかどうかは関係なく、コードかいたものが見える位置にあるというのが評価対象になると思います。まあ、逆に言うと自分ぐらいのレベルの競技プログラマならばコードが書けるかどうかという点ぐらいしか評価できないのかもしれません。書ける人は先日の chokudai さんの記事 を参考にしましょう。

あとは、レートが低くてもイベントにはいろいろ参加できますね。UTPC の懇親会 とか 診断人さんの Topcoder 飲み会とか。主催者側がどう思ってるかはわからないですが、とにかく行っても大丈夫です。行きましょう。

そしてこれから

自分はここ数年はプログラミングコンテスト関連の活動は殆どできていません。これは競技プログラミングが原因ではなく心身の不調により気力がなくなったことが影響しています。今はだいぶ普通です。ただし優先順位がいろいろと変わったためやはり手をつけられてはいないです。なのでやるかもしれないしやらないかもしれません。まあ、それとは関係なく下記の事はずっと考えています。

願わくば老後の趣味として競技プログラミングの楽しみが残っていますように。

以上です。

debian ベースの ディストリ上で openvpn が起動しない場合

ubuntudebianopenvpn をインストールしたときに service restart などが全く効かなかった時の話。

バージョン確認

$ docker run -i -t ubuntu
# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.1 LTS"
$ docker run -i -t debian
# cat /etc/debian_version
8.6

起動してみる

# service openvpn start
[ ok ] Starting virtual private network daemon:.

起動したように見える…が、実際には起動してない。

/etc/systemd/system/multi-user.target.wants/openvpn.service を見ると下記のようになっている。

# This service is actually a systemd target,
# but we are using a service since targets cannot be reloaded.

[Unit]
Description=OpenVPN service
After=network.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/true
ExecReload=/bin/true
WorkingDirectory=/etc/openvpn

[Install]
WantedBy=multi-user.target

ExecStart などが 起動につかうコマンドになるはずだが /bin/true になっている…。成功したように見えるのはこのコマンドのせいのようす。とにかく起動したいので下記のように openvpn を直接起動するように変更してみる。

ExecStart=/usr/sbin/openvpn --daemon ovpn-server --status /run/openvpn/server.status 10 --cd /etc/openvpn --config /etc/openvpn/server.conf
$ sudo systemctl daemon-reload
$ sudo systemctl start openvpn

一応これで起動するようになった…はず。フラグは不明だが systemd のファイルがいつのまにか巻き戻っていたり、 /usr/sbin/openvpn が起動しなくなったりするのでよくわからない。

Docker for Mac 起動中には Android Emulator (HAXM) が動かない

下記のようなエラーが出た場合で virtualbox そのものを使っている覚えがない場合で Docker for Mac を使っている場合は Android Emulatror の HAXM ありの場合に起動しない可能性がある。

Hax is enabled
Hax ram_size 0x40000000
HAX is working and emulator runs in fast virt mode.
emulator: Listening for console connections on port: 5554
emulator: Serial number of this emulator (for ADB): emulator-5554
Failed to sync vcpu reg
Failed to sync vcpu reg
Failed to sync vcpu reg
emulator: ERROR: Unfortunately, there's an incompatibility between HAXM hypervisor and VirtualBox 4.3.30+ which doesn't allow multiple hypervisors to co-exist.  It is being actively worked on; you can find out more about the issue at http://b.android.com/197915 (Android) and https://www.virtualbox.org/ticket/14294 (VirtualBox)
Internal error: initial hax sync failed

https://forums.docker.com/t/cant-using-docker-for-mac-with-android-emulator-haxm/8939

対処方法は Docker for Mac を停止させるぐらいしか思いつかない。