class Sequel::ThreadedConnectionPool
A connection pool allowing multi-threaded access to a pool of connections. This is the default connection pool used by Sequel.
Constants
- USE_WAITER
Attributes
A hash with thread/fiber keys and connection values for currently allocated connections. The calling code should already have the mutex before calling this.
An array of connections that are available for use by the pool. The calling code should already have the mutex before calling this.
The maximum number of connections this pool will create (per shard/server if sharding).
Public Class Methods
Source
# File lib/sequel/connection_pool/threaded.rb 28 def initialize(db, opts = OPTS) 29 super 30 @max_size = Integer(opts[:max_connections] || 4) 31 raise(Sequel::Error, ':max_connections must be positive') if @max_size < 1 32 @mutex = Mutex.new 33 @connection_handling = opts[:connection_handling] 34 @available_connections = [] 35 @allocated = {} 36 @allocated.compare_by_identity 37 @timeout = Float(opts[:pool_timeout] || 5) 38 @waiter = ConditionVariable.new 39 end
The following additional options are respected:
- :max_connections
-
The maximum number of connections the connection pool will open (default 4)
- :pool_timeout
-
The amount of seconds to wait to acquire a connection before raising a PoolTimeout error (default 5)
Sequel::ConnectionPool::new
Public Instance Methods
Source
# File lib/sequel/connection_pool/threaded.rb 46 def all_connections 47 hold do |c| 48 sync do 49 yield c 50 @available_connections.each{|conn| yield conn} 51 end 52 end 53 end
Yield all of the available connections, and the one currently allocated to this thread. This will not yield connections currently allocated to other threads, as it is not safe to operate on them. This holds the mutex while it is yielding all of the available connections, which means that until the method’s block returns, the pool is locked.
Source
# File lib/sequel/connection_pool/threaded.rb 64 def disconnect(opts=OPTS) 65 conns = nil 66 sync do 67 conns = @available_connections.dup 68 @available_connections.clear 69 @waiter.signal 70 end 71 conns.each{|conn| disconnect_connection(conn)} 72 end
Removes all connections currently available. This method has the effect of disconnecting from the database, assuming that no connections are currently being used. If you want to be able to disconnect connections that are currently in use, use the ShardedThreadedConnectionPool, which can do that. This connection pool does not, for performance reasons. To use the sharded pool, pass the servers: {} option when connecting to the database.
Once a connection is requested using hold, the connection pool creates new connections to the database.
Source
# File lib/sequel/connection_pool/threaded.rb 87 def hold(server=nil) 88 t = Sequel.current 89 if conn = owned_connection(t) 90 return yield(conn) 91 end 92 begin 93 conn = acquire(t) 94 yield conn 95 rescue Sequel::DatabaseDisconnectError, *@error_classes => e 96 if disconnect_error?(e) 97 oconn = conn 98 conn = nil 99 disconnect_connection(oconn) if oconn 100 sync do 101 @allocated.delete(t) 102 @waiter.signal 103 end 104 end 105 raise 106 ensure 107 if conn 108 sync{release(t)} 109 if @connection_handling == :disconnect 110 disconnect_connection(conn) 111 end 112 end 113 end 114 end
Chooses the first available connection, or if none are available, creates a new connection. Passes the connection to the supplied block:
pool.hold {|conn| conn.execute('DROP TABLE posts')}
Pool#hold is re-entrant, meaning it can be called recursively in the same thread without blocking.
If no connection is immediately available and the pool is already using the maximum number of connections, Pool#hold will block until a connection is available or the timeout expires. If the timeout expires before a connection can be acquired, a Sequel::PoolTimeout is raised.
Source
# File lib/sequel/connection_pool/threaded.rb 116 def pool_type 117 :threaded 118 end
Source
# File lib/sequel/connection_pool/threaded.rb 122 def size 123 @mutex.synchronize{_size} 124 end
The total number of connections opened, either available or allocated. The calling code should not have the mutex before calling this.
Private Instance Methods
Source
# File lib/sequel/connection_pool/threaded.rb 130 def _size 131 @allocated.length + @available_connections.length 132 end
The total number of connections opened, either available or allocated. The calling code should already have the mutex before calling this.
Source
# File lib/sequel/connection_pool/threaded.rb 140 def acquire(thread) 141 if conn = assign_connection(thread) 142 return conn 143 end 144 145 timeout = @timeout 146 timer = Sequel.start_timer 147 148 if conn = acquire_available(thread, timeout) 149 return conn 150 end 151 152 until conn = assign_connection(thread) 153 elapsed = Sequel.elapsed_seconds_since(timer) 154 # simplecov:disable 155 raise_pool_timeout(elapsed) if elapsed > timeout 156 157 # It's difficult to get to this point, it can only happen if there is a race condition 158 # where a connection cannot be acquired even after the thread is signalled by the condition variable 159 if conn = acquire_available(thread, timeout - elapsed) 160 return conn 161 end 162 # simplecov:enable 163 end 164 165 conn 166 end
Assigns a connection to the supplied thread, if one is available. The calling code should NOT already have the mutex when calling this.
This should return a connection is one is available within the timeout, or raise PoolTimeout if a connection could not be acquired within the timeout.
Source
# File lib/sequel/connection_pool/threaded.rb 169 def acquire_available(thread, timeout) 170 sync do 171 # Check if connection was checked in between when assign_connection failed and now. 172 # This is very unlikely, but necessary to prevent a situation where the waiter 173 # will wait for a connection even though one has already been checked in. 174 # simplecov:disable 175 if conn = next_available 176 return(@allocated[thread] = conn) 177 end 178 # simplecov:enable 179 180 @waiter.wait(@mutex, timeout) 181 182 # Connection still not available, could be because a connection was disconnected, 183 # may have to retry assign_connection to see if a new connection can be made. 184 if conn = next_available 185 return(@allocated[thread] = conn) 186 end 187 end 188 end
Acquire a connection if one is already available, or waiting until it becomes available.
Source
# File lib/sequel/connection_pool/threaded.rb 192 def assign_connection(thread) 193 # Thread safe as instance variable is only assigned to local variable 194 # and not operated on outside mutex. 195 allocated = @allocated 196 do_make_new = false 197 to_disconnect = nil 198 199 sync do 200 if conn = next_available 201 return(allocated[thread] = conn) 202 end 203 204 if (n = _size) >= (max = @max_size) 205 allocated.keys.each do |t| 206 unless t.alive? 207 (to_disconnect ||= []) << allocated.delete(t) 208 end 209 end 210 n = nil 211 end 212 213 if (n || _size) < max 214 do_make_new = allocated[thread] = true 215 end 216 end 217 218 if to_disconnect 219 to_disconnect.each{|dconn| disconnect_connection(dconn)} 220 end 221 222 # Connect to the database outside of the connection pool mutex, 223 # as that can take a long time and the connection pool mutex 224 # shouldn't be locked while the connection takes place. 225 if do_make_new 226 begin 227 conn = make_new(:default) 228 sync{allocated[thread] = conn} 229 ensure 230 unless conn 231 sync{allocated.delete(thread)} 232 end 233 end 234 end 235 236 conn 237 end
Assign a connection to the thread, or return nil if one cannot be assigned. The caller should NOT have the mutex before calling this.
Source
# File lib/sequel/connection_pool/threaded.rb 241 def checkin_connection(conn) 242 @available_connections << conn 243 conn 244 end
Return a connection to the pool of available connections, returns the connection. The calling code should already have the mutex before calling this.
Source
# File lib/sequel/connection_pool/threaded.rb 249 def next_available 250 case @connection_handling 251 when :stack 252 @available_connections.pop 253 else 254 @available_connections.shift 255 end 256 end
Return the next available connection in the pool, or nil if there is not currently an available connection. The calling code should already have the mutex before calling this.
Source
# File lib/sequel/connection_pool/threaded.rb 260 def owned_connection(thread) 261 sync{@allocated[thread]} 262 end
Returns the connection owned by the supplied thread, if any. The calling code should NOT already have the mutex before calling this.
Source
# File lib/sequel/connection_pool/threaded.rb 266 def preconnect(concurrent = false) 267 enum = (max_size - _size).times 268 269 conns = if concurrent 270 enum.map{Thread.new{make_new(:default)}}.map(&:value) 271 else 272 enum.map{make_new(:default)} 273 end 274 275 sync{conns.each{|conn| checkin_connection(conn)}} 276 end
Create the maximum number of connections immediately. The calling code should NOT have the mutex before calling this.
Source
# File lib/sequel/connection_pool/threaded.rb 280 def raise_pool_timeout(elapsed) 281 name = db.opts[:name] 282 raise ::Sequel::PoolTimeout, "timeout: #{@timeout}, elapsed: #{elapsed}#{", database name: #{name}" if name}" 283 end
Raise a PoolTimeout error showing the current timeout, the elapsed time, and the database’s name (if any).
Source
# File lib/sequel/connection_pool/threaded.rb 287 def release(thread) 288 conn = @allocated.delete(thread) 289 290 unless @connection_handling == :disconnect 291 checkin_connection(conn) 292 end 293 294 @waiter.signal 295 296 # Ensure that after signalling the condition, some other thread is given the 297 # opportunity to acquire the mutex. 298 # See <https://github.com/socketry/async/issues/99> for more context. 299 sleep(0) 300 301 nil 302 end
Releases the connection assigned to the supplied thread back to the pool. The calling code should already have the mutex before calling this.
Source
# File lib/sequel/connection_pool/threaded.rb 306 def sync 307 @mutex.synchronize{yield} 308 end
Yield to the block while inside the mutex. The calling code should NOT already have the mutex before calling this.