LoginSignup
5
6

More than 5 years have passed since last update.

# Dalliのsession_id作成部分をざっくり読む[途中まで]

Posted at
dalli-1.1.5/lib/rack/session/dalli.rb
def get_session(env, sid)
  unless sid and session = @pool.get(sid)
    sid, session = generate_sid, {}
    unless @pool.add(sid, session)
      raise "Session collision on '#{sid.inspect}'"
    end
  end
  [sid, session]
end

ここでセッションIDを作成してるっぽい。
unless sid and session = @pool.get(sid)ここでsidの有無を確認していて、存在しなかった場合はgenerate_sidを呼び出してsidを作成してるっぽい。

dalli-1.1.5/lib/rack/session/dalli.rb
def generate_sid
  loop do
    sid = super
    break sid unless @pool.get(sid)
  end
end

superで作成されたsidに対して@pool.get(sid)でsidをkeyにしてmemcachedに格納してるっぽい。
重複やエラーがなくなるまでループしてる・・・?

ちなみにsuperはこれ。

rack-1.3.5/lib/rack/session/abstract/id.rb
def generate_sid(secure = @sid_secure)
  if secure
    SecureRandom.hex(@sid_length)
  else
    "%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1)
  end
rescue NotImplementedError
  generate_sid(false)
end
dalli-1.1.5/lib/dalli/client.rb
def get(key, options=nil)
  resp = perform(:get, key)
  (!resp || resp == 'Not found') ? nil : resp
end

perform(:get, key)ではkeyの形式をチェックしたり、memcachedに追加する部分。

dalli-1.1.5/lib/dalli/client.rb
def perform(op, key, *args)
  key = key.to_s
  validate_key(key)
  key = key_with_namespace(key)
  begin
    server = ring.server_for_key(key)
    server.request(op, key, *args)
  rescue NetworkError => e
    Dalli.logger.debug { e.message }
    Dalli.logger.debug { "retrying request with new server" }
    retry
  end
end

更にring.server_for_keyを読んでみる

ring.server_for_key(key)ってなんぞ?

dalli-1.1.5/lib/dalli/client.rb
def ring
    @ring ||= Dalli::Ring.new(
        Array(@servers).map do |s|
        Dalli::Server.new(s, @options)
    end, @options
    )
end 

Dalli::Ring.newも読むとして、まずはArray(@servers).mapが気になる。
@serverは、initializeで作成されてた。

dalli-1.1.5/lib/dalli/client.rb
def initialize(servers=nil, options={}) 
  @servers = env_servers || servers || '127.0.0.1:11211' 
  @options = { :expires_in => 0 }.merge(options) 
  self.extend(Dalli::Client::MemcacheClientCompatibility) if Dalli::Client.compatibility_mode 
  @ring = nil 
end 

大体想像はつくけどenv_serversも一応見てみる。

dalli-1.1.5/lib/dalli/client.rb
def env_servers
  ENV['MEMCACHE_SERVERS'] ? ENV['MEMCACHE_SERVERS'].split(',') : nil
end

設定ファイルを見ているみたい。無かったら引数で受け取ったserversを使うしそれも無かった場合はデフォルトの'127.0.0.1:11211'を使用する、ということみたい。
Dalli::Ring.newみてみる。

dalli-1.1.5/lib/dalli/ring.rb
def initialize(servers, options)
  @servers = servers
  @continuum = nil
  if servers.size > 1
    total_weight = servers.inject(0) { |memo, srv| memo + srv.weight }
    continuum = []
    servers.each do |server|
      entry_count_for(server, servers.size, total_weight).times do |idx|
        hash = Digest::SHA1.hexdigest("#{server.hostname}:#{server.port}:#{idx}")
        value = Integer("0x#{hash[0..7]}")
        continuum << Dalli::Ring::Entry.new(value, server)
      end
    end
    @continuum = continuum.sort { |a, b| a.value <=> b.value }
  end

  threadsafe! unless options[:threadsafe] == false
  @failover = options[:failover] != false
end

正直読むのきついから次いく

dalli-1.1.5/lib/dalli/ring.rb
def server_for_key(key)
  if @continuum
    hkey = hash_for(key)
    20.times do |try|
      entryidx = self.class.binary_search(@continuum, hkey)
      server = @continuum[entryidx].server
      return server if server.alive?
      break unless @failover
      hkey = hash_for("#{try}#{key}")
    end
  else
    server = @servers.first
    return server if server && server.alive?
  end

  raise Dalli::RingError, "No server available"
end

return server if server.alive?これとreturn server if server && server.alive?これが返り値になってるけど、結局はserverとはDalli::Server classのインスタンスなのでDalli::Server#requestを見てみる。

dalli-1.1.5/lib/dalli/server.rb
def request(op, *args)
  raise Dalli::NetworkError, "#{hostname}:#{port} is down: #{@error} #{@msg}" unless alive?
  begin
    send(op, *args)
  rescue Dalli::NetworkError
    raise
  rescue Dalli::MarshalError => ex
    Dalli.logger.error "Marshalling error for key '#{args.first}': #{ex.message}"
    Dalli.logger.error "You are trying to cache a Ruby object which cannot be serialized to memcached."
    Dalli.logger.error ex.backtrace.join("\n\t")
    false
  rescue Dalli::DalliError
    raise
  rescue => ex
    Dalli.logger.error "Unexpected exception in Dalli: #{ex.class.name}: #{ex.message}"
    Dalli.logger.error "This is a bug in Dalli, please enter an issue in Github if it does not already exist."
    Dalli.logger.error ex.backtrace.join("\n\t")
    down!
  end
end

rescueしまくってるけど気にしない。要するにsend(op, *args)これ。
Object#sendを呼び出してるので、opってなんだよという話。
ring.server_for_keyで帰ってきたserverに対してserver.request(op, key, *args)と呼んでいるので、perform(op, key, *args)このopを見ればいい。

つまり

dalli-1.1.5/lib/rack/session/dalli.rb
def generate_sid
  loop do
    sid = super
    break sid unless @pool.get(sid)
  end
end

@pool.get(sid)が呼び出しているのが、

lib/dalli/client.rb
def get(key, options=nil)
  resp = perform(:get, key)
  (!resp || resp == 'Not found') ? nil : resp
end

なのでopの正体は:get
つまりDalli::Server#getを見ればいいのでは。

dalli-1.1.5/lib/dalli/server.rb
def get(key)
  req = [REQUEST, OPCODES[:get], key.bytesize, 0, 0, 0, key.bytesize, 0, 0, key].pack(FORMAT[:get])
  write(req)
  generic_response(true)
end

writeしてる。

dalli-1.1.5/lib/dalli/server.rb
def write(bytes)
  begin
    @sock.write(bytes)
  rescue SystemCallError, Timeout::Error
    failure!
    retry
  end
end

@sockring.server_for_keyが呼ばれた時に、server.alive?が呼ばれて、その中で生成されている。

dalli-1.1.5/lib/dalli/server.rb
def alive?
  return true if @sock

  if @last_down_at && @last_down_at + options[:down_retry_delay] >= Time.now
    time = @last_down_at + options[:down_retry_delay] - Time.now
    Dalli.logger.debug { "down_retry_delay not reached for #{hostname}:#{port} (%.3f seconds left)" % time }
    return false
  end

  connect
  !!@sock
rescue Dalli::NetworkError
  false
end

このconnectの中

dalli-1.1.5/lib/dalli/server.rb
def connect
  Dalli.logger.debug { "Dalli::Server#connect #{hostname}:#{port}" }

  begin
    if @hostname =~ /^\//
      @sock = USocket.new(hostname)
    elsif options[:async]
      raise Dalli::DalliError, "EM support not enabled, as em-synchrony is not installed." if not defined?(AsyncSocket)
      @sock = AsyncSocket.open(hostname, port, :timeout => options[:socket_timeout])
    else
      @sock = KSocket.open(hostname, port, :timeout => options[:socket_timeout])
    end
    @version = version # trigger actual connect
    sasl_authentication if need_auth?
    up!
  rescue Dalli::DalliError # SASL auth failure
    raise
  rescue SystemCallError, Timeout::Error, EOFError, SocketError
    # SocketError = DNS resolution failure
    failure!
    retry
  end
end

ちょっとこのあたりで読むのが辛くなってきたから一旦終わる。

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6