mirror of
https://gitea.invidious.io/iv-org/shard-crystal-db.git
synced 2024-08-15 00:53:32 +00:00
Merge branch 'feature/pool'
Conflicts: src/db/error.cr
This commit is contained in:
commit
751be7aa6a
14 changed files with 767 additions and 89 deletions
90
spec/database_spec.cr
Normal file
90
spec/database_spec.cr
Normal file
|
@ -0,0 +1,90 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe DB::Database do
|
||||
it "allows connection initialization" do
|
||||
cnn_setup = 0
|
||||
DB.open "dummy://localhost:1027?initial_pool_size=2&max_pool_size=4&max_idle_pool_size=1" do |db|
|
||||
cnn_setup.should eq(0)
|
||||
|
||||
db.setup_connection do |cnn|
|
||||
cnn_setup += 1
|
||||
end
|
||||
|
||||
cnn_setup.should eq(2)
|
||||
|
||||
db.using_connection do
|
||||
cnn_setup.should eq(2)
|
||||
db.using_connection do
|
||||
cnn_setup.should eq(2)
|
||||
db.using_connection do
|
||||
cnn_setup.should eq(3)
|
||||
db.using_connection do
|
||||
cnn_setup.should eq(4)
|
||||
end
|
||||
# the pool didn't shrink no new initialization should be done next
|
||||
db.using_connection do
|
||||
cnn_setup.should eq(4)
|
||||
end
|
||||
end
|
||||
# the pool shrink 1. max_idle_pool_size=1
|
||||
# after the previous end there where 2.
|
||||
db.using_connection do
|
||||
cnn_setup.should eq(4)
|
||||
# so now there will be a new connection created
|
||||
db.using_connection do
|
||||
cnn_setup.should eq(5)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "should allow creation of more statements than pool connections" do
|
||||
DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=2" do |db|
|
||||
db.prepare("query1").should be_a(DB::PoolStatement)
|
||||
db.prepare("query2").should be_a(DB::PoolStatement)
|
||||
db.prepare("query3").should be_a(DB::PoolStatement)
|
||||
end
|
||||
end
|
||||
|
||||
it "should return same statement in pool per query" do
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare("query1")
|
||||
db.prepare("query2").should_not eq(stmt)
|
||||
db.prepare("query1").should eq(stmt)
|
||||
end
|
||||
end
|
||||
|
||||
it "should close pool statements when closing db" do
|
||||
stmt = uninitialized DB::PoolStatement
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare("query1")
|
||||
end
|
||||
stmt.closed?.should be_true
|
||||
end
|
||||
|
||||
it "should not reconnect if connection is lost and retry_attempts=0" do
|
||||
DummyDriver::DummyConnection.clear_connections
|
||||
DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=1&retry_attempts=0" do |db|
|
||||
db.exec("stmt1")
|
||||
DummyDriver::DummyConnection.connections.size.should eq(1)
|
||||
DummyDriver::DummyConnection.connections.first.disconnect!
|
||||
expect_raises DB::PoolRetryAttemptsExceeded do
|
||||
db.exec("stmt1")
|
||||
end
|
||||
DummyDriver::DummyConnection.connections.size.should eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
it "should reconnect if connection is lost and executing same statement" do
|
||||
DummyDriver::DummyConnection.clear_connections
|
||||
DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=1&retry_attempts=1" do |db|
|
||||
db.exec("stmt1")
|
||||
DummyDriver::DummyConnection.connections.size.should eq(1)
|
||||
DummyDriver::DummyConnection.connections.first.disconnect!
|
||||
db.exec("stmt1")
|
||||
DummyDriver::DummyConnection.connections.size.should eq(2)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -61,7 +61,7 @@ describe DB do
|
|||
it "should raise if the sole connection is been used" do
|
||||
with_dummy do |db|
|
||||
db.query "1" do |rs|
|
||||
expect_raises Exception, /DB Pool Exhausted/ do
|
||||
expect_raises DB::PoolTimeout do
|
||||
db.scalar "2"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@ class DummyDriver < DB::Driver
|
|||
class DummyConnection < DB::Connection
|
||||
def initialize(db)
|
||||
super(db)
|
||||
@connected = true
|
||||
@@connections ||= [] of DummyConnection
|
||||
@@connections.not_nil! << self
|
||||
end
|
||||
|
@ -29,6 +30,14 @@ class DummyDriver < DB::Driver
|
|||
0
|
||||
end
|
||||
|
||||
def check
|
||||
raise DB::ConnectionLost.new(self) unless @connected
|
||||
end
|
||||
|
||||
def disconnect!
|
||||
@connected = false
|
||||
end
|
||||
|
||||
protected def do_close
|
||||
super
|
||||
end
|
||||
|
@ -43,11 +52,13 @@ class DummyDriver < DB::Driver
|
|||
end
|
||||
|
||||
protected def perform_query(args : Enumerable)
|
||||
@connection.as(DummyConnection).check
|
||||
set_params args
|
||||
DummyResultSet.new self, @query
|
||||
end
|
||||
|
||||
protected def perform_exec(args : Enumerable)
|
||||
@connection.as(DummyConnection).check
|
||||
set_params args
|
||||
raise "forced exception due to query" if @query == "raise"
|
||||
DB::ExecResult.new 0i64, 0_i64
|
||||
|
@ -188,7 +199,15 @@ end
|
|||
def with_dummy
|
||||
DummyDriver::DummyConnection.clear_connections
|
||||
|
||||
DB.open "dummy://host" do |db|
|
||||
DB.open "dummy://host?checkout_timeout=0.5" do |db|
|
||||
yield db
|
||||
end
|
||||
end
|
||||
|
||||
def with_dummy_connection
|
||||
with_dummy do |db|
|
||||
db.using_connection do |cnn|
|
||||
yield cnn.as(DummyDriver::DummyConnection)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
192
spec/pool_spec.cr
Normal file
192
spec/pool_spec.cr
Normal file
|
@ -0,0 +1,192 @@
|
|||
require "./spec_helper"
|
||||
|
||||
class ShouldSleepingOp
|
||||
@is_sleeping = false
|
||||
getter is_sleeping
|
||||
getter sleep_happened
|
||||
|
||||
def initialize
|
||||
@sleep_happened = Channel(Nil).new
|
||||
end
|
||||
|
||||
def should_sleep
|
||||
s = self
|
||||
@is_sleeping = true
|
||||
spawn do
|
||||
sleep 0.1
|
||||
s.is_sleeping.should be_true
|
||||
s.sleep_happened.send(nil)
|
||||
end
|
||||
yield
|
||||
@is_sleeping = false
|
||||
end
|
||||
|
||||
def wait_for_sleep
|
||||
@sleep_happened.receive
|
||||
end
|
||||
end
|
||||
|
||||
class WaitFor
|
||||
def initialize
|
||||
@channel = Channel(Nil).new
|
||||
end
|
||||
|
||||
def wait
|
||||
@channel.receive
|
||||
end
|
||||
|
||||
def check
|
||||
@channel.send(nil)
|
||||
end
|
||||
end
|
||||
|
||||
class Closable
|
||||
include DB::Disposable
|
||||
|
||||
protected def do_close
|
||||
end
|
||||
end
|
||||
|
||||
describe DB::Pool do
|
||||
it "should use proc to create objects" do
|
||||
block_called = 0
|
||||
pool = DB::Pool.new(initial_pool_size: 3) { block_called += 1; Closable.new }
|
||||
block_called.should eq(3)
|
||||
end
|
||||
|
||||
it "should get resource" do
|
||||
pool = DB::Pool.new { Closable.new }
|
||||
pool.checkout.should be_a Closable
|
||||
end
|
||||
|
||||
it "should be available if not checkedout" do
|
||||
resource = uninitialized Closable
|
||||
pool = DB::Pool.new(initial_pool_size: 1) { resource = Closable.new }
|
||||
pool.is_available?(resource).should be_true
|
||||
end
|
||||
|
||||
it "should not be available if checkedout" do
|
||||
pool = DB::Pool.new { Closable.new }
|
||||
resource = pool.checkout
|
||||
pool.is_available?(resource).should be_false
|
||||
end
|
||||
|
||||
it "should be available if returned" do
|
||||
pool = DB::Pool.new { Closable.new }
|
||||
resource = pool.checkout
|
||||
pool.release resource
|
||||
pool.is_available?(resource).should be_true
|
||||
end
|
||||
|
||||
it "should wait for available resource" do
|
||||
pool = DB::Pool.new(max_pool_size: 1, initial_pool_size: 1) { Closable.new }
|
||||
|
||||
b_cnn_request = ShouldSleepingOp.new
|
||||
wait_a = WaitFor.new
|
||||
wait_b = WaitFor.new
|
||||
|
||||
spawn do
|
||||
a_cnn = pool.checkout
|
||||
b_cnn_request.wait_for_sleep
|
||||
pool.release a_cnn
|
||||
|
||||
wait_a.check
|
||||
end
|
||||
|
||||
spawn do
|
||||
b_cnn_request.should_sleep do
|
||||
pool.checkout
|
||||
end
|
||||
|
||||
wait_b.check
|
||||
end
|
||||
|
||||
wait_a.wait
|
||||
wait_b.wait
|
||||
end
|
||||
|
||||
it "should create new if max was not reached" do
|
||||
block_called = 0
|
||||
pool = DB::Pool.new(max_pool_size: 2, initial_pool_size: 1) { block_called += 1; Closable.new }
|
||||
block_called.should eq 1
|
||||
pool.checkout
|
||||
block_called.should eq 1
|
||||
pool.checkout
|
||||
block_called.should eq 2
|
||||
end
|
||||
|
||||
it "should reuse returned resources" do
|
||||
all = [] of Closable
|
||||
pool = DB::Pool.new(max_pool_size: 2, initial_pool_size: 1) { Closable.new.tap { |c| all << c } }
|
||||
pool.checkout
|
||||
b1 = pool.checkout
|
||||
pool.release b1
|
||||
b2 = pool.checkout
|
||||
|
||||
b1.should eq b2
|
||||
all.size.should eq 2
|
||||
end
|
||||
|
||||
it "should close available and total" do
|
||||
all = [] of Closable
|
||||
pool = DB::Pool.new(max_pool_size: 2, initial_pool_size: 1) { Closable.new.tap { |c| all << c } }
|
||||
a = pool.checkout
|
||||
b = pool.checkout
|
||||
pool.release b
|
||||
all.size.should eq 2
|
||||
|
||||
all[0].closed?.should be_false
|
||||
all[1].closed?.should be_false
|
||||
pool.close
|
||||
all[0].closed?.should be_true
|
||||
all[1].closed?.should be_true
|
||||
end
|
||||
|
||||
it "should timeout" do
|
||||
pool = DB::Pool.new(max_pool_size: 1, checkout_timeout: 0.1) { Closable.new }
|
||||
pool.checkout
|
||||
expect_raises DB::PoolTimeout do
|
||||
pool.checkout
|
||||
end
|
||||
end
|
||||
|
||||
it "should be able to release after a timeout" do
|
||||
pool = DB::Pool.new(max_pool_size: 1, checkout_timeout: 0.1) { Closable.new }
|
||||
a = pool.checkout
|
||||
pool.checkout rescue nil
|
||||
pool.release a
|
||||
end
|
||||
|
||||
it "should close if max idle amount is reached" do
|
||||
all = [] of Closable
|
||||
pool = DB::Pool.new(max_pool_size: 3, max_idle_pool_size: 1) { Closable.new.tap { |c| all << c } }
|
||||
pool.checkout
|
||||
pool.checkout
|
||||
pool.checkout
|
||||
|
||||
all.size.should eq 3
|
||||
all.any?(&.closed?).should be_false
|
||||
pool.release all[0]
|
||||
|
||||
all.any?(&.closed?).should be_false
|
||||
pool.release all[1]
|
||||
|
||||
all[0].closed?.should be_false
|
||||
all[1].closed?.should be_true
|
||||
all[2].closed?.should be_false
|
||||
end
|
||||
|
||||
it "should create resource after max_pool was reached if idle forced some close up" do
|
||||
all = [] of Closable
|
||||
pool = DB::Pool.new(max_pool_size: 3, max_idle_pool_size: 1) { Closable.new.tap { |c| all << c } }
|
||||
pool.checkout
|
||||
pool.checkout
|
||||
pool.checkout
|
||||
pool.release all[0]
|
||||
pool.release all[1]
|
||||
pool.checkout
|
||||
pool.checkout
|
||||
|
||||
all.size.should eq 4
|
||||
end
|
||||
end
|
|
@ -2,14 +2,14 @@ require "./spec_helper"
|
|||
|
||||
describe DB::Statement do
|
||||
it "should prepare statements" do
|
||||
with_dummy do |db|
|
||||
db.prepare("the query").should be_a(DB::Statement)
|
||||
with_dummy_connection do |cnn|
|
||||
cnn.prepare("the query").should be_a(DB::Statement)
|
||||
end
|
||||
end
|
||||
|
||||
it "should initialize positional params in query" do
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare("the query").as(DummyDriver::DummyStatement)
|
||||
with_dummy_connection do |cnn|
|
||||
stmt = cnn.prepare("the query").as(DummyDriver::DummyStatement)
|
||||
stmt.query "a", 1, nil
|
||||
stmt.params[0].should eq("a")
|
||||
stmt.params[1].should eq(1)
|
||||
|
@ -18,8 +18,8 @@ describe DB::Statement do
|
|||
end
|
||||
|
||||
it "should initialize positional params in query with array" do
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare("the query").as(DummyDriver::DummyStatement)
|
||||
with_dummy_connection do |cnn|
|
||||
stmt = cnn.prepare("the query").as(DummyDriver::DummyStatement)
|
||||
stmt.query ["a", 1, nil]
|
||||
stmt.params[0].should eq("a")
|
||||
stmt.params[1].should eq(1)
|
||||
|
@ -28,8 +28,8 @@ describe DB::Statement do
|
|||
end
|
||||
|
||||
it "should initialize positional params in exec" do
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare("the query").as(DummyDriver::DummyStatement)
|
||||
with_dummy_connection do |cnn|
|
||||
stmt = cnn.prepare("the query").as(DummyDriver::DummyStatement)
|
||||
stmt.exec "a", 1, nil
|
||||
stmt.params[0].should eq("a")
|
||||
stmt.params[1].should eq(1)
|
||||
|
@ -38,8 +38,8 @@ describe DB::Statement do
|
|||
end
|
||||
|
||||
it "should initialize positional params in exec with array" do
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare("the query").as(DummyDriver::DummyStatement)
|
||||
with_dummy_connection do |cnn|
|
||||
stmt = cnn.prepare("the query").as(DummyDriver::DummyStatement)
|
||||
stmt.exec ["a", 1, nil]
|
||||
stmt.params[0].should eq("a")
|
||||
stmt.params[1].should eq(1)
|
||||
|
@ -48,8 +48,8 @@ describe DB::Statement do
|
|||
end
|
||||
|
||||
it "should initialize positional params in scalar" do
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare("the query").as(DummyDriver::DummyStatement)
|
||||
with_dummy_connection do |cnn|
|
||||
stmt = cnn.prepare("the query").as(DummyDriver::DummyStatement)
|
||||
stmt.scalar "a", 1, nil
|
||||
stmt.params[0].should eq("a")
|
||||
stmt.params[1].should eq(1)
|
||||
|
@ -58,8 +58,8 @@ describe DB::Statement do
|
|||
end
|
||||
|
||||
it "query with block should not close statement" do
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare "3,4 1,2"
|
||||
with_dummy_connection do |cnn|
|
||||
stmt = cnn.prepare "3,4 1,2"
|
||||
stmt.query
|
||||
stmt.closed?.should be_false
|
||||
end
|
||||
|
@ -67,16 +67,16 @@ describe DB::Statement do
|
|||
|
||||
it "closing connection should close statement" do
|
||||
stmt = uninitialized DB::Statement
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare "3,4 1,2"
|
||||
with_dummy_connection do |cnn|
|
||||
stmt = cnn.prepare "3,4 1,2"
|
||||
stmt.query
|
||||
end
|
||||
stmt.closed?.should be_true
|
||||
end
|
||||
|
||||
it "query with block should not close statement" do
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare "3,4 1,2"
|
||||
with_dummy_connection do |cnn|
|
||||
stmt = cnn.prepare "3,4 1,2"
|
||||
stmt.query do |rs|
|
||||
end
|
||||
stmt.closed?.should be_false
|
||||
|
@ -84,8 +84,8 @@ describe DB::Statement do
|
|||
end
|
||||
|
||||
it "query should not close statement" do
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare "3,4 1,2"
|
||||
with_dummy_connection do |cnn|
|
||||
stmt = cnn.prepare "3,4 1,2"
|
||||
stmt.query do |rs|
|
||||
end
|
||||
stmt.closed?.should be_false
|
||||
|
@ -93,28 +93,28 @@ describe DB::Statement do
|
|||
end
|
||||
|
||||
it "scalar should not close statement" do
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare "3,4 1,2"
|
||||
with_dummy_connection do |cnn|
|
||||
stmt = cnn.prepare "3,4 1,2"
|
||||
stmt.scalar
|
||||
stmt.closed?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
it "exec should not close statement" do
|
||||
with_dummy do |db|
|
||||
stmt = db.prepare "3,4 1,2"
|
||||
with_dummy_connection do |cnn|
|
||||
stmt = cnn.prepare "3,4 1,2"
|
||||
stmt.exec
|
||||
stmt.closed?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
it "connection should cache statements by query" do
|
||||
with_dummy do |db|
|
||||
rs = db.query "1, ?", 2
|
||||
with_dummy_connection do |cnn|
|
||||
rs = cnn.query "1, ?", 2
|
||||
stmt = rs.statement
|
||||
rs.close
|
||||
|
||||
rs = db.query "1, ?", 4
|
||||
rs = cnn.query "1, ?", 4
|
||||
rs.statement.should be(stmt)
|
||||
end
|
||||
end
|
||||
|
@ -124,7 +124,8 @@ describe DB::Statement do
|
|||
expect_raises do
|
||||
db.exec "raise"
|
||||
end
|
||||
db.@in_pool.should be_true
|
||||
DummyDriver::DummyConnection.connections.size.should eq(1)
|
||||
db.pool.is_available?(DummyDriver::DummyConnection.connections.first)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue