fiber_connection_pool 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://secure.travis-ci.org/rubencaro/fiber_connection_pool.png?branch=master)](http://travis-ci.org/rubencaro/fiber_connection_pool)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/fiber_connection_pool.png)](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
|