capistranoでunicornのoldプロセスが殺せない

最近Capistranoを導入しました.
一般的なRailsのプロジェクトにCapistranoを組み込むのは,このあたりを参考にすると割と楽にいける.

morizyun.github.io


qiita.com




ただ,これに合わせて重要になってくるものがあって,どうにもこれだけではunicornのリスタートがうまくいかなかったので,書き残しておく.

unicornのoldプロセスが死んでくれない


今回,capistrano3-unicornというgemを使用した.
まぁ内容的にはそんなに難しいことはなく,unicornにUSR2シグナルを送るだけである.

tablexi/capistrano3-unicorn · GitHub



だけど死んでくれない.
これは,unicornのconfigの書き方に寄るところが大きい.

まずcapistrano側でunicornの設定ファイルを置く


上記のcapistrano3-unicornを見ればだいたい必要なことは書いてある.
deploy.rbの,unicornに関係ありそうなところを抜き出してみた.

set :linked_dirs, %w{bin log tmp/backup tmp/pids tmp/sockets vendor/bundle}
set :unicorn_pid, "#{shared_path}/tmp/pids/unicorn.pid"
set :unicorn_config_path, "#{release_path}/config/unicorn.rb"

大事なのは,pidファイルの保存場所をshared_pathにしておくこと.そしてそれをlinked_dirsに登録しておくこと.
こうしておかないと,pidから現在動いているunicornのプロセスを特定できない.

unicornのconfigファイルはrelease_pathに配置した.これは別にshared_pathでもいいとは思う.

そしてさらに以下のように記述してunicornをrestartさせる.

after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
  task :restart do
    invoke 'unicorn:restart'
  end
end


これでconfig/unicorn.rbがしっかり書いてあれば,unicornのデプロイは成功する.


unicorn.rbを正しく設定する

ここでかなり手間取った.

config/unicorn.rb

shared_path = "/srv/www/asumibot/shared/"
current_path = "/srv/www/asumibot/current"

# ソケット経由で通信する
# ここがcapistranoの設定と合致していないと失敗する
listen File.expand_path('tmp/sockets/unicorn.sock', shared_path)
pid File.expand_path('tmp/pids/unicorn.pid', shared_path)


# capistrano 用に RAILS_ROOT を指定
working_directory current_path

# ダウンタイムなくす
preload_app true

before_fork do |server, worker|
  ENV['BUNDLE_GEMFILE'] = File.expand_path('Gemfile', current_path)
  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

shared_pathとcurrent_pathはcapistranoの設定に合わせて設定してもらえれば良い.

ソケット大事

unicornが再起動する際,前のプロセスを殺しにいくのだがそのときにソケット経由で通信する.
そのためにソケットファイルのある場所を正確に記述しておく必要がある.

これがcapistranoのrelease_pathと同じディレクトリ指定をされていたために,unicornが古いプロセスを探せなくなり,最終的にunicornの再起動に失敗していた.
いや,確かにcapistranoを導入していなければ,デプロイするたびにsocketファイルの場所が変わったりするなんてことがないので,何も考えずにrails_rootからたどるパスしか書いてなかった.

working_directoryは設定しよう

これを設定してあげないと,unicornが再起動するときに古いrelease_pathのソースを読み取っていることがある.
特にassets周りはproduction環境ではdigestがついてしまうので,古いソースを読み取るともれなくassetsがすべて404になったりしていた.

bundle_gemfileを設定しないとgemが更新されない

新しいgemを追加した際などに,bundle install自体は(capistrano側で)走っていても,unicorn起動時に新しいgemを読み込んでくれない.

実際に出てたエラー

だいたいこんなエラーが出てくる.

I, [2015-04-18T06:05:20.121566 #9390]  INFO -- : Refreshing Gem list
E, [2015-04-18T06:05:22.429058 #9390] ERROR -- : adding listener failed addr=/srv/www/asumibot/releases/20150418060529/tmp/sockets/unicorn.sock (in use)
E, [2015-04-18T06:05:22.429122 #9390] ERROR -- : retrying in 0.5 seconds (4 tries left)
E, [2015-04-18T06:05:22.929649 #9390] ERROR -- : adding listener failed addr=/srv/www/asumibot/releases/20150418060529/tmp/sockets/unicorn.sock (in use)
E, [2015-04-18T06:05:22.929722 #9390] ERROR -- : retrying in 0.5 seconds (3 tries left)
E, [2015-04-18T06:05:23.430183 #9390] ERROR -- : adding listener failed addr=/srv/www/asumibot/releases/20150418060529/tmp/sockets/unicorn.sock (in use)
E, [2015-04-18T06:05:23.430255 #9390] ERROR -- : retrying in 0.5 seconds (2 tries left)
E, [2015-04-18T06:05:23.930722 #9390] ERROR -- : adding listener failed addr=/srv/www/asumibot/releases/20150418060529/tmp/sockets/unicorn.sock (in use)
E, [2015-04-18T06:05:23.930792 #9390] ERROR -- : retrying in 0.5 seconds (1 tries left)
E, [2015-04-18T06:05:24.431303 #9390] ERROR -- : adding listener failed addr=/srv/www/asumibot/releases/20150418060529/tmp/sockets/unicorn.sock (in use)
E, [2015-04-18T06:05:24.431377 #9390] ERROR -- : retrying in 0.5 seconds (0 tries left)
E, [2015-04-18T06:05:24.931837 #9390] ERROR -- : adding listener failed addr=/srv/www/asumibot/releases/20150418060529/tmp/sockets/unicorn.sock (in use)
/srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/socket_helper.rb:158:in `initialize': Address already in use - connect(2) for /srv/www/asumibot/releases/20150418060529/tmp/sockets/un\
icorn.sock (Errno::EADDRINUSE)
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/socket_helper.rb:158:in `new'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/socket_helper.rb:158:in `bind_listen'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:242:in `listen'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:809:in `block in bind_new_listeners!'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:809:in `each'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:809:in `bind_new_listeners!'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:138:in `start'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/gems/unicorn-4.8.3/bin/unicorn:126:in `<top (required)>'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/bin/unicorn:23:in `load'
        from /srv/www/asumibot/shared/bundle/ruby/2.2.0/bin/unicorn:23:in `<main>'

UrlShortener for iOSを使ってみた

iOS上で短縮URLを使いたくなったので,短縮URLサービスを色々と調べてみた.
実は短縮URL系のサービスはAPIを提供しているところが多く,そのAPIのURLを叩けば割と簡単に短縮URLを生成できる.
でもこれ,自分で通信部分を書くのって結構めんどくさい気がする.

というわけで,URLShortenerというのを使ってみた.

theaudience/URL-Shortener · GitHub


  • cli.gs
  • redir.ec
  • bit.ly
  • is.gd

この中から好きなサービスを選んで短縮URLが得られる.
ただし,is.gd以外は認証が必要になるので,自分でアカウントを発行しておく必要がある.
というわけで,今回はis.dgを使ってみた.

cocoapodsにする

ライブラリの管理はすべてcocoapodsでやりたかった.

theaudience/URL-Shortener · GitHub


ここには残念ながらcocoapodsの導入方法も書いてなく,cocoapodsに登録もされていないようだった.
仕方ないのでcocoapods化しておいた.

github.com




Podfileに以下の一文を追記するとインストールできる.

pod 'UrlShortener', git: "https://github.com/h3poteto/URL-Shortener.git"

使い方

var shortener = UrlShortener()
shortener.shortenUrl(longURL, withService: UrlShortenerServiceIsgd, completion: { (shortUrl) -> Void in
  // hogehoge
}, error: { (error) -> Void in
  // hogehoge
})


割と楽にいける!!

と思いきや,たまにうまく行かないことがあります.

Sorry, the URL you entered is on our internal blacklist. It may have been used abusively in the past, or it may link to another URL redirection service.

実はこれ一度is.gdで短縮したURLが,再びis.gdで短縮されるときに発生します.

つまりは,短縮URL化が二重で走っているんですね.

しかもこれ,shortenUrlメソッドのブロックでは,completeブロックとして扱われています.
なので,ちゃんとチェックしてやる必要があります.

var shortener = UrlShortener()
shortener.shortenUrl(longURL, withService: UrlShortenerServiceIsgd, completion: { (shortUrl) -> Void i
  if shortUrl.hasPrefix("http://") || shortUrl.hasPrefix("https://") {
    // 成功時の処理
  } else {
    // 二重に短縮化が走っている
  }
}, error: { (error) -> Void in
  // hogehoge
})

参考

qiita.com


d.hatena.ne.jp

enh-ruby-mode対応のruby-block.elを作った


今日はEmacsの話をします.

Enhanced-ruby-modeのすすめ


Emacsrubyのコードを書いてみると,結構よくシンタックスハイライトしてくれています.
これはデフォルトで付属するruby-modeによるものです.

しかし,ruby-modeだとインデントに難があるというのが,一番悲しいところです.

ruby-mode のインデントをいい感じにする - willnet.in



これを施してもいいのですが,それでもなお足らない部分があります.



Railsのコードですが,例えば

class SomeModel < ActiveRecord::Base
  has_one :user,
    foreign_key: :account_id,
    class_name: "Account"

こういうインデントを自動で実現したい(というかこれがruby的には正しい)のですが,ruby-modeだと

class SomeModel < ActiveRecord::Base
  has_one :user,
          foreign_key: :account_id,
          class_name: "Account"

となってしまうのです.

これがどうにもハックできなくて…….



というわけで最近はEnhanced-ruby-modeというのを使うようにしています.

zenspider/enhanced-ruby-mode · GitHub




こちらはシンタックスハイライトや,インデントが独自にカスタマイズされており,上記のようなインデントを(特にカスタマイズなく)しっかりと実現してくれます.


そしてruby-modeの代わりとして使えるので,今まで

(add-hook 'ruby-mode-hook '(lambda () (ruby-electric-mode t)))

となっていたところを

(add-hook 'enh-ruby-mode-hook '(lambda () (ruby-electric-mode t)))

と変えてやるだけでOK.


ちなみにenhを使っている時のdeep-indent-paren-styleはちょっとだけ変わっていて,

(setq enh-ruby-deep-indent-paren nil)

としてやる必要があります.ちなみに閉じカッコの位置については,この設定だけでいい位置に来ます.

Enh用のruby-block


大体のものはEnhanced-ruby-modeでも使えるのですが,ruby-blockだけが使えない.
hookをenh-ruby-modeに書き換えても上手くハイライトしてくれません.

これ,実はruby-block.elのソース内で,ruby-modeがrequireされているからなんですね.

adolfosousa/ruby-block.el · GitHub



だからenhanced-ruby-modeを使っているときは当然ながら動かないんですね.


というわけで,enhanced-ruby-mode用にruby-block.elを拡張してみました.


github.com




まぁ実際requireを書き換えてみたら,案外なにもせずに上手く言っちゃったので,上げてみただけなんですけどね.






Emacs使いの方は是非,Enhanced-ruby-modeとセットで使ってみてください.便利ですよ.

参考

qiita.com


d.hatena.ne.jp

sprocketsの機能に不満を感じたのでgemを作った


Railsのassets関連の機構として採用されているSprockets.github.com


かなり便利な上に,普段あまりこいつの機能について意識せずに使える設計となっているので,見事だなぁと思うわけですが,ちょっとだけ不満なことがありました.

Sprocketsの機能により,

<%= stylesheet_link_tag "application", media: "all" %>

と記述しておけば,実際にhtmlとしてレンダリングされるときには,

<link rel="stylesheet" href="/assets/stylesheets/application.css" />

というタグが生成される.


実はこの,stylesheet_link_tag自体はActionViewメソッドです.
rails/asset_tag_helper.rb at 4-2-stable · rails/rails · GitHub



それをSprockets-railsが上書きしています.
sprockets-rails/helper.rb at master · rails/sprockets-rails · GitHub


Sprocketsを通すことにより,cssなら自動でapp/assets/sytlesheets/以下を見てくれたり,jsなら自動でapp/assets/javascripts以下を見てくれたり,digestをつけてくれたりと,そういう計らいをしてくれます.


存在しないassetsに関しては,スルーする件


ところがこのstylesheet_link_tag,存在しないファイルを指定した場合でも,気にせずhtmlタグを生成してくれます.
そして,このとき存在しないcssを指定するとどうなるのか.


もちろん読み込めないcssなので,ブラウザのデベロッパーコンソールには404エラーが出てきます.

それだけで済めばスタイルが適応されない程度の話なのですが…….

ところが本番だとエラーを吐く


そんな控えめな読み込みエラーなので,うっかり気づかずに本番にアップしたりするとことがあります.
すると,なんと今度はエラーになります.

application.css isn't precompiled


こういうのが出てきます.
もちろん,assets:precompileを忘れた場合もこれが出てくるのですが,存在しないassetsを読み込んだ場合も,同じ扱いになります.


で,これは500エラーになってしまうので,なんとページ自体が見られないわけです.


これ,もうちょっと早い段階で通知できないの?

sprockets-rails-nonexistent

いろいろ探して,sprockets-rails自体にpull requestを飛ばそうとも思ったのですが,どうも拡張的な要素が強すぎる気がして…….

Gemとして作りました.

github.com



rubygems.orgへの登録も済ませてあるので,普通にgem install sprockets-rails-nonexistentで入ります.


development環境,およびtest環境のとき

stylesheet_link_tagと,javascript_include_tagに反応して,ファイルの存在チェックをします.
存在していた場合,特に何もせず,そのままhtmlタグが生成されます.
存在していなかった場合,raiseエラーとなり,Sprockets::FileNotFoundを返します.

開発中の画面であれば,エラーになるのですぐに気づくことが出来ます.

また,テストであってもcontrollerのテストやintegrationテストであれば,表示されるところまでテストで行われるので,その時点でエラーとなり,気づくことが出来ます.

production環境のとき

仮にstylesheet_link_tagや,javascript_include_tagで存在しないファイルを指定した場合,そのまま今までどおりの挙動を示します.

なので,isn't precompiledになることは変化ありません.
というか,production環境に対する影響は特にありません,今まで通りの挙動です.


最近のRailsだともしかしてあまり困らないのかもしれない

このassetsの仕組み自体がRails3.2あたりから取り入れられた仕組みであり,Rails4.0からはデフォルトでturbolinksが入ります.

今回のgemはturbolinksを使うような局面では,あまり役に立たない気がしています.
というのも,turbolinksは,ページの初回ロード時にassetsをすべて読み込んでしまって,その上でpjaxでページの中身だけを切り替える方式です.
なので,stylesheet_link_tagを使うとしても,すべてを初回に読み込むようなassetsの構成にするのが望ましいです(そうでなければ速くならない).

つまりはすべてのページでcssやjsはすべて読み込まれる前提で,実行だけは別々に,という方式が一番turbolinks的なのです.

だとするなら,isn't precompiledが出るとしても,最初の1ページだけの問題であり,そこだけ解決してしまえば残りはすべて同じ構成となるので,一回だけしか困らないこと,なんですね.



今回のgemはそれとは逆行していて,コントローラごと,ページごとに読み込むcssやjsを分けていきたいときに,isn't precompiledが出てしまうと潰しきれない,というのが主眼にあります.

たしかにturbolinksは速くなるのですが,最初からturbolinksで設計していないと,Rails3.2で構築していたものをそのまま移すのはかなり難しいです.
そして,turbolinksを使わない前提であれば,ページをロードする度に必要のないcssやjsの読み込みが走る分,「すべてのassetsを読み込む」方式だとどんどん遅くなってしまうのです.

Emacsにagを入れたら速すぎて捗る

Emacsを使ってのgrepでは,ずっとrgrepを使ってきたのですが,最近なんだか重い気がして色々調べてみました.

するとどうやら,最近はgrepやackじゃなくて,ag(the silver searcher)を使ったほうが速いとのこと…….
そしてこれはemacsからも利用できるんですね.

ag.el


Wilfred/ag.el · GitHub


マジで速くなります.

今まで,rgrepでは,検索語句や範囲の設定をしたあと,emacsが頑張って読み込んでくれてる感じがありましたが,これがほとんど瞬時にでてきます.

導入

  • the silver searcher本体のインストール
  • emacsにagを導入

するだけで使えるようになります.

silver searcherはaptやyumリポジトリが公開されているので,そのあたりから導入できるかと思います.
普通にターミナルで検索するにしても,結構便利なものです.


emacsへのag.el導入は,githubを見ればできるかと思いますが,念の為.
packagesを使えば簡単なんですけど,リポジトリコピーするのが好きなので.

今回は,セットで,wgrepも入れています.

s.elをコピーします.
magnars/s.el · GitHub


同じディレクトリにag.elをコピーします.
Wilfred/ag.el · GitHub


wgrepもコピーしてきます.
mhayashi1120/Emacs-wgrep · GitHub



あとはこれらをload-pathで読み込める場所に置いてやって,

(require 'ag)
(require 'wgrep-ag)

として読み込んでやるだけで大丈夫.
s.elはag.elが必要としています.packagesで入れると,このへんは自動で入りますね.


使い方

rgrepの時とあまり変わりありません.ただひたすら速い.

M-x agで呼び出せるので,あとは検索してやるだけです.
検索結果について,編集したり反映したりしたい場合は,wgrep-agの拡張設定をしてやればいいです.

そのへんは,こちらが詳しい.

codeout.hatenablog.com


参考

gihyo.jp

kotatu.org