-
-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
Showing
5 changed files
with
166 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
require "./spec_helper" | ||
require "./support/http" | ||
|
||
describe DB::Pool do | ||
it "distributes evenly the requests" do | ||
mutex = Mutex.new | ||
requests_per_connection = Hash(Socket::Address, Int32).new | ||
|
||
server = HTTP::Server.new do |context| | ||
remote_address = context.request.remote_address.not_nil! | ||
mutex.synchronize do | ||
requests_per_connection[remote_address] ||= 0 | ||
requests_per_connection[remote_address] += 1 | ||
end | ||
sleep context.request.query_params["delay"].to_f | ||
context.response.print "ok" | ||
end | ||
address = server.bind_unused_port "127.0.0.1" | ||
|
||
run_server(server) do | ||
fixed_pool_size = 5 | ||
expected_per_connection = 5 | ||
requests = fixed_pool_size * expected_per_connection | ||
|
||
pool = DB::Pool.new( | ||
initial_pool_size: fixed_pool_size, | ||
max_pool_size: fixed_pool_size, | ||
max_idle_pool_size: fixed_pool_size) { | ||
HTTP::Client.new(URI.parse("http://127.0.0.1:#{address.port}/")) | ||
} | ||
|
||
done = Channel(Nil).new | ||
|
||
requests.times do | ||
spawn do | ||
pool.checkout do |http| | ||
http.get("/?delay=0.1") | ||
end | ||
done.send(nil) | ||
end | ||
end | ||
|
||
spawn do | ||
requests.times { done.receive } | ||
done.close | ||
end | ||
wait_for { done.closed? } | ||
|
||
requests_per_connection.values.should eq([expected_per_connection] * fixed_pool_size) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
def wait_until_blocked(f : Fiber, timeout = 5.seconds) | ||
now = Time.monotonic | ||
|
||
until f.resumable? | ||
Fiber.yield | ||
raise "fiber failed to block within #{timeout}" if (Time.monotonic - now) > timeout | ||
end | ||
end | ||
|
||
def wait_until_finished(f : Fiber, timeout = 5.seconds) | ||
now = Time.monotonic | ||
until f.dead? | ||
Fiber.yield | ||
raise "fiber failed to finish within #{timeout}" if (Time.monotonic - now) > timeout | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
require "http" | ||
require "./fibers" | ||
|
||
def wait_for(timeout = 5.seconds) | ||
now = Time.monotonic | ||
|
||
until yield | ||
Fiber.yield | ||
|
||
if (Time.monotonic - now) > timeout | ||
raise "block failed to evaluate to true within #{timeout}" | ||
end | ||
end | ||
end | ||
|
||
# Helper method which runs *server* | ||
# 1. Spawns `server.listen` in a new fiber. | ||
# 2. Waits until `server.listening?`. | ||
# 3. Yields to the given block. | ||
# 4. Ensures the server is closed. | ||
# 5. After returning from the block, it waits for the server to gracefully | ||
# shut down before continuing execution in the current fiber. | ||
# 6. If the listening fiber raises an exception, it is rescued and re-raised | ||
# in the current fiber. | ||
def run_server(server) | ||
server_done = Channel(Exception?).new | ||
|
||
f = spawn do | ||
server.listen | ||
rescue exc | ||
server_done.send exc | ||
else | ||
server_done.send nil | ||
end | ||
|
||
begin | ||
wait_for { server.listening? } | ||
wait_until_blocked f | ||
|
||
yield server_done | ||
ensure | ||
server.close unless server.closed? | ||
|
||
if exc = server_done.receive | ||
raise exc | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters