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



```ruby: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を作成してるっぽい。



```ruby: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はこれ。

```ruby: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
```

```ruby: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に追加する部分。

```ruby: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)`ってなんぞ？

```ruby: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で作成されてた。

```ruby: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`も一応見てみる。

```ruby: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みてみる。

```ruby: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
```
正直読むのきついから次いく

```ruby: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を見てみる。


```ruby: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を見ればいい。

つまり


```ruby: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)`が呼び出しているのが、

```ruby: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を見ればいいのでは。

```ruby: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してる。

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

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

```ruby: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の中

```ruby: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
```

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