Allow DB::Pool to be used a generic connection pool (#131)

* Allow DB::Pool to be a generic connection pool

* Use fully qualified class name for consistency

Co-authored-by: Brian J. Cardiff <bcardiff@gmail.com>

* Wrap only the necessary code in an `ensure`

* Add spec for http client pool

* Fix ICE in crystal-sqlite3

Co-authored-by: Brian J. Cardiff <bcardiff@gmail.com>
This commit is contained in:
Jamie Gaskins 2020-09-14 09:49:00 -04:00 committed by GitHub
parent ed686ad301
commit 291b65b853
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 166 additions and 15 deletions

View file

@ -1,4 +1,7 @@
module DB
abstract class Connection
end
class Error < Exception
end
@ -11,20 +14,29 @@ module DB
class PoolRetryAttemptsExceeded < Error
end
class PoolResourceLost(T) < Error
getter resource : T
def initialize(@resource : T)
end
end
class PoolResourceRefused < Error
end
# Raised when an established connection is lost
# probably due to socket/network issues.
# It is used by the connection pool retry logic.
class ConnectionLost < Error
getter connection : Connection
def initialize(@connection)
class ConnectionLost < PoolResourceLost(Connection)
def connection
resource
end
end
# Raised when a connection is unable to be established
# probably due to socket/network or configuration issues.
# It is used by the connection pool retry logic.
class ConnectionRefused < Error
class ConnectionRefused < PoolResourceRefused
end
class Rollback < Error

View file

@ -1,5 +1,7 @@
require "weak_ref"
require "./error"
module DB
class Pool(T)
# Pool configuration
@ -52,12 +54,19 @@ module DB
@idle.clear
end
record Stats, open_connections : Int32
record Stats,
open_connections : Int32,
idle_connections : Int32,
in_flight_connections : Int32,
max_connections : Int32
# Returns stats of the pool
def stats
Stats.new(
open_connections: @total.size
open_connections: @total.size,
idle_connections: @idle.size,
in_flight_connections: @inflight,
max_connections: @max_pool_size,
)
end
@ -91,10 +100,22 @@ module DB
resource
end
res.before_checkout
if res.responds_to?(:before_checkout)
res.before_checkout
end
res
end
def checkout(&block : T ->)
connection = checkout
begin
yield connection
ensure
release connection
end
end
# ```
# selected, is_candidate = pool.checkout_some(candidates)
# ```
@ -122,7 +143,9 @@ module DB
sync do
if can_increase_idle_pool
@idle << resource
resource.after_release
if resource.responds_to?(:after_release)
resource.after_release
end
idle_pushed = true
else
resource.close
@ -153,12 +176,12 @@ module DB
begin
sleep @retry_delay if i >= current_available
return yield
rescue e : ConnectionLost
rescue e : PoolResourceLost(T)
# if the connection is lost close it to release resources
# and remove it from the known pool.
sync { delete(e.connection) }
e.connection.close
rescue e : ConnectionRefused
sync { delete(e.resource) }
e.resource.close
rescue e : PoolResourceRefused
# a ConnectionRefused means a new connection
# was intended to be created
# nothing to due but to retry soon
@ -215,7 +238,7 @@ module DB
sync_dec_waiting_resource
when timeout(@checkout_timeout.seconds)
sync_dec_waiting_resource
raise DB::PoolTimeout.new
raise DB::PoolTimeout.new("Could not check out a connection in #{@checkout_timeout} seconds")
end
end
{% else %}
@ -232,7 +255,7 @@ module DB
sync_dec_waiting_resource
when 1
sync_dec_waiting_resource
raise DB::PoolTimeout.new
raise DB::PoolTimeout.new("Could not check out a connection in #{@checkout_timeout} seconds")
else
raise DB::Error.new
end