Class: Sqlx::Database::PgPool
Overview
A lazy pool of libpq connections. Connections open on first checkout, not
in configure: a prefork child must not inherit the parent's sockets, and
the boot probe already proved the conninfo is reachable. The pool is the
concurrency boundary — one connection is leased to one thread, so libpq's
single-statement-at-a-time rule holds without a process-wide lock.
Pool state: @conns[i] is a live connection, or nil while position i
waits for a replacement; @free holds positions whose connection is live
and idle; @vacant holds positions whose connection is gone; @opening
counts positions reserved for an open in progress. A dead lease token and
the replacement at its position can be outstanding together — release
identifies the connection, not only the position.
A checkout validates the leased connection (idle and lifetime bounds plus
a SELECT 1 ping) before handing it to the caller; a failed check
discards that connection and leases again.
Constant Summary collapse
- CHECKOUT_TIMEOUT_SECONDS =
How long a checkout may wait for a free connection. A request that cannot get one fails loudly instead of parking behind a long transaction; the knob is
checkout_timeout. 5.0- DEFAULT_IDLE_TIMEOUT_MS =
A connection idle longer than this is replaced at checkout rather than reused.
idle_timeoutdisables it at 0. The bound is applied at checkout, not by a background reaper. 300000- DEFAULT_MAX_LIFETIME_MS =
A connection older than this is replaced at checkout. HikariCP calls the knob
maxLifetime; 0 disables it. 1800000
Instance Method Summary collapse
-
#checkout ⇒ LeaseToken
lease a connection, prove it usable, and answer the lease token.
- #close_all ⇒ Object
- #conn(idx) ⇒ Object
-
#discard(conn) ⇒ Object
remove a failed connection.
-
#first ⇒ Object
The fallback connection for single-fiber contexts before
require_leases!: answer position 0, opening it on first use. -
#initialize(conninfo, size) ⇒ PgPool
constructor
A new instance of PgPool.
-
#release(idx, conn, finished) ⇒ Object
the shared lease token's release hook.
Constructor Details
#initialize(conninfo, size) ⇒ PgPool
Returns a new instance of PgPool.
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'runtime/database/postgresql.rb', line 245 def initialize(conninfo, size) if size < 1 raise ArgumentError, "Sqlx::Database: pool_size must be at least 1, got " + size.to_s end @conninfo = conninfo @size = size @timeout_seconds = CHECKOUT_TIMEOUT_SECONDS ev = ENV["DATABASE_POOL_TIMEOUT_MS"] if !ev.nil? && ev != "" @timeout_seconds = ev.to_f / 1000.0 end @idle_timeout_seconds = env_seconds("DATABASE_IDLE_TIMEOUT_MS", DEFAULT_IDLE_TIMEOUT_MS) @max_lifetime_seconds = env_seconds("DATABASE_MAX_LIFETIME_MS", DEFAULT_MAX_LIFETIME_MS) @conns = [] @free = [] @vacant = [] @opening = 0 @lock = Mutex.new @cv = ConditionVariable.new end |
Instance Method Details
#checkout ⇒ LeaseToken
lease a connection, prove it usable, and answer the lease token. A failed check discards the connection and retries the checkout once; the retry is a checkout retry, not a statement retry.
318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'runtime/database/postgresql.rb', line 318 def checkout attempts = 0 while attempts < 2 idx = lease conn = @conns[idx] now = Process.clock_gettime(Process::CLOCK_MONOTONIC) if !conn.nil? && conn.usable?(now, @idle_timeout_seconds, @max_lifetime_seconds) return LeaseToken.new(self, idx) end discard(conn) if !conn.nil? attempts += 1 end raise Sqlx::ConnectionFailed, "Sqlx::Database: no healthy pooled PostgreSQL connection after " + attempts.to_s + " attempts" end |
#close_all ⇒ Object
367 368 369 370 371 372 373 374 375 376 |
# File 'runtime/database/postgresql.rb', line 367 def close_all i = 0 while i < @conns.length conn = @conns[i] if !conn.nil? conn.close end i += 1 end end |
#conn(idx) ⇒ Object
266 267 268 |
# File 'runtime/database/postgresql.rb', line 266 def conn(idx) @conns[idx] end |
#discard(conn) ⇒ Object
remove a failed connection. The position stays vacant and is refilled by a later checkout that finds no free connection; the current lease's release is a no-op because its connection no longer occupies the position. Closing happens outside the lock.
352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'runtime/database/postgresql.rb', line 352 def discard(conn) discarded = false @lock.synchronize do idx = conn.index if idx >= 0 && idx < @conns.length && @conns[idx] == conn @conns[idx] = nil @free.delete(idx) @vacant.push(idx) @cv.signal discarded = true end end conn.close if discarded end |
#first ⇒ Object
The fallback connection for single-fiber contexts before
require_leases!: answer position 0, opening it on first use. The
position stays in the free list, so a later lease can take it once the
server requires leases (the fallback path is gone by then). A failed
connection at position 0 is repaired here too — the fallback has no
lease to ask for a replacement. The open runs outside @lock, like
lease: no caller may wait behind DNS/TCP. Two fallback callers are
outside the single-fiber contract, but the race is closed anyway — the
loser closes its extra connection and answers the repaired one.
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
# File 'runtime/database/postgresql.rb', line 279 def first existing = nil @lock.synchronize do if @conns.length > 0 && !@conns[0].nil? existing = @conns[0] end end return existing if !existing.nil? conn = PgConnection.new(@conninfo) extra = nil @lock.synchronize do if @conns.length == 0 conn.attach_to_position(0) @conns.push(conn) @free.push(0) @cv.signal elsif @conns[0].nil? conn.attach_to_position(0) @conns[0] = conn @vacant.delete(0) @free.push(0) unless @free.include?(0) @cv.signal else extra = conn conn = @conns[0] end end extra.close if !extra.nil? conn end |
#release(idx, conn, finished) ⇒ Object
the shared lease token's release hook. finished is unused here: this
adapter has no statement cache to trim. The position is only returned
when it still holds the same connection; a connection discarded
mid-statement has already left the pool, and its position waits for a
later checkout that finds no free connection.
338 339 340 341 342 343 344 345 346 |
# File 'runtime/database/postgresql.rb', line 338 def release(idx, conn, finished) @lock.synchronize do if idx >= 0 && idx < @conns.length && @conns[idx] == conn conn.mark_used(Process.clock_gettime(Process::CLOCK_MONOTONIC)) @free.push(idx) @cv.signal end end end |