Rails 2.2の threadsafe と ConnectionPool を読んでみる#3
次は ConnectionPool について.ひとまず関連部分を出すために % rak 'ConnectionPool' --ruby で
activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb 19| # connection back in. ConnectionPool is completely thread-safe, and will 21| # as long as ConnectionPool's contract is correctly followed. It will also 24| # connection anyway, then ConnectionPool will wait until some other thread 57| class ConnectionPool 60| # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification 63| # this ConnectionPool. 65| # The default ConnectionPool maximum size is 5. 255| # ConnectionHandler is a collection of ConnectionPool objects. It is used 288| @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
と言うことなので activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb の動作を調べる.まずは ActiveRecord::ConnectionAdapters::ConnectionPool#initialize から
65 | # The default ConnectionPool maximum size is 5. 66 | def initialize(spec) 67 | @spec = spec 68 | # The cache of reserved connections mapped to threads 69 | @reserved_connections = {} 70 | # The mutex used to synchronize pool access 71 | @connection_mutex = Monitor.new 72 | @queue = @connection_mutex.new_cond 73 | # default 5 second timeout 74 | @timeout = spec.config[:wait_timeout] || 5 75 | # default max pool size to 5 76 | @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 77 | @connections = [] 78 | @checked_out = [] 79 | end 80 | 81 | # Retrieve the connection associated with the current thread, or call 82 | # #checkout to obtain one if necessary. 83 | # 84 | # #connection can be called any number of times; the connection is 85 | # held in a hash keyed by the thread id. 86 | def connection 87 | if conn = @reserved_connections[current_connection_id] 88 | conn 89 | else 90 | @reserved_connections[current_connection_id] = checkout 91 | end 92 | end 93 |
ActiveRecord::ConnectionAdapters::ConnectionPool#connection で現在のコネクションが取得できて,
218 | def current_connection_id #:nodoc: 219 | Thread.current.object_id 220 | end
どのコネクションを使うかには,Thread.current オブジェクトのID を使ってるらしい.
287 | def establish_connection(name, spec) 288 | @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec) 289 | end
ここで初心に戻ると,ActiveRecord は通常 establish_connection で接続しに行くので,該当部分を activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb から見ると
49 | def self.establish_connection(spec = nil) 50 | case spec 51 | when nil 52 | raise AdapterNotSpecified unless defined? RAILS_ENV 53 | establish_connection(RAILS_ENV) 54 | when ConnectionSpecification 55 | @@connection_handler.establish_connection(name, spec) 56 | when Symbol, String 57 | if configuration = configurations[spec.to_s] 58 | establish_connection(configuration) 59 | else 60 | raise AdapterNotSpecified, "#{spec} database is not configured" 61 | end 62 | else
establish_connection の引数に ConnectionSpecification クラスが指定されていれば,@@connection_handler の establish_connection を呼ぶ.@@connection_handler の establish_connection というのは
10 | # The connection handler 11 | cattr_accessor :connection_handler, :instance_writer => false 12 | @@connection_handler = ConnectionAdapters::ConnectionHandler.new
で,ActiveRecord::ConnectionAdapters::ConnectionHandler#establish_connection が
287 | def establish_connection(name, spec) 288 | @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec) 289 | end
なので,上に書いてある ActiveRecord::ConnectionAdapters::ConnectionPool#connection に戻る.そこで現状のスレッド(Thread.current)で接続が無い(else)場合は checkout が呼ばれ
173 | def checkout 174 | # Checkout an available connection 175 | @connection_mutex.synchronize do 176 | loop do 177 | conn = if @checked_out.size < @connections.size 178 | checkout_existing_connection 179 | elsif @connections.size < @size 180 | checkout_new_connection 181 | end 182 | return conn if conn 183 | # No connections available; wait for one 184 | if @queue.wait(@timeout) 185 | next 186 | else 187 | # try looting dead threads 188 | clear_stale_cached_connections! 189 | if @size == @checked_out.size 190 | raise ConnectionTimeoutError, "could not obtain a database connection within #{@timeout} seconds. The pool size is currently #{@size}, perhaps you need to increase it?" 191 | end 192 | end 193 | end 194 | end 195 | end
なので @connection_mutex.synchronize = Mutex#synchronize によって他のスレッドと同期がとられて,データベースコネクションの Pooling が行われる.
続く.