西脇.rb & 東灘.rb(第3回) SPDYでRails動かすまで + Rubyのthread調べた

もくもく会第3回いってきました。
今回のお題は2つ

  • SPDYとRailsで何かつくる
  • RubyのThreadについてしらべる

なぜ二つになったかというとSPDY調べていくうちに
Rails(ruby) 関係ないやん( ̄Д ̄;)
となったため急遽追加。

Mac(lion) で SPDYでRails動かすまで

nginxが1.4よりSPDYが標準装備となったらしい http://nginx.org/en/CHANGES-1.4

これはもらった!

APサーバーはunicornでもよかったが、自分はネコ科の動物には目がないので nginx + puma でいくことにする。

Nginxをコンパイル。

brewにあるやつみても 1.4.0 ではなさそうなので、ソースを落としてくる。

cd work/
wget http://nginx.org/download/nginx-1.4.0.tar.gz
tar zxvf nginx-1.4.0.tar.gz

色々調べるとSSLも最新でないとダメみたい。
Mac(Lion)の標準搭載はだいぶ古いのでこれまたソースをダウンロードしてくる。
NginxのconfigureでOpenSSLのソースあるところを指定するとそのまま取り込んでくれるらしい
べんり〜

cd work/ngix-1.4.0
wget http://www.openssl.org/source/openssl-1.0.1e.tar.gz
tar zxvf openssl-1.0.1e.tar.gz

configureはちょっと特殊。
Macで運用するわけではないし、うまくいかなかったときにversion複数さくさく切り替えれると楽かなと適当なところにbinary置く事にした。
あとでbrewで入れるかもしれないし。

最後のオプションでOpenSSLのソースを展開したディレクトリを指定。

./configure \
  --prefix=$HOME/work/nginx \
  --sbin-path=$HOME/work/nginx/sbin \
  --with-http_spdy_module \
  --conf-path=$HOME/work/nginx/etc/nginx.conf \
  --error-log-path=$HOME/work/nginx/var/error.log \
  --http-log-path=$HOME/work/nginx/var/access.log \
  --http-client-body-temp-path=$HOME/work/nginx/tmp/client_body \
  --http-proxy-temp-path=$HOME/work/nginx/tmp/proxy \
  --http-fastcgi-temp-path=$HOME/work/nginx/tmp/fastcgi \
  --http-uwsgi-temp-path=$HOME/work/nginx/tmp/uwsgi \
  --http-scgi-temp-path=$HOME/work/nginx/tmp/scgi \
  --pid-path=$HOME/work/nginx/tmp/nginx.pid \
  --lock-path=$HOME/work/nginx/var/nginx \
  --with-http_ssl_module \
  --with-openssl=./openssl-1.0.1e

ここでエラー

./configure: error: the HTTP rewrite module require the PCRE library.
...

rewrite はおそらく使わないけどとりあえず入れる事にする。

sudo brew install pcre

そしてmake

make

んで、またエラー

WARNING! If you wish to build 64-bit library, then you have to 
                 invoke './Configure darwin64-x86_64-cc' *manually*
                 You have about 5 seconds to press Ctrl-C to abort. 

いろいろ調べたら OpenSSL の話だった。
んで、configure の中身読みながら以下をconfigureに追加

--with-openssl-opt=darwin64-x86_64-cc

するも撃沈。

いくら調べてもよくわからない(Shellがたいして読めない)んで直接書き換える。

diff -u auto/lib/openssl/make.org auto/lib/openssl/make
--- make.org     2013-05-06 21:21:58.000000000 +0900
+++ make     2013-05-06 21:19:42.000000000 +0900
@@ -56,7 +56,7 @@
 $OPENSSL/.openssl/include/openssl/ssl.h:     $NGX_MAKEFILE
      cd $OPENSSL \\
      && \$(MAKE) clean \\
-     && ./config --prefix=$ngx_prefix no-shared $OPENSSL_OPT \\
+     && ./Configure darwin64-x86_64-cc --prefix=$ngx_prefix no-shared $OPENSSL_OPT \\
      && \$(MAKE) \\
      && \$(MAKE) install LIBDIR=lib
make

成功ヾ(@^▽^@)ノ

証明書は以前練習で発行したのを使用。

最後にコンフィグの設定。デフォルトのやつをコピーして見よう見まねで変更

    # ここに puma と通信する UnixSocket の path を指定
    upstream app {
      server unix:///var/run/app.sock;
    }

    server {
        # listenポートを変えてssl spdy と追加
        listen       3000 default ssl spdy;
        server_name  localhost;
        
        # SSLの設定を追加
        ssl on;
        ssl_certificate     /path/to/your_cert.cert;
        ssl_certificate_key /path/to/your_secret.key;
        ssl_session_timeout 5m;

        root /path/to/app/public;

        location / {
            # root   html;
            # index  index.html index.htm;
            
            # ここでupstream指定
            proxy_pass http://app;

            # 多分Headerに元のHostを足してる?
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        #error_page  404              /404.html;
        ....

Railsを準備

まずはインストールするフォルダを作成

mkdir app
cd app

次にGemfileを作成。今回はせっかくなので一番新しいのを使った。

source "https://rubygems.org"
gem "rails", "4.0.0.rc1"

rails をインストール

bundle install --path vendor/bundle

Railsの基本ファイルを生成。

rails new .

上でGemfileが上書きされるので、上書きされたGemfileにpumaを追加

gem "puma"

puma インストール

bundle

puma確認

bundle exec rails s puma

=> Booting Puma
=> Rails 4.0.0.rc1 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
Puma 2.0.1 starting...
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:3000

最後にSPDY確認

こちら(spdy indicator)chromeエクテンションで確認。

nginx起動。

sbin/nginx

puma 起動

# -bでさっきnginxで指定した UnixSocket のpathを指定
bundle exec puma -b unix:///var/run/app.sock

ブラウザで確認
f:id:sutetotanuki:20130512201002p:plain

RubyのThreadについてしらべる

RubyにはGVL(Giant VM lock)なるものがあるらしくどういうものなのってことで、調べたかったのは以下の5点

  1. CPU100%以上ちゃんと使うの?
  2. switchのタイミングは?
  3. Monitor の便利な使い方
  4. Thread.currentの使いどころ
  5. Abort on Exceptionの使いどころ

んで、時間内に調べられたのは上2つ

CPU100%以上ちゃんと使うの?

公式のDocより引用

ネイティブスレッドを用いて実装されていますが、 現在の実装では Ruby VM は Giant VM lock (GVL) を有しており、同時に実行される ネイティブスレッドは常にひとつです。 ただし、IO 関連のブロックする可能性があるシステムコールを行う場合には GVL を解放します。その場合にはスレッドは同時に実行され得ます。 また拡張ライブラリから GVL を操作できるので、複数のスレッドを 同時に実行するような拡張ライブラリは作成可能です。

同時に実行されるネイティブスレッドは常にひとつです。

(・_・?)...ン?

じゃあCPU100%しか使わないの?

def fib(n)
  n < 2 ? n : fib(n-1) + fib(n - 2)
end

th1 = Thread.new {
  sleep 1
  100.times {
    fib(1000)
  }
}

th2 = Thread.new {
  sleep 1
  100.times {
    fib(1000)
  }
}

th1.join; th2.join;

f:id:sutetotanuki:20130512223835p:plain

ちゃんとつかってる!理由はわからないけど\(^o^)/

switchのタイミングは?

IO 関連のブロックする可能性があるシステムコールを行う場合には GVL を解放します。

さっぱりわからないのでぐぐってみるととてもよいスライドを見つける
http://www.slideshare.net/kosaki55tea/ruby-gvlimprovement-8617719

全ては理解できなかったけど、無理矢理理解すると

  • IOのようなwaitが発生しそうな処理のときにswitchする。たぶんsleepもだとおもう
  • 長い時間同じthreadが走らないようにTimerでswitchする。これは必ず同じthreadがもう一度Lockをとらないようにしている。

ふむふむ。ならこれでどう?

th1 = Thread.new {
  sleep 1
  30.times {
    $th1 << $global
    $global += 1
    w.write("..")
  }
}

th2 = Thread.new {
  sleep 1
  30.times {
    $th2 << $global
    $global += 1
    r.read(1)
  }
}

th1.join; th2.join

p $th1
p "------------"
p "------------"
p "------------"
p $th2

f:id:sutetotanuki:20130513004420p:plain

きれいには切り替わらないな \(^o^)/

この辺で時間終了。

レビュー中(発表中) に Unixの仕組みと似ているという大変貴重な意見をいただけたので、時間できたときにもう少しCのRubyのソース追ったりして調べてみようっと。


その後の懇談会にて、こんなぐだぐだな発表に関わらずMVPをいただけたヾ(@^▽^@)ノ

f:id:sutetotanuki:20130513005242j:plain

これが何か良くわかってないけど、プリペイドカードっぽいのでスターバックスいって支払いのときに出してみて店員の反応を見て考えようw

今回は書く事多くてつかれたw