Hatena::ブログ(Diary)

自称すーじー。

2017-01-09

2016年に買ってよかったもの

なんか「2016年に買ってよかったもの」みたいなエントリが大量に生産されているようなので、いまさらながら流行に乗って書いてみる。

コーヒーのハンドドリップセット

2016年で最も買ってよかったものと言うと、まちがいなくハンドドリップセットだった。コーヒーという趣味がひとつ増えた。

コーヒーは、楽しい。コンビニで買ったクッキーを食べるだけでも、ハンドドリップしたコーヒーがあると一気に「コーヒータイム」になる。知らない街で豆を買ったりコーヒーの美味しい店を探して入ったりする楽しみも増えた。

ハンドドリップってどうやって初めていいかよくわからないものだと思うのだけど、そういう人には東急ハンズの福袋をおすすめしたい。必要なものを必要な組み合わせできちんと入れてくれて、しかもリーズナブル。

正月を待てない人には、上リンクのドリッパーセットとケトルを買うのがおすすめ。豆を粉にするミルは、持っていなくても全然問題なし。

ちなみに個人的には Hario めっちゃ使いやすいと思うけど、プロへの憧れとしては Kalita 一式を欲しいなと思うとき結構ある。特に↓このケトル…最高にかっこいい…。

Chromecast

https://www.google.com/intl/ja_jp/chromecast/tv/chromecast/

一人で色々なことをするぶんにはパソコンがあればなんでもできるけれど、コンピュータに疎い人とのコミュニケーションにパソコンはまったく役に立たない。

写真を一緒に見るためにと思って Chromecast を買ったところ、実際これが大正解で、写真はもちろんのこと youtube を見るのにも使えて、結果、お互いにスマホの画面を見ているだけみたいな時間が圧倒的に減った。

本当に期待していたのは Web ページを見ることだったんだけど、こちらは iPhone ではできないのと、 Nexus5 でもちょっと解像度が低くて使えないなとなった。

まあでも、買ってよかったと思う。

AriaProII エレキギター用ギグバッグ AGC-EG

昨年からギターの持ち運びが増えて、これまで使ってきた付属ケースがボロボロに壊れてしまった。仕方ないから持ち運び用にケースを検討したら、これが「ダサい」か「高い」かの二択になっているもんで、とても困ってしまった。NAZCAとか二万くらいするし、そうでなければ5000円くらいだけど使いにくい。

そんな中でずっと探していてこれいいなとなったのがこの Aria 社のケース。実店舗で8000円程度で売っているのに、内容的にはすごくいい。外見も気に入ったので個人的には2016年でいちばんいい買い物のひとつになった。

いいところはこんな感じ:

  • 運びやすい
    • 背中と腰にぴったり合う形でクッションがついている
    • 肩紐が適度に太くて背負いやすい。こちらもクッション付き
  • ポケットが多彩
    • シールドとエフェクターチューナーとカポと…と全部入る。このバッグだけ持っていけばスタジオ練習できる
    • 楽譜すら入れられる大きなポケット
    • 大きさの違うポケットがそれぞれあるので物を入れやすい
  • ギターへの負荷も小さい
    • 周り全体に7mmくらいのクッションがある
    • ネックをマジックテープで留められる
    • 12フレットくらいのところに肩の根元が来る(ので背負っていてネックに過剰な負荷がかからない)
  • 防水
    • 雨でもまったく問題なし

割とマジな話、ソフト(セミハード)ケースの1万円未満部門とかあったら優勝間違いないし、2万円未満部門でもかなり戦えるレベルだと思う。

8インチタブレット

スマホで漫画を読むようになってから常々、5インチのスマホではなくもっと大きな画面で見たいと思っていた。で色々電気屋で試したところ、どうも私は7インチだと中身に入り込めず、9インチは最高だと感じるものの重いのだということがわかった。必要なのは、その中間の8インチだった。

で、何種類もの機種を検討した結果、さほど解像度はよいわけではないが軽くて安い ASUS の 8 インチ Android タブレットを買った。

結果、漫画を紙媒体で買わなくなった。技術書もこの端末で読むようになった。

今となってはもう自分にとっては電子書籍はあって当たり前な存在だけど、一年前はそうでもなかった。それを変えたのは圧倒的に、この8インチタブレットだった。

自転車

自転車、十年くらい乗ってなかったんだけど、久しぶりに手に入れてみたらびっくり。うちから徒歩15〜30分くらいのところにある街々にシュッと行けるようになったし、10分くらいかかっていたスーパーに夜中でも行こうという気になれる。まさに生活が変わった。

…とまあ私にとってはライフチェンジングだったわけだけど、読んでる人からすれば、買ってよかったものに自転車って言われてもねえ、ということになるんじゃないかな。そういう人はあきらめてください。所詮その程度の日記です。

2016-03-08 なんでもいいから書こう このエントリーを含むブックマーク

iPhoneをやめた。

iPhoneを使っていた、月額6000円くらいのつもりだったけど月によって9000円くらいになることもざらにあってちょっともう耐えられないぞという気持ちになったので電話用端末と通信用端末の二台持ち+Walkman A10に移行した。月額は2000円以内になった。

で、何か月か利用してみて、色々と生活が変わった。

まず、音楽を聴かなくなった。

iPhoneの頃、通勤途中にイヤホンをして音楽を聴くことが多かったのだけど、それだとiPhoneでゲームをすることができなかったのが不満だった。しかしいざ Android + Walkman にしてみると、端末を二台取り出すのが圧倒的に億劫。Walkman はポケットに入れておくにしても、ずっと入れておくわけにもいかない。

またiPhoneの頃は夜中に仕事中しているときにもよく音楽を聴いていて、しかし聴きすぎると充電が切れてしまうのも悩みだった。Walkmanなら充電も気にならないぞ、となるかというとそうでもなく、Walkmanはケーブルが専用のやつなので一本しか持っていなく、充電とデータ転送の片方しか一度にできない。まあ正確にはデータ転送時にも充電されるけど、夜中にパソコンつけっぱにするのは無駄だし。

それと、ケーブルのこともあって、新曲を入れるのが本当に面倒。まあケーブルさえあればiPhoneとそう変わらない(あるいは少ないくらいの)労力でもって曲を入れられるのだけど、ケーブルが専用というそれだけで圧倒的に使い勝手が悪い。

そしてネットで買えない。買えないわけではないけど、買ってすぐに聴けない。これがいちばん大きい。その時その時に頭に浮かんだあの曲を買うぞと思っても買えない。そうするともう聞く気が失せてしまう。とても残念。

電話にあまり気づかなくなった。

生活に支障はない。LINE時代の今、電話はLINEで済む。メールを受け取ることもまれだし、古典的なメールしか使えない人に対してはGmailアドレスを取得している。これでもう困っていない。

充電をしょっちゅうするようになった。

いやこれは Nexus5 の電池の持ちが悪すぎるだけなのだけど、一日二回充電しないと絶対切れてしまうようになった。

iPhone時代はその3倍くらいは持ったし、かなり酷使していたという認識もある。やはりこのあたりは iPhone が圧倒的に良かった。

まあそれくらいか。ほとんどかわってないな。音楽を聴かなくなったことと、料金が安くなったことくらい。

まあまあ、悪くはない。悪くはないんだ。

2015-06-14 Truly Ergonomic Keyboard

Truly Ergonomic Keyboard (JIS配列) 使い始めた

職場で Mac OS X を使うようになり、また腱鞘炎にもなっていたところだったので Truly Ergonomic Keyboard を買うことにした。…のが4月になりたての頃。

購入して早速 Mac にぶっ刺してみたところ、どうも動かないキーが多くて困った。裏側にあるDIPスイッチを Mac 向けとか日本語系とかにしてみても、真ん中の Del が Backspace だったり Del と Backspace との区別が(動作上)なかったり、Tabが遠かったりパイプがどこだかわからなかったり、何より「半角全角」キーがないせいで日本語入力ができなかったりして、実質的には使えたもんじゃない状態だった。

幸いにしてこれはファームウェア書き換えをすることによってキー配置を自在に変更できるようになっており、その辺の問題は無事解決できそうに思えた、が、肝心のファームウェア入れ替えはWindowsでしかできないのだった。(発売当初。今はmacでもできるので心配ない)

ということで持ち帰って、ついでに書き換えるのならしばらく Mac でのキー入力を試してみてそれに合った入力方法を選択するのhttp://d.hatena.ne.jp/suu-g/edit#がよいのではないかということになり、そのまま放置されることとなった。ありがちだ。

で、しばらく使ってみるとまあ Macキーボードってそんなに押しやすいわけでもないし本体熱くなるしでだんだん別キーボードを使いたい気持ちがふつふつと盛り上がってきて、今回ようやく重い腰を上げて入れ替えをしたってわけ。

suu-g: Mac向けカスタマイズ

今回は上記の構成にしてみた。しばらく使ってみて、また考えたいところ。

未だに悩んでいるのは半角全角をどうするかで、スペースキーはわりとッターンしたいから親指に置きたいけれど、カタカナ・ひらがなと無変換キーも親指で押したいので、これはどうしたものか。スペースキー基本的に左手で打っているようなので、変換(全角半角)キーを右スペースにするのでいいかも知れない。

そのへん直したら適当に追記か書き換えしていくつもり。

=> さっそく右スペースを全角半角にしたバージョン。ど真ん中の※マークにはパイプをおいた。もったいない気もするけどいちばん使うのシェルだし…。

=> さらに更新。パイプ周りとかを修正。アンダースコアが若干遠いことが今の難点だけどさすがにキーが足りないのでしゃーなし。あとは意外とスペースキー右側も使いたいときあるらしい。なんか気分次第で右も使ってた。それと Mac キーは頻繁に使うので左下に配置。Altはあまり使わないから諦めて左上にした。

2014-01-13 gem 作成中

rbish gem 作成中

erb を組み入れたシェルスクリプトを実行する gem を作成している。とりあえず公開だけはした。

https://rubygems.org/gems/rbish

読みは rubbish と同じ【rʌ'biʃ】。

erb で書かれたシェルスクリプトを動かすだけなら下にある一行くらいでもどうにかなるので、ほとんどの労力はコマンドとして整えるところに行ってることになるかな。

`#{ERB.new(File.read(ARGV[0])).result}` 

今回は thor ではなく mixlib シリーズを使用してコマンドラインを立ててみてる。Thor はサブコマンドを作るときは最高の選択肢のひとつだけど、UNIX style のコマンドを作成するには最適ではない感じがしたので、ほかの諸々を調査してた。で、いくつかの候補の中から mixlib-cli を選択してみた、というわけ。

mixlib には config や log などもあるので、それらと一緒に一通り使ってみて、それから判断しようと思ってる。

現在のバージョンは 0.1.0 ってことにしてる。Semantic Versioning 2.0 によると、公開時は 1.0.0 にしろってことなんだけど、今のものにその数字を背負わせる自信はない。

まずはこちらで認識してる、エラー表示とかテストとかデバッグ出力とか、そのあたり直したらもうちょっとバージョンを上げようかなと思う。

2013-09-08

bundle install を早くする唯四の方法

皆さん、bundler 使用していますか?(はーい!という声)そうですね、とても便利ですね。でも遅い。何より速さが足りない。そう思うことも時折あるのではないでしょうか。bundle install に数分間かかるのはよくある話、ときによっては10分以上も bundle install だけでかかってしまう。しかも遅いときに限って何も表示されない。壊れてる?…と思って放置してごはんを食べて戻ってくるといつの間にか終わってたりする。別に bundler 自体が悪いわけではないですが、不安になります。

「俺の bundler がこんなに遅いわけがない。もっともっと速くなればいいのに」、そう思ったあなたのためにこの記事です。bundle install 時間を短くするただ一つ、いや四つの方法をご紹介します。

rubygems のバージョンを適切にアップデートする

あまり知られてない気がしますが、gem のバージョンによっては bundle install がものすごく低速になることがあります。私が経験したところでは、 2.0.3 では 1.8.25 の 3 倍程度遅くなっていました。

$ gem --version
2.0.3
$ sudo gem update --system

バージョンを確認して 2.0.3 以下の 2.0 系だった場合、アップデートしておきましょう。2.0.7 は速度的には問題なさそうです。

https://rubygems.org ではなく http://rubygems.org を利用する

標準で利用される source 'https://rubygems.org’ よりも、 source 'http://rubygems.org’ としたほうが微妙に bundle install が早くなります。下の画像はさくらVPS にて bundle install を試したときの時間を計測したものですが、 http のときは https と比較して1.5倍近く高速化されていますね。

f:id:suu-g:20130908131511p:image

http proxy がはさまれている環境の場合は、さらに早くなる可能性もあります。

ただ、http にすれば当然ながら公開鍵認証による信頼の恩恵は受けられませんし、本家 bundler にて推奨しているのは https://rubygems.org です。このあたりは自己責任でプリーズ。

並列 bundle install を利用する*1

1.4.0.pre.1 より、並列 bundle install が可能になっています。これは爆速です。ぜかましです。本家 bundler の issue によれば、特に RTT が長いときに効果を発揮するとのこと。ちなみに source 行の http/https の違いは並列化でかなり吸収されますので、並列 install を利用する場合はわざわざ source 行を http に書き換える必然性は薄いです。

雑な計測*2ですが、私の環境ではこんな結果が出ました。

$ gem install bundler --version='1.4.0.pre.2'
$ time bundle install --path=.bundle/gems --binstubs=.bundle/bin
real    4m32.956s
$ rm -rf .bundle/gems .bundle/bin
$ time bundle install -j 10 --path=.bundle/gems --binstubs=.bundle/bin
real    0m39.983s

この機能の原作者は @eagletmt 先生。利用する際はありがとうを三唱しましょう。

ただ、正式版ではなく pre の機能*3 ということはお忘れなく。メモリやCPUも使いますし。これも自己責任ですね。

インストール時間を表示させる

bundler は多くの gem を扱うわけなので、そのうちのひとつの gem の install が長くなると、全体としての bundle install 時間も一気に遅くなってしまいます。

具体的には、therubyracer 0.11.0 問題とか、Nokogiri 1.6.0 問題とかですね。

こういった問題に遭遇していたら、install の遅かったやつを探す必要があるわけですが、現状の bundler だとどこが遅いのか、よくわかりません。

ところで MOGOK では bundle install の時間が表示されるのが地味に便利です*4

Bundler installing..
2013-09-08T14:26:02+09:00 console[app2002.22]:   $ bundle install --path=.bundle/gems --binstubs=.bundle/bin --without=test development
2013-09-08T14:26:02+09:00 console[app2002.22]: Fetching gem metadata from http://rubygems.org/...........
2013-09-08T14:26:09+09:00 console[app2002.22]: Fetching gem metadata from http://rubygems.org/..
2013-09-08T14:26:10+09:00 console[app2002.22]: Installing rake (10.1.0)
2013-09-08T14:26:10+09:00 console[app2002.22]: Installing i18n (0.6.5)
...
2013-09-08T14:26:16+09:00 console[app2002.22]: Installing activeresource (3.2.14)
2013-09-08T14:26:16+09:00 console[app2002.22]: Using bundler (1.1.3)
2013-09-08T14:26:16+09:00 console[app2002.22]: Installing json (1.8.0) with native extensions
2013-09-08T14:26:18+09:00 console[app2002.22]: Installing libv8 (3.16.14.3)
2013-09-08T14:26:18+09:00 console[app2002.22]: Installing rack-ssl (1.3.3)
...

これをローカルでも実施できるよう、 bundle install に時刻表示をつけてみます。なんとなくこんな感じで Gemfile にモンキーパッチを書いておけばそれっぽく表示できます。

class << Bundler.ui
  def tell_me (msg, color = nil, newline = nil)
    msg = word_wrap(msg) if newline.is_a?(Hash) && newline[:wrap]
    msg = "[#{Time.now}] " + msg if msg.length > 3
    if newline.nil?
      @shell.say(msg, color)
    else
      @shell.say(msg, color, newline)
    end
  end
end

source 'http://rubygems.org'
gem 'libv8', '~>3.11.8.17'
gem 'therubyracer'

表示がこんな具合↓になるので、ボトルネック探しがはかどりますね。

$ bundle install --path=.bundle/gems --binstubs=.bundle/bin
[2013-09-08 14:38:26 +0900] Fetching gem metadata from http://rubygems.org/..
[2013-09-08 14:38:27 +0900] Installing libv8 (3.11.8.17)
[2013-09-08 14:38:28 +0900] Installing ref (1.0.5)
[2013-09-08 14:38:46 +0900] Installing therubyracer (0.11.4)
[2013-09-08 14:38:46 +0900] Using bundler (1.4.0.pre.2)
[2013-09-08 14:38:46 +0900] Your bundle is complete!
[2013-09-08 14:38:46 +0900] It was installed into ./.bundle/gems

ただ、これは bundler の内部構造に依存したモンキーパッチなので、バージョンによっては使用できなかったり、最悪の場合は機能を壊してしまう可能性があります。これも自己責任

以上。

これ全部やれば、人によっては bundle install の時間が 1/10 くらいに縮むかもしれません。

システムワイドにインストールするとか、複数のプロジェクトで同じ BUNDLE_PATH を利用する方法もありますが、それらはあまりお勧めできないかなーと思います。bundle install は --path 指定をし、プロジェクトごとに別々の gem を使うようにしましょー。

Have a happy bundle life!

*1:1.4.0.pre.1 〜

*2:Gemfile.lock も .bundle/config も残ってる

*3:pre.2 の段階では並列のオプションが保存されないとか、pre1 では :github の互換性が崩れていたとか、まだ十分に叩かれていないかも知れないとか、画面表示が変わるとか、いろいろ

*4:ステマ

2013-06-29 突然

Nginx Proxy の下で GrowthForecast を動かした話

GrowthForecast を動かしたい欲がさきほど突然現れたので、さくらVPSUbuntuをセットアップしてGrowthForecastを設定してみた。

インストール方法はさておいて、設定するときに注意すべき点が幾つかあったので、それらについて列挙してく。

目的

127.0.0.1:5125 で立てた GrowthForecast を、同ホスト上に立てた Nginx でリバースプロキシし、 http://example.com/growthforecast という URL で見られるようにすること。

サブドメインを使いたくないようなケースね。

想定してるのは、外部に GrowthForecast のグラフを見せ、データの挿入は 127.0.0.1 以外から許さないような用途。

Nginx での基本的な Proxy 設定

とりあえず GET/HEAD だけ許容して 127.0.0.1:5125 に Proxy しとこう。

location /growthforecast {
  if ($request_method !~ ^(GET|HEAD)$) {
    return 403;
  }
  proxy_pass http://127.0.0.1:5125;
}

って思うじゃん。

ダメなんです

ところが、これだとこんな画面になって失敗する。

f:id:suu-g:20130630025744p:image

原因は、ページ内のすべてのリンクが http://127.0.0.1:5125/ を向いてしまっていること。実にロケンロー。

ただ、こんな問題を本家が認識していないはずもなく、 https://github.com/kazeburo/GrowthForecast/issues/3 には対処法が書かれている。

GrowthForecast で行うべき設定

GrowthForecast では、前段に proxy が挟まる場合にはその設定を行う必要がある。と言っても、起動オプションを一つ追加するだけだ。

 $ growthforecast.pl --front-proxy=127.0.0.1

こっちはこれで問題ない。

Nginx で行うべき設定

Nginx 側としては、次の二点を行うことになる。

  • パスの書き換え
  • ホスト名の書き換え
パスの書き換え

Nginx で特に設定を行わない場合、 http://example.com/growthforecast/ にアクセスすると、GrowthForecast 側には /growthforecast/ というパスが伝わる。ここは当然 / であることを期待しているわけなので、 Rewrite が必要となる。

  rewrite ^/growthforecast/(.+) /$1 break;
  rewrite ^/growthforecast / break;

書き換えルールくらい一行で書けそうなものだけど、なぜかうまくいかなかったので二行で無理やり解決。

ホストの通知

GrowthForecast では、HTML中にある各種リンクがすべてフルパスで指定されている。で、プロキシの裏側にいる場合は 127.0.0.1:5125 で立っているもんだから、ページ内のリンクはすべて http://127.0.0.1:5125/ に対するリンクになってしまう。

先ほどの --front-proxy オプションをつけるのは、これを解決させるためだ。

ここで指定されたアドレスからの接続であった場合、先ほどの 127.0.0.1:5125 の代わりに指定されたホスト名を使用するようになる。

つまり、 Nginx 側でも渡すホスト名を指定してやらねばならない。

  proxy_set_header Host $host/growthforecast;

この設定を入れることで、このアドレス問題が解決されるわけ。

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html" charset="utf-8">
<link rel="stylesheet" href="http://example.com/growthforecast//css/bootstrap.min.css">
<style type='text/css'>
body {

そんな感じ。

何が起きているか?

Nginx からのメッセージを netcat とかで適当にキャプチャすればよくわかる。

$ nc -l 5125
GET /growthforecast HTTP/1.0
Host: 127.0.0.1:5125
...

適当に設定した Nginx では GrowthForecast に対してこういうメッセージが来ていたところ、

$ nc -l 5125
GET / HTTP/1.0
Host: example.com/growthforecast
...

こんな HTTP Request が来るようになるわけ。

まとめ

以上まとめると、次のような感じになる。

Nginx 設定
location /growthforecast {
  if ($request_method !~ ^(GET|HEAD)$) {
    return 403;
  }
  rewrite ^/growthforecast/(.+) /$1 break;
  rewrite ^/growthforecast / break;
  proxy_pass http://127.0.0.1:5125;
  proxy_set_header Host $host/growthforecast;
}
GrowthForecast 起動オプション
 $ growthforecast.pl --front-proxy=127.0.0.1

f:id:suu-g:20130630025745p:image

オッケー☆

知っていればなんてことのない話なのだけど、ブログにまとまっているところを見かけないので、ちょっと残してみることにした。

ただ今回の設定だと URL の途中にダブルスラッシュ入ってかっこわるい。リダイレクトルールの書き方とか、もっといい設定がありそう。

2013-05-21 寿命の短い記事だけど

Ruby と exec と fd と

ruby で exec するときにソケットや fd のリークを起こさないために。主に自分用の調査結果のメモ。

結論

ruby 1.9.1 以上で exec するときは、何はなくとも :close_others をつける

ruby 2.0.0 以降ではこれを設定しなくても fd リークはしない

ruby 1.9.0 以前は人力で必死に頑張る

基本的な話

ruby の exec は基本的には POSIX execve のラッパ。execve 前後では多くのものが保持されないが、ファイルディスクリプタは基本的には残る。

デフォルトでは、ファイルディスクリプタは execve() を行った後でもオープンされたままである。 close-on-exec の印が付いているファイルディスクリプタはクローズされる。

http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/execve.2.html

ということで、fork-exec や exec を行いたい場合は fd の扱いに気を付ける必要がある。

また、exec を行う時点で open しっぱなしになっている可能性のある fd に関しては、 close-on-exec のフラグを付けておけば execve 内で close してもらえる。

ruby 1.9.3 以前では、この close-on-exec は自分で設定する必要がある。

f = File.open("/tmp/hoge")
f.close_on_exec = true

close-on-exec は ruby 2.0.0 以降ではデフォルトになっている*1。そのため、 exec による予期せぬ fd リークは起こりにくくなっている。

困るシーン

他の人が使用するフレームワークライブラリの中で exec を使用する場合、そのライブラリユーザが開いた fd やソケットが開きっぱなしになってしまう可能性がある。やばい。宇宙やばい。マルチスレッドならさらにこのような問題が起こりやすくなる。

ユーザの知恵はそんなものを乗り越えられるかというとそんなことはなくて、ならば今すぐ ruby 1.9.3 ユーザ全員の IO::open に close_on_exec = true を授けて見せろと言われてもそうだよそれはできないから、ruby 2.0.0 になってからそうさせてもらうと言うことになった。

では 1.9.3 ではどうすればよいかというと、 Kernel#exec (及び #spawn) に :close_others というオプションがあり、stdin/stdout/stderr 以外の fd をすべて調べて閉じてくれる仕組みがある。しかしこれは単に ruby 処理系にて C で書かれているだけなので、どうやら標準ではなさそうだ。少なくとも execve にはそのようなオプションはない。

f = File.open("/tmp/hoge")
exec("ls /proc/$$/fd", :close_others = true)

1.9.2 1.9.0 以前は close_others の実装がない(1.9.1 より実装された)。そのため、close_others オプションによる fd リーク防止は効果を持たない。close_othersはつけておいても特に害はない。

2.0.0 以降は、 fd ごとに close_on_exec が設定され、exec 時の close を保証してくれる。したがって、意図的に fd を残したい場合を除いては自動的に fd は exec 時に close されることとなり、 :close_others の設定は不要である。とはいえ、close_others を付けていてもそう問題は起きないだろうし、つけておいても問題はない。むしろ、ユーザの不注意でつけられた close_on_exec = false な fd も閉じてくれるので、使い勝手はよいかもしれないくらいだ。

そういうわけで結論(再掲)は

ruby 1.9.1 以上で exec するときは、何はなくとも :close_others をつける

ruby 2.0.0 以降ではこれを設定しなくても fd リークはしない

ruby 1.9.0 以前は人力で必死に頑張る

ということになった。まあ 1.9.0 以前って普通は 1.8.7 以前のことだけど。

補足

むしろ、ユーザの不注意でつけられた close_on_exec = false な fd も閉じてくれるので、使い勝手はよいかもしれないくらいだ。

とさっき書いた。「fd を勝手に閉じられたら困る、という話があるのでは??」と思うかもしれないが…、

本来、Kernel#exec 前後で開いたままにするべき fd は、exec (spawn) のオプションとして明示的に渡しているはずなので、それ以外の fd はすべて閉じるべき。

# たとえば fd 7 番を残したいのであれば
exec("ls -ls", 7 => 7, :close_others => true)

また、close_on_exec は IO::open 時のオプションに設定できないので、ruby 1.9.3 以前では open と close_on_exec 設定とがアトミックな処理にならない。つまり、その間に exec が発生する可能性があり、fd 漏れの起こりうる箇所になる。

その点 :close_others => true なら exec 時に確実に設定できるので、fd 漏れのタイミングは生まれない。多分。

ただ、IO#close_on_exec は POSIX 標準に対するインターフェイスである一方で、:close_others => true は ruby の独自実装。なので、現実的には :close_others を付けるのが正しそうなんだけど、あんまり美しくない感はあって悔しいかもしれない。

そんな感じ。

2012-12-22

therubyracer 0.11.0 問題まとめ

Rails 3.1 以降で良く使われるようになった therubyracer ですが、最近、これを含む bundle install がやたらと時間がかかったり、あるいはそもそも失敗してしまう、という事象が多数報告されています。

解決方法

以下の三つの方法が基本的な解決方法です。

その0 (※ 2013/1/11 追記)

Gemfile にて、libv8 3.11 系を使用することを明記します*1 *2

gem 'therubyracer', '0.11.1'
gem 'libv8', '~> 3.11.8.13'
その1

Gemfile にて、 therubyracer のバージョンを 0.11.0beta8 に戻します

gem 'therubyracer', '0.11.0beta8'
gem 'libv8'
その2

Gemfile にて、 therubyracer のバージョンを 0.10.2 に戻します

gem 'therubyracer', '0.10.2'
その3

Gemfile にて、 libv8 のバージョンを 3.11.8.3 に戻します

gem 'therubyracer'
gem 'libv8', '3.11.8.3'

これらのうちどれでも解決しない場合は、もともと therubyracer がうまく動かない環境ですので、新しいバージョンが出るのを待つか、あるいは自前で v8 のエンジンを導入してください。

なお、環境ごとの therubyracer および libv8 のバイナリ提供バージョンを確認されたい場合、 https://github.com/cowboyd/libv8/issues/62 こちらの表が便利です。

何が起きているの?

therubyracer は、JavaScript のエンジンである v8 *3Ruby から使えるようにする gem です。therubyracer では、v8 エンジンがシステム上で利用できる場合はそのエンジンを、また利用できない場合には libv8 という gem を取得して、各環境ごとに利用できるバイナリv8 エンジンをこの libv8 gem から取得するようになっていました。賢いです。

ところがこの「バイナリv8 エンジンを利用できる」という前提が、 0.11.0 に入って崩されてしまいました。therubyracer 0.11.0 が依存している最新の libv8 3.11.8.4 は、 v8 エンジンをソースコードからコンパイルしようとします。ソースコードからコンパイルすると、環境次第ですが、2分〜15分程度かかったという報告もありますし、そもそも環境が整っていないとコンパイル自体失敗します。

therubyracer 0.10 系では何も考えずに(ものの3秒で)インストールできていたものが、どうしてこんなことに…なってしまったのでしょーか。

どうしてこうなった

therubyracer の作者 @cowboyd は以前より、とある問題に頭を悩ませていました。

「therubyracer は特定のバージョンの libv8 のバイナリを入れることを強制してしまっている…。これは多くの環境では上手くいくけれど、このバイナリが動かないような環境では therubyracer を使う方法がなくなってしまう」

この問題への対応として彼は、0.11系でバージョン依存性を弱くしようとしました。また、新しいバージョンの libv8 へと依存するよう関係を変更しました。

ところがこの新しい libv8 が作成するのに失敗しており、バイナリが壊れている状態でリリースされてしまいました*4。この結果 therubyracer はあまりに多くの環境で動作しなくなってしまいました。

そして彼は、緊急でとある方策をとりました:

 

「とりあえずソースコードからコンパイルさせればいっか!」

 

い い わ け な い

 

先ほども言いましたが、v8コンパイルは、要はひとつの言語処理系コンパイルなのでそれなりに時間がかかります。Rails3.1 初期状態の bundle install はおおよそ1分くらいですが、libv8のコンパイルにはそれこそ10分とるわけなので、この影響は甚大でした。

@cowboyd にとっては、バージョン依存による環境縛りを外せるという意味で、若干いけてないけれどもまあ意味のあるワークアラウンドを出したなーと、この時点では思っていたように思います。

しかし、多くの利用者が欲しているのは「何も考えなくても入っていた therubyracer という gem」でした。

そのことを指摘した発言が出て、 @cowboyd はそのことの価値を明確に意識するようになります。

I'm very curious how 0.10.x versions of therubyracer managed to "just work" on nearly everyone's system (I never encountered or heard of anyone encountering a problem)(中略), and what changed in 0.11.x to make this no longer work. I am very familiar with "unix dependency hell"(中略) -- but therubyracer prior to 0.11.0 managed to avoid this and be an awesomely great install experience which just worked for everyone.

issues/215 @jrochkind

You make a valid point, but since this is the first positive feedback about the old system I have received , I guess I didn't really realize its value up until now. (and thank you by the way)

issues/215 @cowboyd

誰からのお礼やフィードバックがなかったがゆえ、彼は、自分の作った therubyracer が「簡単にインストールできる」ということがいかに大きな価値を持っていたか十分に認識していなかったのですね。応援とフィードバックは大事ですね。だれか私も応援してください。わぁい。

therubyracer の問題

以上の問題をまとめると、次の三つの要素が絡み合っていることになります。

  • gem 'therubyracer' としたら何も考えなくてもインストール成功してほしい
  • libv8 はバイナリで入ってほしい
  • バイナリバージョンが上手くいかない人のために、v8 エンジンを選択的に利用できてほしい
gem 'therubyracer' としたら何も考えなくてもインストール成功してほしい

今回はバージョンアップに伴って therubyracer の依存性が壊れたわけですが、ユーザは以前のバージョンを指定することで回避は可能でした。ただ、ライブラリ提供者としては、そういった回避手段をユーザ誰もに期待することはできません。特に therubyracer は、Rails 3.1 以降では標準的に使用され、多くの初心者が利用するライブラリですからなおさらです。

と言うわけで、gem 'therubyracer' その一行だけで全てが問題なく動くということは、とても大きな価値なのでした。

libv8 はバイナリで入ってほしい

これは言わずもがなですね。バイナリバージョンが入ってくれないと、数分〜十分以上の待ち時間が出てきます。しかも、このインストールデプロイのたびに発生します。これはちょっとした恐怖ですよ。バイナリバージョンの libv8 は therubyracer では今や必須です。

バイナリバージョンが上手くいかない人のために、v8 エンジンを選択的に利用できてほしい

@cowboyd が大胆な変更をして実現しようとしていたのがこちらの機能です。似たようなケースとして、セキュリティリスクに敏感なため、その libv8 のバージョンは利用できない、という人がいるということもあるようです。ともあれ、ここで達成したい目的は、libv8 のバージョン指定をより自由にしたい、と言うことです。そしてそれは、一つ目の「gem 'therubyracer' が成功してほしい」という願いとわりと衝突しやすいものです。

この三つの問題をどうまとめて解決策を出すか、それを考えるのに @cowboyd は二週間悩みました。そして12/18にとうとう、今後の解決策を提案したのです。

解決策、その後

@cowboyd は、次の解決策を示しました。

  • libv8 と therubyracer とが互いに依存するようにする
  • libv8 は v8 への proxy としてふるまう
  • ソースバージョンとバイナリバージョン、両方をリリースする

前半二つは実装の話なので省略、最後の一つが面白いのでご紹介します。

今後の libv8 のリリースでは、「最新バージョンは常に binary 入り、そのひとつ前に source 版の libv8 を準備する」というリリース方針になるようです。

3.11.8.4 -> source
3.11.8.5 -> binary

こんな感じです。バイナリ版の libv8 がうまく動かないようだったら、バージョンを一つ戻してソース版をインストールすればいい、そういう設計になる予定のようです。

この変更はそれほど時間がかからない予定だそうですが、本家のコミットに動きが一切ないのでちょっと心配です。

ともあれ、今後も therubyracer は「インストールは簡単で、サクサク動くよ便利!」という方向は持ち続けたままメンテナンスされていくようなので、一安心です。私は次のリリースが来るまでは素直に therubyracer 0.10.2 を使い続けることにします。

ひとこと

問題は、 gem update とか bundle install で最新バージョンを無理やり取ってきてしまうとか、バージョン番号が四ケタになっているやつは '~>' でのバージョン指定でマイナーバージョンが上がってしまうとか、そういうところな気もしますけど、かといって私には、バージョン全指定以外に確実な方法なんて思い浮かばないので、この rubygems 式指定法とどう付き合っていくかなんでしょうね。

まとめ

がんばれ。

 

あとソフトウェア製作者への応援大事。

 

参考URL

https://github.com/cowboyd/therubyracer/issues/215

https://github.com/cowboyd/libv8/issues/62


追記

あ、応援ブクマありがとうございます、普段がいかにもオメガブロガーなもので初の10ブクマ越えになりました。

わぁいブクマ suu-g ブクマ大好き

追記2 (2013/09/08)

therubyracer 0.12.0 でも同じ問題があるとのブクマコメを頂きました。

0.12.0 では、therubyracer は libv8 3.16.14 系に依存するように変更が加えられました。いまの最新の 3.16.14.3 は x86_64-darwin-12 および x86_64-linux に対するバイナリパッケージが提供されていますので、これらの環境では libv8 のビルドは行われずに済みそうです。言い方を変えると、それ以外の環境、たとえば mingwdarwin-10、32bit Linux といった環境では、バイナリパッケージが入らずに困る可能性があります。

*1:これを書かないと、未対応なのに投入された 3.15 系が入るかもしれません

*2:以前に何度か bundle install を試している場合は一度 bundle clean を行った方がよいです

*3http://code.google.com/p/v8/ V8 JavaScript Engine

*4:issues/215 を読む限り。多分

2012-11-21 インターネット記念日

yamlにRubyのクラス名を入れて設定ファイルとして利用する

Rubyで設定ファイルを作るとき、yamlファイルを利用することがある。そのyamlファイルの中に、ユーザ定義のクラス名を入れて、設定ファイルによって利用するクラスを変更するようなことをしたい。と思った時のためのやり方。

まあ、yamlクラスにあるんですけど。

yaml では、Ruby 向けに以下のローカルタグを扱えます。

・ !ruby/class: Class オブジェクト

http://doc.ruby-lang.org/ja/1.9.3/library/yaml.html

「この記法Ruby独自の方法だろうし、利用するエンジンによって実装が異なるだろうし使いたくないな!」と思っていたのだけど、調べてみるとこの !〜〜 という書き方はYAML1.1以降の正式なユーザ定義タグの仕様に則ってた。

http://www.yaml.org/spec/1.2/spec.html

ただ、この記法を使う場合、yamlファイルをパーズする時点でこのタグに相当するクラスが定義されていないとエラーが起こってしまうので、不便なときがある。

それと、Ruby以外で読めないyamlファイルになってしまう、という問題もないではない。

yamlから文字列として取得して、それをクラスにする

yamlファイルにはクラス名を文字列として格納しておいて、利用するときに改めてクラス名として展開する、というのが平和な解決法に思える。

文字列をクラスにする方法はググればそれなりに見つかる。大きく分けると二つ。

  • eval する
  • const_get する

evalは、与えられた文字列がクラス名だったら良いけれど、yamlファイルに "`sudo rm -rf ~/*`" とか書かれてるだけで死ぬので使えない。

const_getなら使えそうに見えるけど、階層が深くなると…

irb(main):001:0> Object.const_get("Hash")
=> Hash
irb(main):002:0> require 'net/http'
=> true
irb(main):003:0> Net::HTTP
=> Net::HTTP
irb(main):004:0> Object.const_get("Net::HTTP")
NameError: wrong constant name Net::HTTP
        from (irb):4:in `const_get'
        from (irb):4
        from :0

とまあ、こうなる。

Object.const_getが行っているのは文字通り、クラス名という定数(const)をObjectから取り出しているだけ。Objectの中には "Net::HTTP" という定数はないので、失敗する。というからくり。

でも、Object直下に "Net" 自体はあるので、

irb(main):005:0> Object.const_get("Net")
=> Net
irb(main):006:0> Object.const_get("Net").const_get("HTTP")
=> Net::HTTP

こうやって辿っていくことで、Net::HTTPを取得することができる。

したがって、任意のクラス名をクラスとして解釈するワンライナーは、例えばこう。

"Net::HTTP".split("::").reduce(Object, :const_get)
=> Net::HTTP

わりと常用しそうなテクニックなのに、誰も書いてない気がしたので記事化してみた。

reduceよりinjectが好きならそちらでどうぞ。

2012-11-16 冬がはじまるよ

rbenvを全ユーザ用にインストールする

システムで利用するrubyのバージョン管理にaptyumは使いたくないので、rbenvでやってしまいましょう。という話。


rbenv のインストール

インストール場所は /opt/ruby 以下。別に/usr/localでも問題ないけど、私の管理ポリシ的にそうしてる。

# mkdir -p /opt/ruby
# cd /opt/ruby
# git clone git://github.com/sstephenson/rbenv.git

/etc/profileに追記する、のは緊急時の影響が大きいので、rbenv環境変数系はまとめて1ファイルにしてユーザごとにsourceさせるように変更するとよい気がする。/etc/profile内でevalしたくはないよね。

# cat >> /etc/profile

RBENV_ROOT=/opt/ruby/rbenv
export RBENV_ROOT

PATH=/opt/ruby/rbenv/bin:${PATH}
eval "$(rbenv init -)"
^D

ここまでは、まあ、普通。


ruby-build のインストール

ruby-buildは便利なのだけど、rubyのバージョンごとに設定ファイルが作られていて、新しいバージョンのrubyを入れようと思ったらこのruby-buildごとインストールしなおさなくてはいけなくて、残念。

もういっそinstallなんてしないでも、git pull してくるだけで新バージョンの情報が手に入るようにしてみた。

# cd /opt/ruby
# git clone git://github.com/sstephenson/ruby-build.git
# find /opt/ruby/ruby-build/bin/* | xargs -I{} ln -s {} /opt/bin
# cat >> /etc/profile

PATH=${PATH}:/opt/bin
^D

インストール手順は以上で終わり。よくできているので、間違えなければやるべきことは少ない。


rubyをinstallする

うちの子の前準備。うちの子gcc入ってなかった。悲しかった。うちの子の名前何がいいですかね。いまのところ「はるだんじ」なんですけど。嘘ですけど。

# apt-get update; apt-get install -y build-essential automake autoconf cmake
# source /etc/profile

rubyを入れる。インストール可能なバージョンを表示するためには rbenv install -l と、知らないうちにオプションが必要になってた。

# rbenv install -l
...
# rbenv install 1.9.3-p327
# rbenv rehash
# rbenv global 1.9.3-p327

アップデートしたいとき

新しいrubyのバージョンが出て、アップデートしたいと思ったとき、通常であれば

$ rbenv install -l
...                                                           <== …あれ?新しいrubyバージョンがない
...                                                               rbenvをアップデートしなきゃ!
...                                                               rbenvじゃなくてruby-buildの方…?
$ cd /tmp
$ git clone https://github.com/sstephenson/ruby-build.git     <== このURLを知るためにググる
$ cd ruby-build
$ sudo ./install.sh                                           <== 一瞬で終わって不安…
$ rbenv install -l                                            <== あ、出たでた。良かった…

という感じの行動をとるので非常にやる気を削がれる。しかもインストールできてもそれまで入れたgemが引き継がれてなくて二度ガッカリする。*1

今回の記事で作った環境であれば、

$ cd /opt/ruby/ruby-build
$ sudo git pull
$ rbenv install -l

で新しいrubyバージョンの定義ファイルが入るので、時間とやる気を節約できる。


リンク。 http://yatmsu.hatenablog.com/entry/20120413/1334300058

*1gem引継ぎがないことは利点にもなるので、この記事では対処してない