ましめも

技術系メモ

MacでGUIを使って簡単にSANs対応のオレオレ証明書(マルチドメイン証明書)を発行する

以下の記事の続き。 mashi.hatenablog.com

オレオレ認証局を使って、オレオレ証明書を発行する。

CSRを使って証明書を作成する場合

本物の証明書を作る手順同様、CSRを作成してからオレオレ認証局で証明書を作成するパターン。CSRを作成する方法は世の中に溢れてるので適当にググってほしい。

1) キーチェーンを開きメニューからアシスタントを開く

f:id:mashijp:20190414212254p:plain

CSRをドラッグドロップする画面が出てくるのでCSRを入れる。

2) 発行認証局

f:id:mashijp:20190414212745p:plain

この要求に対するデフォルトを無効化にチェックを入れる

3) 証明書情報

f:id:mashijp:20190414212846p:plain

4) 【重要】 証明書情報(2)

f:id:mashijp:20190414172438p:plain

  • 【重要】名前(通称) は Common Name (CN) に相当する。配信したいFQDNを入力する
    • SANs領域はあとで設定できるので、複数のFQDNで配信したい場合、メインとなるFQDNを設定しておく
    • 近代のブラウザだとCNは無視されるものもあるらしい (SANs 領域しかみない)

5) 鍵用途拡張領域 【スキップ可能】

f:id:mashijp:20190414172838p:plain そのまま

6) 拡張鍵用途拡張領域

f:id:mashijp:20190414172912p:plain

ごくまれに、ここの項目が異なっているとSSL証明書として使えない(選択できない)ロードバランサ等が存在するため注意。

  • SSLクライアント認証、SSLサーバ認証 にチェックを入れる

7) 基本成約拡張領域 【スキップ可能】

f:id:mashijp:20190414173002p:plain

  • 基本制約拡張領域を含める にチェックを入れる

8) 【重要】サブジェクト代替名拡張領域

f:id:mashijp:20190414173106p:plain

  • dNSName に配信したいFQDNを半角スペース区切りですべて登録する
    • Common Nameも含めてすべて指定する
    • 近代のブラウザの中には Common Name を無視しこの SANs 領域しかみないものもあるらしい

9) 作成完了!

f:id:mashijp:20190414173654p:plain SANs がそれっぽくなっていることを確認

10) 証明書のエクスポート

発行した証明書を右クリックし、エクスポートする。

秘密鍵と証明書を同時に作成する場合

1) キーチェーンを開きメニューからアシスタントを開く

f:id:mashijp:20190414172148p:plain

2) 証明書を作成

f:id:mashijp:20190414172239p:plain

  • 固有名のタイプ を リーフに
  • 証明書のタイプ を SSLサーバに
  • 「デフォルトを無効化」 にチェックを入れる

3) 証明書情報(1)

f:id:mashijp:20190414172358p:plain 好きに設定

4) 【重要】 証明書情報(2)

f:id:mashijp:20190414172438p:plain

  • 【重要】名前(通称) は Common Name (CN) に相当する。配信したいFQDNを入力する
    • SANs領域はあとで設定できるので、複数のFQDNで配信したい場合、メインとなるFQDNを設定しておく
    • 近代のブラウザだとCNは無視されるものもあるらしい (SANs 領域しかみない)

5) 発行者を選択

f:id:mashijp:20190414172729p:plain 先ほど発行したルート証明書を選択する

(中略)

このへんは上のCSRを元にするやつと同じ

11) 証明書の場所を指定【スキップ可能】

f:id:mashijp:20190414173605p:plain

12) 作成完了!

f:id:mashijp:20190414173654p:plain SANs がそれっぽくなっていることを確認

13) エクスポート

証明書の出力

f:id:mashijp:20190414173940p:plain f:id:mashijp:20190414174006p:plain

秘密鍵の出力

f:id:mashijp:20190414174215p:plain

パスワード保護されたPKCS12形式でしか出力できない模様。PEM形式等にしたい場合は自分でopensslコマンド等を使って変換する必要がある。

MacでGUIを使ってオレオレルート認証局を作る

GUIを使ってオレオレルート認証局を作り、楽に証明書を発行したい!

オレオレ認証局を作ること自体は簡単だが、SANs証明書(マルチドメイン証明書)を発行するのは実はかなりめんどくさい!めったに作らないので手順も覚えられない!

qiita.com

実は Mac には標準に組み込まれているキーチェーンでGUIベースでオレオレ認証局を開設し SANs証明書を払い出す仕組みがあるので、今回はそれを利用して開設する。 Windows を使っている人はわからないのでご自身で調べてください><

オレオレ認証局を開設するメリット

常時SSL化が当たり前となった今、ローカル環境や開発環境等でもSSL化しテストすることが多くなってきたと思う。

たとえ商用の環境でなかったとしても、やっぱりこういうエラーは出てほしくないし無視したくない。

f:id:mashijp:20190414175200p:plain f:id:mashijp:20190414175239p:plain

f:id:mashijp:20190414175455p:plain

例えばよくある事故として、「開発時は」証明書の信頼チェックをオフにしオレオレ証明書を使えるようにしているつもりが、実際にエンドユーザーにリリースしたアプリケーションでもチェックをスキップしたままになっていた、などがある。

このような事故を防ぐために&ブラウザ等で毎回証明書を無視する画面を見なくてすむよう、「真面目に」オレオレ証明書を発行しようと思う。 (あらゆる環境で正規の証明書を発行できる人は正規のものを使うにこしたことはないのでそうしてほしい)

f:id:mashijp:20190414180710p:plain

目指したい姿はこんな感じ。オレオレルート認証局を作り、使いたい環境でルート証明書を信頼するようにしておく。 一度オレオレルート認証局を作ってしまえば、あとは 本番環境等と同じ手順で証明書を発行できるようになる。

本番環境

  1. 秘密鍵を払い出し、CSRを発行する
  2. 認証局CSR を渡し、証明書を発行する
    • いわゆるマルチドメイン証明書にしたい場合はこのときに複数のドメインを設定する (SANs)
  3. 証明書を使う

オレオレ環境

  1. 秘密鍵を払い出し、CSRを発行する
  2. オレオレルート認証局で証明書を発行する
    • いわゆるマルチドメイン証明書にしたい場合はこのときに複数のドメインを設定する (SANs)
  3. 証明書を使う

キーチェーンを使うメリット

最初に書いたとおり、Macを使っているなら、オレオレルート認証局をキーチェーンで開設することができる。そのメリットは以下の通り。

  • GUI
  • ルート認証局秘密鍵が自動的にそれなりに安全なところで保存される
  • OS上の信頼設定もできる

ルート認証局を開設する手順

手順がかなり長いように見えるが、SSL大好きマン以外は【スキップ可能】のところは無視してそのまま進んでもらっていい(多分普段の開発で困ることはないと思う)。

1) 認証局作成ウィザードを起動する

f:id:mashijp:20190414165032p:plain

キーチェーンを起動し、メニューから起動できる

2) 認証局を作成

f:id:mashijp:20190414165411p:plain

  • 固有名のタイプ: 自己署名ルート証明書
  • ユーザー証明書: SSLサーバ
    • ここで何が変わるかは分からない…
  • 「デフォルトを無効化」にチェックを入れる
    • ここにチェックを入れるとカスタマイズが可能になる
  • メールの送信元は適当

3) 証明書情報(1)

f:id:mashijp:20190414165851p:plain

  • 有効期間: 好きに設定する
    • 長くないと不便だと思う人が多いと思うのでここでは10年にする
  • インビテーションに署名: 外す
    • 割愛するが今回の用途では不要なので外す (どっちでもいい)

4) 証明書情報(2)

f:id:mashijp:20190414170134p:plain 好きに入力してほしい

5) このCAの鍵ペア情報【スキップ可能】

f:id:mashijp:20190414170224p:plain 好きなものを選ぶ。2019年現在、世の中の主要ルート証明書RSA 2048bit。こだわりがないなら変えないほうがいい。

6) このCAのユーザーが使う鍵ペア情報を指定【スキップ可能】

f:id:mashijp:20190414170424p:plain これも特に変えなくていい

7) このCAが使う鍵用途拡張領域【スキップ可能】

f:id:mashijp:20190414170608p:plain

なんとなく本物のルート証明書っぽくしたいので以下のようにしてみた。詳しい方、間違ってたら教えてください。 多分オレオレ証明書として使う分にはここの設定をどう変えても普通に動くと思う。

  • この拡張領域は重要 にチェックを入れる
  • 証明書署名、CRL署名 にチェックを入れる

多分ここの部分に相当すると思うんだけど… f:id:mashijp:20190414170750p:plain

8) このCAのユーザーが使う鍵用途拡張領域【スキップ可能】

f:id:mashijp:20190414171014p:plain

  • この拡張領域は重要 にチェックを入れる
  • 署名、鍵の暗号化 にチェックを入れる

9) このCAが使う拡張鍵用途拡張領域【スキップ可能】

f:id:mashijp:20190414171158p:plain

そのまま進む

10) このCAのユーザーが使う拡張鍵用途拡張領域【スキップ可能】

f:id:mashijp:20190414171236p:plain - この拡張領域は重要 のチェックを外す - SSLクライアント認証、SSLサーバ認証 にチェックを入れる

11) このCAが使う基本成約拡張領域【スキップ可能】

f:id:mashijp:20190414171352p:plain そのまま進む

12) このCAのユーザーが使う基本成約拡張領域【スキップ可能】

f:id:mashijp:20190414171446p:plain - 基本成約拡張領域を含める にチェックを入れる

13) このCAが使うサブジェクト代替名拡張領域【スキップ可能】

f:id:mashijp:20190414171528p:plain そのまま

14) ユーザーが使うサブジェクト代替名拡張領域【スキップ可能】

f:id:mashijp:20190414171629p:plain

15) 保存場所の指定【スキップ可能】

f:id:mashijp:20190414171647p:plain

16) 完成!

f:id:mashijp:20190414171713p:plain

オレオレルート証明書の信頼設定

f:id:mashijp:20190414173820p:plain

発行した証明書を選び、上記のように「常に信頼する」になっていれば使える状態のはずだが、なぜか発行したてのルート証明書は信頼されないことがある? その場合は、一回「信頼しない」にし画面を閉じたあともう一度開いて「常に信頼する」に設定しておく。

f:id:mashijp:20190414173840p:plain

Mac以外の環境で信頼するようにしたい場合は、ルート証明書を書き出し、その証明書を信頼するようそれぞれのOS/デバイスで設定すればよい。

続き

mashi.hatenablog.com

この手順で作った僕の認証局の証明書を貼っておく。みんな僕のことすごく信頼していると思うので、みんなのOSで信頼するように設定しておいてね*1! (^^)

$ openssl x509 -in mashi\ Root\ CA\ certificates.pem -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1 (0x1)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=mashi Root CA, C=JP/emailAddress=example@mashi.hatenablog.com
        Validity
            Not Before: Apr 14 08:16:51 2019 GMT
            Not After : Apr 11 08:16:51 2029 GMT
        Subject: CN=mashi Root CA, C=JP/emailAddress=example@mashi.hatenablog.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:c0:87:db:1e:cf:8d:79:53:c7:08:db:8c:90:99:
                    a9:cd:5d:71:32:5a:34:9b:1d:6f:aa:65:12:eb:0f:
                    84:54:04:84:9b:82:9b:0c:0a:96:c1:42:4f:3b:48:
                    9d:a8:6e:7b:f1:d3:5e:e2:75:df:b9:07:4a:eb:c3:
                    8c:1a:bb:7a:41:ca:c8:74:94:de:30:d3:78:7b:40:
                    ef:f2:db:d9:b4:c7:fe:1a:9e:8e:af:ce:32:f7:42:
                    2c:f9:0d:f8:44:48:fd:ea:05:7b:2c:77:c7:d7:9c:
                    a4:1a:01:d9:bc:90:56:91:8a:3e:bb:50:36:58:a3:
                    37:14:e1:9a:66:7c:20:b4:7e:a2:d1:58:f7:cb:98:
                    85:fe:2a:b7:1f:e3:f0:c2:75:c9:43:e0:83:c0:c1:
                    0b:8f:bb:fa:96:21:78:dc:b0:79:38:39:c3:f1:22:
                    f0:af:1a:97:f7:58:9c:1b:f7:3e:04:51:ae:c8:21:
                    bb:88:be:a2:1a:32:cb:6a:25:4d:5d:54:23:76:3d:
                    54:9c:93:81:71:50:bd:cb:7e:ef:f4:35:de:07:b6:
                    75:67:78:2c:51:ed:1c:8d:29:d7:81:d3:aa:2d:d2:
                    9c:3f:29:5e:15:0c:59:77:fa:1a:1d:79:d3:42:7f:
                    6b:41:da:98:62:90:d7:f9:f8:83:86:ed:e3:dd:bb:
                    2f:89
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment, Certificate Sign
            X509v3 Extended Key Usage: 
                TLS Web Client Authentication, TLS Web Server Authentication
    Signature Algorithm: sha256WithRSAEncryption
         57:31:23:3c:e0:d1:3e:c5:c6:ae:8e:e6:7b:ef:45:7d:58:c7:
         9e:81:8d:f1:cd:66:57:ba:03:2e:f0:8e:13:03:8a:59:98:9b:
         20:00:71:a7:1b:a2:b4:7c:5c:0b:8e:0d:9a:34:98:be:6a:d0:
         35:13:3c:0d:46:eb:e5:f5:a5:37:37:80:a5:ff:55:b1:95:41:
         f8:b5:8e:8f:3e:5f:30:43:63:3d:4a:f6:0b:2d:d1:94:d4:b8:
         c2:4d:b0:89:05:d3:ce:74:fc:7c:99:29:fe:4c:cd:2f:1d:ef:
         02:ed:31:28:39:50:3d:17:ec:6c:2a:16:31:e5:74:ab:3f:10:
         8c:85:13:74:bf:0a:e0:e7:0d:c3:34:28:8c:05:f6:16:cc:aa:
         c1:1c:d8:75:02:eb:73:a1:3f:b9:71:f6:f5:ce:8d:13:0f:f0:
         8a:eb:79:51:28:d7:fc:63:ed:26:91:1c:cd:51:81:16:ca:d1:
         d5:4a:70:8e:06:02:b0:d3:3e:c6:d9:59:86:92:9e:ae:1d:32:
         29:e3:7b:1f:e8:e0:43:ca:37:79:82:85:f2:19:e3:ac:db:a3:
         e4:86:c8:f4:62:ba:01:03:21:91:b2:c1:ba:da:cd:00:00:29:
         26:ce:cc:92:14:3c:74:2b:c1:79:6d:06:8b:22:b8:10:2a:53:
         0f:6b:59:6b
-----BEGIN CERTIFICATE-----
MIIDYTCCAkmgAwIBAgIBATANBgkqhkiG9w0BAQsFADBSMRYwFAYDVQQDDA1tYXNo
aSBSb290IENBMQswCQYDVQQGEwJKUDErMCkGCSqGSIb3DQEJARYcZXhhbXBsZUBt
YXNoaS5oYXRlbmFibG9nLmNvbTAeFw0xOTA0MTQwODE2NTFaFw0yOTA0MTEwODE2
NTFaMFIxFjAUBgNVBAMMDW1hc2hpIFJvb3QgQ0ExCzAJBgNVBAYTAkpQMSswKQYJ
KoZIhvcNAQkBFhxleGFtcGxlQG1hc2hpLmhhdGVuYWJsb2cuY29tMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwIfbHs+NeVPHCNuMkJmpzV1xMlo0mx1v
qmUS6w+EVASEm4KbDAqWwUJPO0idqG578dNe4nXfuQdK68OMGrt6QcrIdJTeMNN4
e0Dv8tvZtMf+Gp6Or84y90Is+Q34REj96gV7LHfH15ykGgHZvJBWkYo+u1A2WKM3
FOGaZnwgtH6i0Vj3y5iF/iq3H+PwwnXJQ+CDwMELj7v6liF43LB5ODnD8SLwrxqX
91icG/c+BFGuyCG7iL6iGjLLaiVNXVQjdj1UnJOBcVC9y37v9DXeB7Z1Z3gsUe0c
jSnXgdOqLdKcPyleFQxZd/oaHXnTQn9rQdqYYpDX+fiDhu3j3bsviQIDAQABo0Iw
QDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICpDAdBgNVHSUEFjAUBggr
BgEFBQcDAgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAFcxIzzg0T7Fxq6O
5nvvRX1Yx56BjfHNZle6Ay7wjhMDilmYmyAAcacborR8XAuODZo0mL5q0DUTPA1G
6+X1pTc3gKX/VbGVQfi1jo8+XzBDYz1K9gst0ZTUuMJNsIkF0850/HyZKf5MzS8d
7wLtMSg5UD0X7GwqFjHldKs/EIyFE3S/CuDnDcM0KIwF9hbMqsEc2HUC63OhP7lx
9vXOjRMP8IrreVEo1/xj7SaRHM1RgRbK0dVKcI4GArDTPsbZWYaSnq4dMinjex/o
4EPKN3mChfIZ46zbo+SGyPRiugEDIZGywbrazQAAKSbOzJIUPHQrwXltBosiuBAq
Uw9rWWs=
-----END CERTIFICATE-----

ScalaのFutureについてのスライド書きました/つまらないシステム

つまらないシステム(1) - Scala の Future と ExecutionContext

speakerdeck.com

つまらないシステム(2) - Scala 書きやすすぎるFutureの罠

speakerdeck.com

というスライドを公開しました。

Scala の Future を使うのに慣れてきた人向けの、Future の ExecutionContext を上手に使って詰まらない堅牢なシステムを作ろうという話です。

Scala の Future ってどうやって使うの?Promiseって何?

"Scala Future" で検索して出てくるFutureの解説は、Scala公式サイトのドキュメントを除いて大体こんな感じで紹介されてることが多い。

import scala.concurrent._
import ExecutionContext.Implicits.global
Future {
  Thread.sleep(1000)
  println("hoge")
}
println("fuga")
//->
// fuga
// hoge

こういうFuture.applyにThread.sleepやIOのblockingをする例って非常に悪いと思っている。まるで、Futureでsleepするのが普通のコードっぽく見えるじゃん。違うの、単に説明の時に楽だからsleepしてるだけなの。説明コードが短くてすむの。ちょっと使ってるだけ。プロダクトコードでsleepするのやめろ。うわあ!!Future内でブロッキングやめろ!!そこみんなのトイレだから!無駄に占有しないで!

かといって、公式ドキュメント読めっていってもあのドキュメント長いし退屈。詳細に書いてあるのはわかるんだけど、初学者がちょっとFuture使ってみたいわ〜ってときに「ん?長いな別のサイト見るか」ってなってもしょうがない。

じゃあお前がかけよって言われるわけだが、なんか上手に書けない。前々から書こう書こうと思ってるんだけど。。箇条書きレベルでメモを残すので誰か日本語にしてください。

Scala の Future と Promise って何?

Promise なんて興味ない?いや、Futureの本体はPromiseといっても過言じゃないですよ。

Future[A]... いつか型Aの値が与えられる
Promise[A]... いつか型Aの値を与える

Future[A] いつか型Aの値が与えられる

Future にはいつか値が与えられる。値が与えられたときどういう動作をしてほしいのかは、foreach(等)で定義できる。

import scala.concurrent._
import ExecutionContext.Implicits.global
val a: Future[String] = getFuture()
// a にはいつかStringの値が与えられる。
// a に値が与えられたら、printlnは実行される。
a.foreach(e => println("値やっときたわ: " + e))
println("piyopiyo")

// ->
//   piyopiyo
//   値やっときたわ: ???

Promise[A] いつか型Aの値を与える

import scala.concurrent._
val b: Promise[String] = Promise[String]
// b にはいつかStringの値を与える

b.success("値あげるよ〜")
b.success("2回目実行するよ〜") // -> java.lang.IllegalStateException: Promise already completed.

これ何の役にたつの?

Promise[A] から Future[A] を作ることができる

Promise#futureを呼ぶと、Futureを作ることができる。
Promiseのsuccessを呼ぶと、その瞬間にFutureのforeach(等)が呼び出される。

import scala.concurrent._
import ExecutionContext.Implicits.global
val promise: Promise[String] = Promise[String]
val future: Future[String] = promise.future
future.foreach(e => println("値やっときたわ: " + e))
println("piyopiyo")
promise.success("こんにちは")
// ->
//  piyopiyo
//  値やっときたわ: こんにちは

じゃあ Future.apply って何

Futureの作り方ってFuture.applyじゃないんですか?!そう聞きました!こういう例で習いました!

val future: Future[A] = Future {
  Thread.sleep(1000)
  function1("fugahoge")
}

それでも作れるけど内部で同じように Promise 使ってます。ただの便利関数です。

まとめ

  • Future[A]... いつか型Aの値が与えられる
  • Promise[A]... いつか型Aの値を与える
  • Promise と Future で イベントハンドリング が楽に書ける
  • FutureでThread.sleepやめろ!(Thread.sleepやブロッキングすると当然暇なスレッドができます。それを意識してやってるなら別にいいです)

というのがわかる5分で読める解説ページができるといいな〜

Future内でThread.sleepはするな

前回の記事(Scala ExecutionContextって何 / Futureはスレッド立ち上げじゃないよ - ましめも) で import scala.concurrent.ExecutionContext.Implicits.global とは何なのか、そもそも ExecutionContext とは ということについて解説した。

おさらい

  • ExecutionContext は スレッドプールを持っていて、そこにタスクを割り当てる機構
  • ExecutionContext.Implicits.global はデフォルトではCPUコア数分のスレッドを持っている

Future内でThread.sleepはご法度

ExecutionContext.Implicits.global を使っている状態でThread.sleepをすると非常に迷惑になることがある。
例えば次のようなコードがあったとして、CPUコア数4のマシンで関数fugaと関数hogeが同時に実行されると一体どういうことになってしまうか?

import scala.concurrent._
import ExecutionContext.Implicits.global
def fuga = {
  // CPUコストのかかる function1 という関数をマルチコアを使って効率よく処理したい
  (1 to 10).map {e =>
    Future {function1(e)}
  }
}
def hoge = {
  // 3秒待ってから処理するのを簡単に書いてみた
  (1 to 3).map {e =>
    Future {Thread.sleep(3000); function2(e)} 
  }
}

ご想像の通り、処理がつっかえてしまう(下図)。

f:id:mashijp:20141207235401p:plain

もちろんCPU使用率が100%であればマシン資源をフルに使っているからいいのだが、緑色のタスクはただ寝てるだけだ!(Thread.sleep しているだけ) 資源を無駄にしてしまっている!
橙色のタスクは律儀にglobalのスレッドが空くのをただただ待っている状態だ…

よって、ExecutionContext.Implicits.global を使う場合に原則 Thread.sleep を行ってはいけない。同様に、ブロッキングが発生する操作を行うべきではない(IOのblockingなど)。ここ、みんなの使うところだから。

Thread.sleepしたい場合やIOのblockingを行う場合どうすりゃいいのか

タイトルは半分ウソで、絶対にThread.sleepしてはいけないわけではない。みんなで共用する(可能性のある) ExecutionContext.Implicits.global でそういうことをすると思わぬパフォーマンスの低下を生む可能性があるわけであって、自分で使う用の ExecutionContext を作ってそれを使えば問題ない。

ExecutionContext のscaladocにもそう書いてある

A custom ExecutionContext may be appropriate to execute code which blocks on IO or performs long-running computations. ExecutionContext.fromExecutorService and ExecutionContext.fromExecutor are good ways to create a custom ExecutionContext.

例えばこんな感じ

import scala.concurrent._
import java.util.concurrent.Executors
val ec = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(2))
(1 to 30).foreach {_ => Future{Thread.sleep(3000); println(new java.util.Date)}(ec)}
/* 2個ずつ動いている
Sun Dec 07 23:47:55 JST 2014
Sun Dec 07 23:47:55 JST 2014
Sun Dec 07 23:47:58 JST 2014
Sun Dec 07 23:47:58 JST 2014
Sun Dec 07 23:48:01 JST 2014
Sun Dec 07 23:48:01 JST 2014
Sun Dec 07 23:48:04 JST 2014
**/

これで、global側を邪魔しない。

なおPlay frameworkを使っている場合だと、内包してる akka で簡単に ExecutionContext を作ることができたりする ("Many specific thread pools" のところ参照)
https://www.playframework.com/documentation/2.3.x/ThreadPools

ExecutionContext.Implicits.global のご利用は計画的に

ここまで書いて、ExecutionContext とトイレ(個室)はほとんど同じなんじゃと思い始めた。ExecutionContext.Implicits.global は共用トイレと考えると…

  • 個室の数(= Thread数 = 同時に処理できる数)には限りがある。先着順で処理する
  • 空いてるときは100歩譲って寝てようと何してようと問題にならない
  • 待ち行列ができてるにも関わらずトイレ(ExecutionContext)で寝てるのは許せない。本当に用を足してるならしょうがない
  • 待ち行列ができることに何も問題ないと言い切れるなら別に構わない
  • 自分で作った自分専用のトイレ(ExecutionContext)なら自由にしてくれ

うん!大体あってる気がする!それでは快適なトイレライフを!

Scala ExecutionContextって何 / Futureはスレッド立ち上げじゃないよ

こういう人は、あとあと処理が詰まったり理解できない挙動が起きたりして困るので注意。

  • よくわからないけどコンパイル時に怒られるので import scala.concurrent.ExecutionContext.Implicits.global を書いている
  • Future.apply は 「スレッドを立ち上げて非同期に実行する」と理解している
  • 特に何も考えず Future 内で Thread.sleep をしている

ExecutionContextとは

Future#mapやFuture.applyにimplicitパラメータとして要求される*1ExecutionContextって何なのか?
何か渡さないといけないからとりあえず import scala.concurrent.ExecutionContext.Implicits.global と書いている人もいるんじゃないだろうか。

ExecutionContext*2は簡単に説明するといい感じに非同期に実行してくれる仕組み*3。ExecutionContextのexecuteメソッドは、Runnableを受け取り「適当なタイミングで」実行してくれる。適当なタイミングって結局いつよ?というのはExecutionContextの実装による。

scala.concurrent.ExecutionContext.Implicits.global は scalaが標準で提供しているExecutionContextだと思えば良い。

RunnableはJavaのinterfaceで、runという関数を持っているものを表す。
https://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html

試しにExecutionContextに Thread.sleepしてprintlnするだけ のRunnableを渡してみよう。

val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
// ec: scala.concurrent.ExecutionContext = scala.concurrent.impl.ExecutionContextImpl@6168d3c5

def now() = new java.util.Date();
println(now); ec.execute(new Runnable{def run: Unit = {Thread.sleep(3000); println("fuga! " + now);}}); println(now);
// Sun Nov 23 23:41:25 JST 2014
// Sun Nov 23 23:41:25 JST 2014
// fuga! Sun Nov 23 23:41:28 JST 2014

このようにExecutionContextのexecuteに渡したRunnableは非同期に実行されていることがわかる。

ExecutionContextのありがたみとは

そうすると次のような疑問が出てくると思う

  • ああ、つまりスレッド立ち上がって非同期に実行するってこと?
  • 非同期に動作させたいならjava.lang.Thread使えば同じなんじゃないの?

これはいずれもNoである*4

例えば10個の重いタスクがあったとして、それぞれのタスクに対し new Thread & start (スレッド立ち上げ) して処理した場合は当然次のように10個のスレッドが立ち上がる。

f:id:mashijp:20141124002645p:plain
しかし、この実装は以下のような問題点がある

  • タスク数分スレッドを立ち上げるためメモリ/CPU資源の無駄になる
  • しかも暇しているスレッドがいる

もし次のように最小限のスレッドでタスクを分配できれば最高のパフォーマンスを出せるのではないだろうか?
f:id:mashijp:20141124003318p:plain

このようにタスク(Runnableといったほうが適切か)を「いい感じ」にスレッドに分配するのがExecutionContextの役目だ。繰り返しになるが、「いい感じ」とはどういう感じなのかは実装による。一般には「一定数を最大数とするスレッドプールを持っており、空いてるスレッドを利用してRunnableを処理してくれる」と考えればいいのではないだろうか。
ExecutionContextを使う側は内部実装がどうであるかとかスレッドがどう存在するのかとかを一切意識せずRunnableを渡すだけで良い。

scala.concurrent.ExecutionContext.Implicits.global はどういう実装なのか

ExecutionContext.Implicits.global を使ってるとどういうふうに「いい感じ」に動くのかというと*5 最大でCPU論理プロセッサ数のn倍(nはJavaオプションで指定可能。デフォルトは1)のスレッドを立ち上げ処理してくれるようだ。

実際、8コアのマシンで使うと次のように動く。

val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
def now() = new java.util.Date();
(1 to 30).foreach{_ => ec.execute(new Runnable {def run {Thread.sleep(3000); println(now)}})}
/*
Mon Nov 24 00:43:03 JST 2014
Mon Nov 24 00:43:03 JST 2014
Mon Nov 24 00:43:03 JST 2014
Mon Nov 24 00:43:03 JST 2014
Mon Nov 24 00:43:03 JST 2014
Mon Nov 24 00:43:03 JST 2014
Mon Nov 24 00:43:03 JST 2014
Mon Nov 24 00:43:03 JST 2014
Mon Nov 24 00:43:06 JST 2014
Mon Nov 24 00:43:06 JST 2014
Mon Nov 24 00:43:06 JST 2014
Mon Nov 24 00:43:06 JST 2014
Mon Nov 24 00:43:06 JST 2014
Mon Nov 24 00:43:06 JST 2014
Mon Nov 24 00:43:06 JST 2014
Mon Nov 24 00:43:06 JST 2014
Mon Nov 24 00:43:09 JST 2014
Mon Nov 24 00:43:09 JST 2014
Mon Nov 24 00:43:09 JST 2014
Mon Nov 24 00:43:09 JST 2014
Mon Nov 24 00:43:09 JST 2014
Mon Nov 24 00:43:09 JST 2014
Mon Nov 24 00:43:09 JST 2014
(以下略)
*/

このように、8個ずつRunnableが処理されているのがわかる。

Future.applyとは一体なんなのか

Futureを使った簡単なサンプルコードを以下に示す。

import scala.concurrent.ExecutionContext.Implicits.global
val a: Future[String] = Future {
  Thread.sleep(3000)
  "fuga"
}
a.foreach(println)

このFuture.applyを使ったコードは次のように書き表すことができる。

val p: Promise[String] = Promise[String]
val a: Future[String] = p.future
a.foreach(println)
scala.concurrent.ExecutionContext.Implicits.global.execute(
  new Runnable{def run = {
    Thread.sleep(3000)
    p.success("fuga")
  }}
)

後者のコードはPromiseを書いたりしなければならなくて面倒。後者のコードを簡単に実現してくれるのが、Future.applyだ。

Future.apply は CPUコア数分しか同時に処理しない

つまりFuture.applyはCPUコア数分しかスレッドを使わず、同時に処理する数もCPUコア数と同等になる(ExecutionContext.Implicits.global を使った場合は)。Future.applyは決して「スレッドを立ち上げる」という処理と同等でないことに気をつけよう。

import scala.concurrent.ExecutionContext.Implicits.global
(1 to 30).foreach(_ => Future{Thread.sleep(2000);println(now)})

/*
Mon Nov 24 00:29:42 JST 2014
Mon Nov 24 00:29:42 JST 2014
Mon Nov 24 00:29:42 JST 2014
Mon Nov 24 00:29:42 JST 2014
Mon Nov 24 00:29:42 JST 2014
Mon Nov 24 00:29:42 JST 2014
Mon Nov 24 00:29:42 JST 2014
Mon Nov 24 00:29:42 JST 2014
Mon Nov 24 00:29:44 JST 2014
Mon Nov 24 00:29:44 JST 2014
Mon Nov 24 00:29:44 JST 2014
Mon Nov 24 00:29:44 JST 2014
Mon Nov 24 00:29:44 JST 2014
Mon Nov 24 00:29:44 JST 2014
Mon Nov 24 00:29:44 JST 2014
Mon Nov 24 00:29:44 JST 2014
Mon Nov 24 00:29:46 JST 2014
Mon Nov 24 00:29:46 JST 2014
*/

(追記)

同時並行数を変えたい場合やExecutionContext.Implicits.globalを使う上での注意点をまとめた記事を上げた
Future内でThread.sleepはするな - ましめも

*1:http://www.scala-lang.org/api/current/index.html#scala.concurrent.Future

*2:http://www.scala-lang.org/api/current/index.html#scala.concurrent.ExecutionContext

*3:やろうとすれば同期するExecutionContextも作れるが基本的に誰も得しないと思う

*4:必ずしもNoではないが(当然ExecutionContextの実装による)

*5:https://github.com/scala/scala/blob/v2.10.3/src/library/scala/concurrent/impl/ExecutionContextImpl.scala#L65

IntelliJ IDEAさんがplayの自動生成ファイルを認識してくれない

play2.3で自動生成するコード(ReverseRoutingやView)をIntelliJ IDEAが認識してくれないことがあるのでその解決方法をメモ

認識されていない様子(Assets.atが赤い)
f:id:mashijp:20140607123209p:plain

1) File -> Project Structure
f:id:mashijp:20140607123556p:plain

2) Modulesを開き、targetの下を見る
src_managed/main/controllers と twirl/main/views がSourcesになっている
f:id:mashijp:20140607123415p:plain

3) 上の"Mark as"のところをポチポチしてsrc_managed/main と twirl/main がSourcesになるように設定する
f:id:mashijp:20140607123424p:plain

4) 直った!
f:id:mashijp:20140607123430p:plain


play2.2のときもこういう問題が起きていたけど、play2.3になってscala templateが分離されたので作業が増えた…
activator ideaせずにsbt projectとしてimportしたせいかもしれない(play2.2時代はplay ideaしてもこの問題が発生していた)。