dalliはfork safeか?
ふと、dalliがunicornなどのmulti processで動作するアプリケーションで動かしたときにコネクションを張りなおす処理をしないといけないか気になったので、調べてみた。
これを実行してみると、
true 1 I, [2014-01-07T00:27:41.396287 #9290] INFO -- : localhost:11211 failed (count: 0) I, [2014-01-07T00:27:41.396622 #9291] INFO -- : localhost:11211 failed (count: 0) I, [2014-01-07T00:27:41.396905 #9292] INFO -- : localhost:11211 failed (count: 0) I, [2014-01-07T00:27:41.397327 #9293] INFO -- : localhost:11211 failed (count: 0) I, [2014-01-07T00:27:41.397726 #9294] INFO -- : localhost:11211 failed (count: 0) I, [2014-01-07T00:27:41.398108 #9295] INFO -- : localhost:11211 failed (count: 0) I, [2014-01-07T00:27:41.398495 #9296] INFO -- : localhost:11211 failed (count: 0) I, [2014-01-07T00:27:41.399108 #9297] INFO -- : localhost:11211 failed (count: 0) I, [2014-01-07T00:27:41.399440 #9298] INFO -- : localhost:11211 failed (count: 0) I, [2014-01-07T00:27:41.399961 #9299] INFO -- : localhost:11211 failed (count: 0) [9290, 0, 1] [9292, 0, 1] [9291, 0, 1] [9290, 1, 1] [9293, 0, 1]
こんな感じになり、forkした直後のログで失敗した、というエラーメッセージが出るが、その後、再接続し、通信できているようだ。
dalliのソースコード(acb1ff3afd4d)をpidでgrepすると、
# lib/dalli/server.rb def verify_state failure!(RuntimeError.new('Already writing to socket')) if @inprogress failure!(RuntimeError.new('Cannot share client between multiple processes')) if @pid && @pid != Process.pid end
という処理が出てきて、newしたときと、pidが変わっているかをチェックしていることがわかる。
このverify_stateはクライアントがリクエストしようとしたときには必ず通過するようになっているので、これでpidの変更を検出しているようだ。
failureの中では、
def failure!(exception) message = "#{hostname}:#{port} failed (count: #{@fail_count}) #{exception.class}: #{exception.message}" Dalli.logger.info { message } @fail_count += 1 if @fail_count >= options[:socket_max_failures] down! else close sleep(options[:socket_failure_delay]) if options[:socket_failure_delay] raise Dalli::NetworkError, "Socket operation failed, retrying..." end end
のようになっていて、失敗回数を保持するカウンタを1あげつつ、例外を吐いている。
ここで発生させた例外は、
# lib/dalli/client.rb # Chokepoint method for instrumentation def perform(*all_args, &blk) return blk.call if blk op, key, *args = *all_args key = key.to_s key = validate_key(key) begin server = ring.server_for_key(key) ret = server.request(op, key, *args) ret rescue NetworkError => e Dalli.logger.debug { e.inspect } Dalli.logger.debug { "retrying request with new server" } retry end end
serverを呼びだしている元の、performメソッドで、rescueされて、retryするようだ。