allow DB to use a connection pool.

allow Driver to parse connection pool options for extensibility.
fix waiting_resource counter after a timeout was generated.
This commit is contained in:
Brian J. Cardiff 2016-07-07 15:50:09 -03:00
parent b8cabee956
commit a2c22c16cf
7 changed files with 46 additions and 22 deletions

View file

@ -61,7 +61,7 @@ describe DB do
it "should raise if the sole connection is been used" do it "should raise if the sole connection is been used" do
with_dummy do |db| with_dummy do |db|
db.query "1" do |rs| db.query "1" do |rs|
expect_raises Exception, /DB Pool Exhausted/ do expect_raises DB::PoolTimeout do
db.scalar "2" db.scalar "2"
end end
end end

View file

@ -187,7 +187,7 @@ end
def with_dummy def with_dummy
DummyDriver::DummyConnection.clear_connections DummyDriver::DummyConnection.clear_connections
DB.open "dummy://host" do |db| DB.open "dummy://host?checkout_timeout=0.5" do |db|
yield db yield db
end end
end end

View file

@ -131,6 +131,13 @@ describe DB::Pool do
end end
end end
it "should be able to release after a timeout" do
pool = DB::Pool.new(->{ Closable.new }, max_pool_size: 1, checkout_timeout: 0.1)
a = pool.checkout
pool.checkout rescue nil
pool.release a
end
it "should close if max idle amount is reached" do it "should close if max idle amount is reached" do
all = [] of Closable all = [] of Closable
pool = DB::Pool.new(->{ Closable.new.tap { |c| all << c } }, max_pool_size: 3, max_idle_pool_size: 1) pool = DB::Pool.new(->{ Closable.new.tap { |c| all << c } }, max_pool_size: 3, max_idle_pool_size: 1)

View file

@ -6,8 +6,7 @@ require "uri"
# #
# Drivers implementors check `Driver` class. # Drivers implementors check `Driver` class.
# #
# Currently a *single connection* to the database is stablished. # DB manage a connection pool. The connection pool can be configured by `URI` query. See `Database`.
# In the future a connection pool and transaction support will be available.
# #
# ### Usage # ### Usage
# #

View file

@ -1,7 +1,14 @@
require "http/params"
module DB module DB
# Acts as an entry point for database access. # Acts as an entry point for database access.
# Currently it creates a single connection to the database. # Connections are managed by a pool.
# Eventually a connection pool will be handled. # The connection pool can be configured from URI parameters:
#
# - initial_pool_size (default 1)
# - max_pool_size (default 1)
# - max_idle_pool_size (default 1)
# - checkout_timeout (default 5.0)
# #
# It should be created from DB module. See `DB#open`. # It should be created from DB module. See `DB#open`.
# #
@ -13,19 +20,21 @@ module DB
# Returns the uri with the connection settings to the database # Returns the uri with the connection settings to the database
getter uri getter uri
@connection : Connection? @pool : Pool(Connection)
# :nodoc: # :nodoc:
def initialize(@driver : Driver, @uri : URI) def initialize(@driver : Driver, @uri : URI)
@in_pool = true # TODO: PR HTTP::Params.new -> HTTP::Params.new(Hash(String, Array(String)).new)
@connection = @driver.build_connection(self) params = (query = uri.query) ? HTTP::Params.parse(query) : HTTP::Params.new(Hash(String, Array(String)).new)
pool_options = @driver.connection_pool_options(params)
@pool = uninitialized Pool(Connection) # in order to use self in the factory proc
@pool = Pool.new(->{ @driver.build_connection(self).as(Connection) }, **pool_options)
end end
# Closes all connection to the database. # Closes all connection to the database.
def close def close
@connection.try &.close @pool.close
# prevent GC Warning: Finalization cycle involving discovered by mysql implementation
@connection = nil
end end
# :nodoc: # :nodoc:
@ -41,14 +50,12 @@ module DB
# :nodoc: # :nodoc:
def get_from_pool def get_from_pool
raise "DB Pool Exhausted" unless @in_pool @pool.checkout
@in_pool = false
@connection.not_nil!
end end
# :nodoc: # :nodoc:
def return_to_pool(connection) def return_to_pool(connection)
@in_pool = true @pool.release connection
end end
# yields a connection from the pool # yields a connection from the pool

View file

@ -27,5 +27,14 @@ module DB
# driver implementation instructions. # driver implementation instructions.
abstract class Driver abstract class Driver
abstract def build_connection(db : Database) : Connection abstract def build_connection(db : Database) : Connection
def connection_pool_options(params : HTTP::Params)
{
initial_pool_size: params.fetch("initial_pool_size", 1).to_i,
max_pool_size: params.fetch("max_pool_size", 1).to_i,
max_idle_pool_size: params.fetch("max_idle_pool_size", 1).to_i,
checkout_timeout: params.fetch("checkout_timeout", 5.0).to_f,
}
end
end end
end end

View file

@ -72,12 +72,18 @@ module DB
timeout.start timeout.start
# if there are no available resources, sleep until one is available # if there are no available resources, sleep until one is available
@availability_channel.receive @availability_channel.receive
timeout.raise_if_reached if timeout.timeout_reached?
dec_waiting_resource
raise DB::PoolTimeout.new
end
# double check there is something available to be checkedout # double check there is something available to be checkedout
while @available.empty? while @available.empty?
@availability_channel.receive @availability_channel.receive
timeout.raise_if_reached if timeout.timeout_reached?
dec_waiting_resource
raise DB::PoolTimeout.new
end
end end
timeout.cancel timeout.cancel
@ -125,10 +131,6 @@ module DB
def timeout_reached? def timeout_reached?
@should_timeout @should_timeout
end end
def raise_if_reached
raise DB::PoolTimeout.new if timeout_reached?
end
end end
end end
end end