Class: Sqlx::Database::DatabasePool
- Defined in:
- runtime/database/sqlite.rb
Overview
Dedicated SQLite connection pool. A single-use object so its instance ivars type concretely: @conns is a DatabaseConnection PtrArray (objects keep their tag, unlike a generic pool's int slot), @free is an IntArray stack of available indices into @conns.
Constant Summary collapse
- PRAGMAS =
PRAGMAs ARE PER-CONNECTION, so they run on every handle the pool opens, not once on the first; a file-backed server re-reads its working set through the OS page cache on every request otherwise.
cache_size is NEGATIVE on purpose: sqlite reads a positive value as a page COUNT and a negative one as a KiB budget, so -65536 is 64 MiB regardless of page_size, while 65536 would be 64k PAGES.
An in-memory database ignores these (its pages are already the heap), which is harmless.
[ "PRAGMA cache_size=-65536", # Read pages straight out of the page cache by mapping the file, which # drops the read() syscall and its copy for everything that fits. "PRAGMA mmap_size=268435456", # WAL for readers-alongside-writer. The lock wait is not a pragma: the # pool installs a fair busy handler on every connection (see # `busy_wait`), because `PRAGMA busy_timeout` sleeps on a back-off ladder # that starves waiters under a stream of short writes. "PRAGMA journal_mode=WAL", # NORMAL, because FULL fsyncs the WAL on EVERY COMMIT. Set rather than # left to a library default, so a rebuild cannot move the durability # setting silently. NORMAL survives a process crash and risks only the # most recent commits on OS or power failure. "PRAGMA synchronous=NORMAL", ].freeze
Instance Method Summary collapse
- #available ⇒ Object
-
#close_all ⇒ void
finalize every cached statement on every connection, then close its handle.
- #conn(idx) ⇒ Object
-
#ensure_open ⇒ void
open this pool's connections once, on first use.
- #first ⇒ Object
-
#initialize(path, n) ⇒ DatabasePool
constructor
A new instance of DatabasePool.
-
#lease ⇒ Integer
a free connection index (LIFO), parking this thread while the pool is empty;
releasesignals it. -
#release(idx, conn, finished) ⇒ void
return one connection to the free list and wake a waiter.
Constructor Details
#initialize(path, n) ⇒ DatabasePool
Returns a new instance of DatabasePool.
352 353 354 355 356 357 358 359 360 361 362 363 |
# File 'runtime/database/sqlite.rb', line 352 def initialize(path, n) @path = path @size = n @conns = [] @free = [] @lock = Mutex.new @cv = ConditionVariable.new # Connections open on first use, not here: a prefork child inherits # nothing because the parent never opened a handle before the fork. @open_lock = Mutex.new @opened = false end |
Instance Method Details
#available ⇒ Object
435 436 437 438 439 440 441 |
# File 'runtime/database/sqlite.rb', line 435 def available n = 0 @lock.synchronize do n = @free.length end n end |
#close_all ⇒ void
finalize every cached statement on every connection, then close its handle. A pool that never opened (the prefork parent before the fork) has nothing to close.
492 493 494 495 496 497 498 499 500 |
# File 'runtime/database/sqlite.rb', line 492 def close_all i = 0 while i < @conns.length c = @conns[i] c.finalize_all SQLite3.sqlite3_close(c.dbh) i += 1 end end |
#conn(idx) ⇒ Object
478 479 480 |
# File 'runtime/database/sqlite.rb', line 478 def conn(idx) @conns[idx] end |
#ensure_open ⇒ void
open this pool's connections once, on first use. The prefork parent
calls configure and forks before any database access, so the child
inherits an unopened pool object and opens its own handles here. The
open lock serializes simultaneous first requests so the pool is not
built twice.
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 |
# File 'runtime/database/sqlite.rb', line 372 def ensure_open return if @opened failure = nil conns = [] # Raising inside `Mutex#synchronize` would skip its unlock on this # adapter, so an open error is recorded and raised after the lock. @open_lock.synchronize do if !@opened i = 0 while i < @size && failure.nil? rc = SQLite3.sqlite3_open_v2(@path, SQLite3.db_out, SQLite3::OPEN_URI_RWC, nil) if rc != SQLite3::OK # Best-effort error surface — sqlite3_errmsg requires a valid # db handle, which we don't have on open failure. The numeric # rc + path are the only signals we can raise pre-handle. failure = "Sqlx::Database.configure: sqlite3_open(" + @path + ") failed (" + rc.to_s + ")" else dbh = SQLite3.read_ptr(SQLite3.db_out) # Deliberately not raising on a refused pragma. Every one of # these is an optimization or a politeness; none changes a # query's RESULT, so a build that declines one # (journal_mode=WAL on a read-only mount, say) should still # serve, slower, rather than fail to boot. PRAGMAS.each { |p| SQLite3.sqlite3_exec(dbh, p, nil, nil, nil) } # The lock wait is a callback, not a pragma; `busy_wait` says # why. A refused install is loud: without a handler SQLite # answers BUSY immediately, so the pool would be far less # available than configured. rc = SQLite3.sqlite3_busy_handler(dbh, Sqlx::Database.method(:busy_wait), nil) if rc != SQLite3::OK failure = "Sqlx::Database.configure: sqlite3_busy_handler failed (" + rc.to_s + ")" else conns.push(DatabaseConnection.new(dbh)) end end i += 1 end if failure.nil? @lock.synchronize do @conns = conns i = 0 while i < conns.length @free.push(i) i += 1 end @opened = true @cv.broadcast end end end end if !failure.nil? i = 0 while i < conns.length SQLite3.sqlite3_close(conns[i].dbh) i += 1 end raise failure end end |
#first ⇒ Object
482 483 484 485 |
# File 'runtime/database/sqlite.rb', line 482 def first ensure_open @conns[0] end |
#lease ⇒ Integer
a free connection index (LIFO), parking this thread while the pool is
empty; release signals it.
447 448 449 450 451 452 453 454 455 456 457 |
# File 'runtime/database/sqlite.rb', line 447 def lease ensure_open idx = 0 @lock.synchronize do while @free.length == 0 @cv.wait(@lock) end idx = @free.delete_at(@free.length - 1) end idx end |
#release(idx, conn, finished) ⇒ void
return one connection to the free list and wake a waiter. A completed
request trims the connection's statement cache first; the release sits
in an ensure so a raise while trimming cannot leak the slot.
467 468 469 470 471 472 473 474 475 476 |
# File 'runtime/database/sqlite.rb', line 467 def release(idx, conn, finished) begin conn.trim! if finished ensure @lock.synchronize do @free.push(idx) @cv.signal end end end |