fiber_connection_pool 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +7 -1
- data/README.md +24 -0
- data/examples/reel_server/Gemfile +7 -0
- data/examples/reel_server/main.rb +69 -0
- data/lib/fiber_connection_pool.rb +16 -5
- data/test/fiber_connection_pool_test.rb +66 -2
- data/test/helper.rb +20 -2
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d5107300e5f00c78557fabe1ef21a964bc0d929
|
|
4
|
+
data.tar.gz: 06f8d340506aba8db7035d0c5f17fb6ddc4e563b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d258dabb23ce59218aa85cb5271a6efebc91817bd74748f2ad9933001a5b160f8373c38c2768f26694f3ff5bde7a1331455f6f515fd7af7ebfe8c24af81bc245
|
|
7
|
+
data.tar.gz: 03f4d89e6235a16501a1998752e6693d7b8e8cd824a5d7c49d956482c667549fd73d59ea0d3b7f9028d0023c67cd5bbf1c78c7359732c36a9874693ddbe285eb
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -2,6 +2,7 @@ fiber_connection_pool
|
|
|
2
2
|
=====================
|
|
3
3
|
|
|
4
4
|
[](http://travis-ci.org/rubencaro/fiber_connection_pool)
|
|
5
|
+
[](http://rubygems.org/gems/fiber_connection_pool)
|
|
5
6
|
|
|
6
7
|
Fiber-based generic connection pool
|
|
7
8
|
|
|
@@ -16,3 +17,26 @@ with [Goliath](https://github.com/postrank-labs/goliath)
|
|
|
16
17
|
and in promising experiments with
|
|
17
18
|
[Reel](https://github.com/celluloid/reel)
|
|
18
19
|
([Celluloid](http://celluloid.io/) based) servers.
|
|
20
|
+
|
|
21
|
+
Install
|
|
22
|
+
----------------
|
|
23
|
+
|
|
24
|
+
Add this line to your application's Gemfile:
|
|
25
|
+
|
|
26
|
+
gem 'fiber_connection_pool'
|
|
27
|
+
|
|
28
|
+
Or install it yourself as:
|
|
29
|
+
|
|
30
|
+
$ gem install fiber_connection_pool
|
|
31
|
+
|
|
32
|
+
Inside of your Ruby program, require FiberConnectionPool with:
|
|
33
|
+
|
|
34
|
+
require 'fiber_connection_pool'
|
|
35
|
+
|
|
36
|
+
Supported Platforms
|
|
37
|
+
-------------------
|
|
38
|
+
|
|
39
|
+
Used in production environments on Ruby 1.9.3 and 2.0.0.
|
|
40
|
+
Tested against Ruby 1.9.3, 2.0.0, and rbx-19mode ([See details..](http://travis-ci.org/rubencaro/fiber_connection_pool)).
|
|
41
|
+
|
|
42
|
+
TODO: sparkling docs
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
source 'https://rubygems.org'
|
|
2
|
+
|
|
3
|
+
gem 'celluloid', github: 'celluloid/celluloid', branch: 'master'
|
|
4
|
+
gem 'celluloid-io', github: 'celluloid/celluloid-io', branch: 'master'
|
|
5
|
+
gem 'reel', github: 'celluloid/reel', branch: 'master'
|
|
6
|
+
gem 'ruby-mysql', github: 'rubencaro/ruby-mysql', branch: 'celluloid_io'
|
|
7
|
+
gem 'fiber_connection_pool'
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'reel'
|
|
2
|
+
require 'mysql'
|
|
3
|
+
require 'fiber'
|
|
4
|
+
require 'fiber_connection_pool'
|
|
5
|
+
require 'celluloid/autostart'
|
|
6
|
+
|
|
7
|
+
Mysql.unixsocket_class = Celluloid::IO::UNIXSocket
|
|
8
|
+
|
|
9
|
+
class Dispatcher
|
|
10
|
+
include Celluloid::IO
|
|
11
|
+
|
|
12
|
+
def initialize(opts = {})
|
|
13
|
+
@db_pool_size = opts[:db_pool_size] || 5
|
|
14
|
+
@pool = FiberConnectionPool.new(:size => @db_pool_size) do
|
|
15
|
+
Mysql.connect 'localhost','user','pass','bogusdb',nil,'/var/run/mysqld/mysqld.sock'
|
|
16
|
+
end
|
|
17
|
+
puts "DB Pool of size #{@db_pool_size} ready..."
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def dispatch(request)
|
|
21
|
+
print '.'
|
|
22
|
+
@pool.query 'select sleep(2);'
|
|
23
|
+
puts "Done #{Thread.current.to_s}, #{Fiber.current.to_s}"
|
|
24
|
+
request.respond :ok, "hello, world! #{Time.now.strftime('%T')}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DispatcherPool
|
|
30
|
+
|
|
31
|
+
def initialize(opts = {})
|
|
32
|
+
@size = opts[:size] || 5
|
|
33
|
+
@dispatchers = []
|
|
34
|
+
@size.times{ |i| @dispatchers << "dispatcher_#{i}".to_sym }
|
|
35
|
+
@dispatchers.each{ |d| Dispatcher.supervise_as d }
|
|
36
|
+
@next_dispatcher = 0
|
|
37
|
+
puts "Pool of #{@size} dispatchers ready."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def dispatch(request)
|
|
41
|
+
d = @next_dispatcher
|
|
42
|
+
@next_dispatcher += 1
|
|
43
|
+
@next_dispatcher = 0 if @next_dispatcher >= @dispatchers.count
|
|
44
|
+
Celluloid::Actor[@dispatchers[d]].dispatch(request)
|
|
45
|
+
rescue => ex
|
|
46
|
+
puts "Someone died: #{ex}"
|
|
47
|
+
request.respond :internal_server_error, "Someone died"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MyServer < Reel::Server
|
|
54
|
+
|
|
55
|
+
def initialize(host = "127.0.0.1", port = 3000)
|
|
56
|
+
super(host, port, &method(:on_connection))
|
|
57
|
+
@dispatcher = DispatcherPool.new
|
|
58
|
+
puts "Listening on #{host}:#{port}..."
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def on_connection(connection)
|
|
62
|
+
while request = connection.request
|
|
63
|
+
@dispatcher.dispatch(request)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
MyServer.run
|
|
@@ -1,18 +1,25 @@
|
|
|
1
|
+
require 'fiber'
|
|
2
|
+
|
|
1
3
|
class FiberConnectionPool
|
|
2
|
-
VERSION = '0.1.
|
|
4
|
+
VERSION = '0.1.1'
|
|
3
5
|
|
|
4
6
|
attr_accessor :saved_data
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
# Initializes the pool with 'size' instances
|
|
9
|
+
# running the given block to get each one. Ex:
|
|
10
|
+
#
|
|
11
|
+
# pool = FiberConnectionPool.new(:size => 5) { MyConnection.new }
|
|
12
|
+
#
|
|
13
|
+
def initialize(opts)
|
|
14
|
+
raise ArgumentError.new('size > 0 is mandatory') if opts[:size].to_i <= 0
|
|
15
|
+
|
|
7
16
|
@saved_data = {} # placeholder for requested save data
|
|
8
17
|
@reserved = {} # map of in-progress connections
|
|
9
18
|
@reserved_backup = {} # backup map of in-progress connections, to catch failures
|
|
10
19
|
@available = [] # pool of free connections
|
|
11
20
|
@pending = [] # pending reservations (FIFO)
|
|
12
21
|
|
|
13
|
-
opts[:size].
|
|
14
|
-
@available.push(block.call) if block_given?
|
|
15
|
-
end
|
|
22
|
+
@available = Array.new(opts[:size].to_i) { yield }
|
|
16
23
|
end
|
|
17
24
|
|
|
18
25
|
def save_data_for_fiber
|
|
@@ -39,12 +46,16 @@ class FiberConnectionPool
|
|
|
39
46
|
end
|
|
40
47
|
end
|
|
41
48
|
|
|
49
|
+
##
|
|
50
|
+
# avoid method_missing for most common methods
|
|
51
|
+
#
|
|
42
52
|
def query(sql)
|
|
43
53
|
execute(false,'query') do |conn|
|
|
44
54
|
conn.query sql
|
|
45
55
|
end
|
|
46
56
|
end
|
|
47
57
|
|
|
58
|
+
|
|
48
59
|
def recreate_connection(new_conn)
|
|
49
60
|
bad_conn = @reserved_backup[Fiber.current.object_id]
|
|
50
61
|
release_backup Fiber.current
|
|
@@ -3,8 +3,72 @@ require 'helper'
|
|
|
3
3
|
|
|
4
4
|
class TestFiberConnectionPool < Minitest::Test
|
|
5
5
|
|
|
6
|
-
def
|
|
7
|
-
|
|
6
|
+
def test_blocking_behaviour
|
|
7
|
+
# get pool and fibers
|
|
8
|
+
pool = FiberConnectionPool.new(:size => 5) { ::BlockingConnection.new(:delay => 0.05) }
|
|
9
|
+
|
|
10
|
+
fibers = Array.new(15){ Fiber.new { pool.do_something } }
|
|
11
|
+
|
|
12
|
+
a = Time.now
|
|
13
|
+
result = fibers.map(&:resume)
|
|
14
|
+
b = Time.now
|
|
15
|
+
|
|
16
|
+
# 15 fibers on a size 5 pool, but -blocking- connections
|
|
17
|
+
# with a 0.05 delay we expect to spend at least: 0.05*15 = 0.75
|
|
18
|
+
assert_operator((b - a), :>, 0.75)
|
|
19
|
+
|
|
20
|
+
# Also we only use the first connection from the pool,
|
|
21
|
+
# because as we are -blocking- it's always available
|
|
22
|
+
# again for the next request
|
|
23
|
+
assert_equal 1, result.uniq.count
|
|
8
24
|
end
|
|
9
25
|
|
|
26
|
+
def test_em_synchrony_behaviour
|
|
27
|
+
require 'em-synchrony'
|
|
28
|
+
|
|
29
|
+
a = b = nil
|
|
30
|
+
info = { :threads => [], :fibers => [], :instances => []}
|
|
31
|
+
|
|
32
|
+
EM.synchrony do
|
|
33
|
+
# get pool and fibers
|
|
34
|
+
pool = FiberConnectionPool.new(:size => 5) { ::EMSynchronyConnection.new(:delay => 0.05) }
|
|
35
|
+
|
|
36
|
+
fibers = Array.new(15){ Fiber.new { pool.do_something(info) } }
|
|
37
|
+
|
|
38
|
+
a = Time.now
|
|
39
|
+
fibers.each{ |f| f.resume }
|
|
40
|
+
# wait all fibers to end
|
|
41
|
+
while fibers.any?{ |f| f.alive? } do
|
|
42
|
+
EM::Synchrony.sleep 0.01
|
|
43
|
+
end
|
|
44
|
+
b = Time.now
|
|
45
|
+
EM.stop
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# 15 fibers on a size 5 pool, and -non-blocking- connections
|
|
49
|
+
# with a 0.05 delay we expect to spend at least: 0.05*15/5 = 0.15
|
|
50
|
+
# plus some breeze lost on precision on the wait loop
|
|
51
|
+
# then we should be under 0.20 for sure
|
|
52
|
+
assert_operator((b - a), :<, 0.20)
|
|
53
|
+
|
|
54
|
+
# we should have visited 1 thread, 15 fibers and 5 instances
|
|
55
|
+
info.dup.each{ |k,v| info[k] = v.uniq }
|
|
56
|
+
assert_equal 1, info[:threads].count
|
|
57
|
+
assert_equal 15, info[:fibers].count
|
|
58
|
+
assert_equal 5, info[:instances].count
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_size_is_mandatory
|
|
62
|
+
assert_raises ArgumentError do
|
|
63
|
+
FiberConnectionPool.new { ::BlockingConnection.new }
|
|
64
|
+
end
|
|
65
|
+
assert_raises ArgumentError do
|
|
66
|
+
FiberConnectionPool.new(:size => 'a') { ::BlockingConnection.new }
|
|
67
|
+
end
|
|
68
|
+
assert_raises ArgumentError do
|
|
69
|
+
FiberConnectionPool.new(:size => 0) { ::BlockingConnection.new }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
|
|
10
74
|
end
|
data/test/helper.rb
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
require 'minitest/pride'
|
|
2
2
|
require 'minitest/autorun'
|
|
3
3
|
|
|
4
|
-
$VERBOSE = 1
|
|
5
|
-
|
|
6
4
|
require_relative '../lib/fiber_connection_pool'
|
|
5
|
+
|
|
6
|
+
class BlockingConnection
|
|
7
|
+
def initialize(opts = {})
|
|
8
|
+
@delay = opts[:delay] || 0.05
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def do_something
|
|
12
|
+
sleep @delay
|
|
13
|
+
self.object_id
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class EMSynchronyConnection < BlockingConnection
|
|
18
|
+
def do_something(info)
|
|
19
|
+
info[:threads] << Thread.current.object_id
|
|
20
|
+
info[:fibers] << Fiber.current.object_id
|
|
21
|
+
info[:instances] << self.object_id
|
|
22
|
+
EM::Synchrony.sleep @delay
|
|
23
|
+
end
|
|
24
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fiber_connection_pool
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ruben Caro
|
|
@@ -56,6 +56,8 @@ files:
|
|
|
56
56
|
- README.md
|
|
57
57
|
- Rakefile
|
|
58
58
|
- examples/.placeholder
|
|
59
|
+
- examples/reel_server/Gemfile
|
|
60
|
+
- examples/reel_server/main.rb
|
|
59
61
|
- fiber_connection_pool.gemspec
|
|
60
62
|
- lib/fiber_connection_pool.rb
|
|
61
63
|
- test/fiber_connection_pool_test.rb
|