Skip to content

Commit

Permalink
fibered connection pool spec
Browse files Browse the repository at this point in the history
  • Loading branch information
humanzz committed Sep 23, 2008
1 parent 7b90d4c commit dfcf477
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 17 deletions.
40 changes: 23 additions & 17 deletions lib/never_block/pool/fibered_connection_pool.rb
Expand Up @@ -25,36 +25,42 @@ module Pool
# end
# 32.times do
# Fiber.new do
# conn = pool.hold # hold will pause the fiber until a connection is available
# conn.execute('something') # you can use the connection normally now
# # acquire a connection from the pool
# pool.hold do |conn|
# conn.execute('something') # you can use the connection normally now
# end
# end.resume
# end
#
# The pool has support for transactions, just pass true to the pool#hold method
# and the connection will not be released after the block is finished
# The pool has support for transactions, just pass true to the
# pool#hold method and the connection will not be released after the block
# is finished
# It is the responsibility of client code to release the connection
class FiberedConnectionPool

# initialize the connection pool
# using the supplied proc to create the connections
# you can choose to start them eagerly or lazily (lazy by default)

attr_reader :size

# initialize the connection pool using the supplied proc to create
# the connections
# You can choose to start them eagerly or lazily (lazy by default)
# Available options are
# :size => the maximum number of connections to be created in the pool
# :eager => (true|false) indicates whether connections should be
# created initially or when need
def initialize(options = {}, &block)
@connections, @busy_connections, @queue = [], {},[]
@connection_proc = block
@size = options[:size] || 8
if options[:eager]
@size.times do
@connections << @connection_proc.call
end
end
end
end

# If a connection is available,
# pass it to the block, otherwise
# pass the fiber to the queue
# till a connection is available
# when done with a connection
# try to porcess other fibers in the queue
# If a connection is available, pass it to the block, otherwise pass
# the fiber to the queue till a connection is available
# when done with a connection try to porcess other fibers in the queue
# before releasing the connection
# if inside a transaction, don't release the fiber
def hold(transactional = false)
Expand All @@ -67,7 +73,7 @@ def hold(transactional = false)
yield conn
ensure
release(fiber, conn) unless transactional
process_queue
process_queue
end
end

Expand Down Expand Up @@ -106,7 +112,7 @@ def process_queue
fiber = @queue.shift
# What is really happening here?
# we are resuming a fiber from within
# another, should we call transfer insted?
# another, should we call transfer instead?
fiber.resume @busy_connections[fiber] = @connections.shift
end
end
Expand Down
106 changes: 106 additions & 0 deletions spec/fibered_connection_pool_spec.rb
@@ -0,0 +1,106 @@
$:.unshift File.expand_path('..')
require 'lib/neverblock'

class MockConnection; end

describe NB::Pool::FiberedConnectionPool do
before(:each) do
@pool = NB::Pool::FiberedConnectionPool.new(:size => 10) do
MockConnection.new
end
end

it "should create all connections lazily by default" do
@pool.instance_variable_get(:@connections).length.should == 0
@pool.instance_variable_get(:@busy_connections).length.should == 0
end

it "should create all connections eagerly if specified" do
@pool = NB::Pool::FiberedConnectionPool.new(:size => 10, :eager => true) do
MockConnection.new
end
@pool.instance_variable_get(:@connections).length.should == 10
@pool.instance_variable_get(:@busy_connections).length.should == 0
end

it "should create and yield a connection if :size not reached" do
@pool.instance_variable_get(:@connections).length.should == 0
@pool.instance_variable_get(:@busy_connections).length.should == 0

@pool.hold {|conn| conn.should be_instance_of(MockConnection)}

@pool.instance_variable_get(:@connections).length.should == 1
@pool.instance_variable_get(:@busy_connections).length.should == 0
end

it "should create connections up to :size and queue other requests" do
# prepate the fiber pool
fpool = NB::Pool::FiberPool.new(15)
fibers = []; fpool.fibers.each {|f| fibers << f}
progress = Array.new(15, false)

# send 15 requests to the connection pool (of size 10)
10.times do |i|
fpool.spawn do
@pool.hold do |conn|
Fiber.yield
progress[i] = Fiber.current #mark task finished
end
end
end
(10..14).each do |i|
fpool.spawn do
@pool.hold do |conn|
progress[i] = Fiber.current #mark task finished
end
end
end

# 10 requests should be in progress and 5 should be queued
@pool.instance_variable_get(:@connections).length.should == 0
@pool.instance_variable_get(:@busy_connections).length.should == 10
@pool.instance_variable_get(:@queue).length.should == 5

#resume first request which will finish it and will also handle the
#queued requests
fibers[0].resume
[0,*10..14].each {|i| fibers[i].should == progress[i]}
[*1..9].each do |i|
progress[i].should == false
fibers[i].resume
progress[i].should == fibers[i]
end
end

it "should use the same connection in a transaction" do
#make sure there are more than one connection in the pool
@pool = NB::Pool::FiberedConnectionPool.new(:size => 10, :eager => true) do
MockConnection.new
end
fpool = NB::Pool::FiberPool.new(12)
fibers = []; fpool.fibers.each {|f| fibers << f}
t_conn = nil
fpool.spawn do
#announce the beginning of a transaction
@pool.hold(true) {|conn| t_conn = conn}

#another call to hold should get the same transaction's connection
@pool.hold {|conn| t_conn.should == conn}

#release the transaction connection
@pool.hold do |conn|
t_conn.should == conn
@pool.release(Fiber.current, conn)
end

#will now get a connection other than the transation's one (since there
#are many connections. If there was only one then it would have been
#returned anyways)
@pool.hold {|conn| t_conn.should_not == conn}
end
end

after(:each) do
@pool = nil
end
end

0 comments on commit dfcf477

Please sign in to comment.