Redisのソースコードをちょっと読んでみようという試み(1)

C言語Hello world以上のものを読むのは初めてだった。とりあえず実装の詳細は飛ばして、
サーバー立ち上げる -> クライアントから繋がる -> コマンドが実行される -> 書き込まれる までの関数の呼ばれる流れだけを追ってみた。
大分意図的に端折ってるし、意図せず抜けてたり間違ってたりすると思うから、間にうけないほうが良さそう

Doxygenというので、ドキュメントっぽいのを生成して、それも時々見てた。全然関係ないけどdoxygengui付きのオプション付けてbrewでビルドしようとしたけど、依存しているQtのビルドに時間がかかって、そのうえGUI(doxywizard)のバイナリ出来てなかった感じがして損した感じがした。

しょうがないからDoxyfile色々いじってやった。https://gist.github.com/ToQoz/5057612 途中関数のコールグラフみたいなのが出てきてる感じになったけど、色々いじってたら出てこなくなった気がする。

main
  initServer
    acceptTcpHandler(acceptUnixHandler)
      anetTcpAccept
        anetGenericAccept
          accept
                                   # クライアントから繋がれました~~~
      acceptCommonHandler
        createClient               # redisClient作って返す(この後こいつが色々持つ、コマンドとか)
          readQueryFromClient      # clientからのqueryの長さとかチェックしたりする
            processInputBuffer     # request typeとか決定したり(決まって無い時), そのチェックしたり
              processCommand       # lookupCommandでコマンド探してcallで実行する
                lookupCommand      # 実行するコマンドを探しす
                call               # コマンドを実行してる(redisClient c->cmd->proc())
                                   # procはredisCommandTableで, コマンドごとに登録されていて、
                                   # それが実行される. ↓こんな感じ

  struct redisCommand redisCommandTable[] = {
    {"get",getCommand,2,"r",0,NULL,1,1,1,0,0},
    {"set",setCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},

例えばsetコマンドだと、procは

# src/t_string.c
void setCommand(redisClient *c) {
  c->argv[2] = tryObjectEncoding(c->argv[2]);
  setGenericCommand(c,0,c->argv[1],c->argv[2],NULL,0);
}

という感じで, valueをエンコードして, setGenericCommandに client, nx(謎), key, value, expire, unit(謎) を渡している.

流れはこんな感じ。

setCommand          # 値をエンコードとか
  tryObjectEncoding # エンコードしようとしてる
  setGenericCommand # expireチェックとか
    setKey          # 実際にキーに値をセット(この関数の中より下見てない, dbAdd or dbOverrideで書き込まれてるっぽい)

とにかくredisClientというやつが, コマンドの情報とか色々持っていて、そいつに対して値をセットしたり色々しながら 最終的にそいつが持ってる情報から実行するみたいな感じだった。とりあえずメモを残そうと思って残したけど、次読もうとするのいつになるか分からない。

zsh autoload

zsh使ってたけど、あんまりちゃんと使ってなくて、zshで/path-to-users-zsh.d/(site-)?functions みたいなとこに関数置いといて、autoloadするの、単にzshrc分割してsourceするのとどう違うのか知ってなかったけど、autoloadだと実行時まで中身を読み込まないからzshの起動が遅くならない、みたいなことが書いてあって、そりゃー便利だってなった。

会話のなかでモヒカンという言葉を使うのには特になにも感じないけど、オッサンが「おれモヒカンッじゃねっしー」とか「xxさんモヒカンッスよー」とか「モッヒー、やべモヒ」とか言い合ってるの見るの苦手でとてもつらい気持ちになる。特に言いたいことなし。

もう一個面白かったやつ

irb(main):007:0> require 'benchmark'
=> true
irb(main):008:0> require 'bigdecimal'
=> true
irb(main):009:0> n = 1000
=> 1000
irb(main):011:0> Benchmark.realtime { n.times { BigDecimal('3') == true } }
=> 0.023832
irb(main):010:0>  Benchmark.realtime { n.times { BigDecimal('3').is_a?(TrueClass) } }
=> 0.001259

https://github.com/rails/rails/commit/1fd78305b5812c186d9eed9475677f90946eba5f

シンボル

フト見たRailsのコミットログ https://github.com/rails/rails/commit/5d58948fe72e3b0422790b8adc0fab7bbf9e6573

to_symからto_s使うように変えてるけど、なぜだか一瞬分からなかった。シンボルがGCに回収されないから、入力値によっていくらでもシンボル増えるようなのはダメだ。ってことだろうということで落ち着いた。他の理由は特に思いつかなかった。

WebSocketからIRC繋げるやつ作った

今日Web Audioのハッカソンというかまあそんな感じのをしてて、音のなるIRC Client(on Browser)を作りたいと思って、そのクライアントを作るのを簡単にするために、WebSocketからIRC見たり、ポストしたり出来るようにしようと思って作った。Web Audioの会なのに、Audio関係するところまでいかずに終わってしまった。

https://github.com/ToQoz/ircmad

IRC <-> WebSoket Server(ここ作った) <-> WebSocket Client

こんな感じで立ち上げる

# proxy.rb
require 'ircmad'
Ircmad.new do
  set :host, '127.0.0.1'
  set :port, 6667
  set :channel_list, [ '#channel', '#channel2' ]
  set :username, 'username'
  set :password, 'password'  # 必要であれば
  set :websocket_port, 3333
end.run!
$ ruby proxy.rb

普通にonmessageにコールバック登録しておいたら、JSONが渡ってくるし、sendでJSON送ったら投稿できる。

var socket = new WebSocket('ws://localhost:3333')

// Send
socket.send(JSON.stringify({ channel: '#channel1', body: 'yeah' }))

// Get
socket.onmessage = function(msg) { console.log(msg.data) };
// => '{"username":"ToQoz","channel":"#channel1","body":"hello world"}'

ブラウザでIRCクライアントを作るとなるとちょっと面倒そうだけど、 こんな感じでWebSocketから扱えれば、簡単に作り放題だし、好きなの作れるかなと思った。

追記

サーバからの:PINGに対する PONG ってどーしてるんすか?

とのことですが、使用しているzirconというgemがやってくれています。 https://github.com/r7kamura/zircon/blob/master/lib/zircon.rb#L69

というかそもそもIRCとのやりとりは今のところ完全に任せっきりですね。

まあそれはともかくとして、IRC Server側が落ちた時とか長時間スリープした時(?)とかに微妙な感じなの、直したいと思っております。。

追記 v0.0.3

やりとりするjsonのフィールド名変えました。

at v0.0.3

var socket = new WebSocket('ws://localhost:3333')

// Send
socket.send(JSON.stringify({ to: '#channel1', body: 'yeah', type: 'privmsg' }))
socket.send(JSON.stringify({ type: 'join', to: '#ruby'}))

// Get
socket.onmessage = function(msg) { console.log(msg.data) };
// => '{"from":"ToQoz","to":"#channel1","body":"hello world","type":"privmsg"}'

// => {"from":"ToQoz","to":"#ruby","body":null,"type":"join"}
// => {"from":"hybrid7.debian.local","to":"ToQoz","body":"@","type":"353"}
// => {"from":"hybrid7.debian.local","to":"ToQoz","body":"#ruby","type":"366"}

あとreconnect的なのとかちょこちょこ入れた。

Devise and https

devise使ってて, ログインとか登録とかcontroller で force_sslとかして, https使うようにしてた場合、 大体普通に動くけどログアウトのリンクをプロトコル付きで生成するようにしとかないと、httpなページからログアウトしようとすると、DELETE http://example.com/users/sign_out 送って、そんなページないと怒られる。

-          <%= link_to destroy_user_session_path, :method => 'delete' do %>
+          <%= link_to destroy_user_session_url(:protocol => "https"), :method => 'delete' do %>

こんな感じで良さそう

Qiita Hackathonで作ったやつ

https://github.com/ToQoz/anything-hub

Introduction of anything-hub

$ anything-hub search:rails
$ anything-hub starred:ToQoz

みたいな感じでリポジトリ検索の結果やユーザーのスター一覧をanything風のインターフェースで絞り込んで選択肢ブラウザで表示するってやつ。

キャッシュするためだけのコマンドがあるので、

$ crontab -e
05 12 * * * /Users/toqoz/.rbenv/shims/anything-hub cache:search:rails >> /tmp/anything-hub.cron.log 2>> /tmp/anything-hub.cron.error.log
05 1 * * * /Users/toqoz/.rbenv/shims/anything-hub cache:starred:ToQoz >> /tmp/anything-hub.cron.log 2>> /tmp/anything-hub.cron.error.log

みたいな感じで定期的に実行するようにしておけば、実際使うときには比較的新しいキャッシュから高速に検索できる。

普通に使う分にはユーザー名だけ~/.anything-hubrcに書いておけば良くて(書き方はREADME.mdに書いてる)、一回目パスワード聞いて、そのユーザー名とパスワードからoauth tokenとって来てキャッシュしているが、キャッシュ消しちゃったときとかまたパスワード聞くプロンプト出たりするので、cronとか非対話環境で実行する場合は~/.anything-hubrcにtokenも書いておいたほうが良いと思う。tokenを取得するには$ anything-hub tokenってコマンドで取れるので、それを書いたら良い。

検索結果をanything風に絞り込んで選択はオマケ程度で、ユーザーのスターを検索できるのが便利だと思う。公式ではstarsはリポジトリ名ユーザー名からしか検索できないが、descriptionからも絞り込めるので、昔star押したと思う、よく覚えてないファイルアップロードのgemとかも、uploadとかで絞り込める。

OSX使ってないひと、というかOSXのopenコマンドみたいなのが違う名前の人は-s オプションにそのコマンド名渡せば良い。

ドメイン駆動とかそういうの、意図してないとしても言葉の節々から、「自分たちは顧客の業務への理解を高める必要があるが、顧客はシステムを分からないのはしょうがない」のような、虐げられる側が必死で頑張る姿勢みたいなもの感じて、とても辛く悲しくなってきて、結局頭に入らない。

pryの履歴からanythingっぽい感じで絞り込む

必要. 適当にgem installすれば良いと思う

~/.pryrc に追記

require 'ruby-anything'
def anything_hist(tail = 10)
  _anything_ command_result("hist --tail #{tail}").lines.map { |h|
    h.gsub(/^ *\d+: */, '').chomp
  }
end
$ pry
pry> anything_hist(50) | -> v { puts v; command_result(v) }
>>>> 履歴からanything インターフェースで選択して実行 >>>>