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 が行われる.

続く.

 
comments powered by Disqus