fiber_connection_pool 0.2.0 → 0.2.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/README.md +1 -0
- data/lib/fiber_connection_pool.rb +28 -30
- data/lib/fiber_connection_pool/exceptions.rb +4 -2
- data/test/fiber_connection_pool_test.rb +60 -33
- data/test/helper.rb +9 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9fdb88ab21985a6014912366f1a917da8d0df76c
|
4
|
+
data.tar.gz: cc131028a049c18efc0029e63e83966b24cc10b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 84abe69a34155bdf1307987d93a4b3c55cd0483be1d55ca0b50fadd8faf42f2582c46a1cada282beb9d245138c71829b00f3e86b8721a553ac75f6c8d1a9e414
|
7
|
+
data.tar.gz: d7f20e3e765f1e334405b0c333c86d63302804318856d2928823f9e10a00dc3015abfb826ccc773e39ab089621d958601301596564d062cfb7162cab7cc9bd3e
|
data/README.md
CHANGED
@@ -3,6 +3,7 @@ fiber_connection_pool
|
|
3
3
|
|
4
4
|
[](http://travis-ci.org/rubencaro/fiber_connection_pool)
|
5
5
|
[](http://rubygems.org/gems/fiber_connection_pool)
|
6
|
+
[](https://codeclimate.com/github/rubencaro/fiber_connection_pool)
|
6
7
|
|
7
8
|
Fiber-based generic connection pool
|
8
9
|
|
@@ -2,12 +2,12 @@ require 'fiber'
|
|
2
2
|
require_relative 'fiber_connection_pool/exceptions'
|
3
3
|
|
4
4
|
class FiberConnectionPool
|
5
|
-
VERSION = '0.2.
|
5
|
+
VERSION = '0.2.1'
|
6
6
|
|
7
|
-
|
7
|
+
RESERVED_TTL_SECS = 30 # reserved cleanup trigger
|
8
8
|
SAVED_DATA_TTL_SECS = 30 # saved_data cleanup trigger
|
9
9
|
|
10
|
-
attr_accessor :saved_data, :
|
10
|
+
attr_accessor :saved_data, :treated_exceptions
|
11
11
|
|
12
12
|
# Initializes the pool with 'size' instances
|
13
13
|
# running the given block to get each one. Ex:
|
@@ -19,8 +19,8 @@ class FiberConnectionPool
|
|
19
19
|
|
20
20
|
@saved_data = {} # placeholder for requested save data
|
21
21
|
@reserved = {} # map of in-progress connections
|
22
|
-
@
|
23
|
-
@
|
22
|
+
@treated_exceptions = [ PlaceholderException ] # list of Exception classes that need further connection treatment
|
23
|
+
@last_reserved_cleanup = Time.now # reserved cleanup trigger
|
24
24
|
@available = [] # pool of free connections
|
25
25
|
@pending = [] # pending reservations (FIFO)
|
26
26
|
@save_data_requests = {} # blocks to be yielded to save data
|
@@ -119,14 +119,12 @@ class FiberConnectionPool
|
|
119
119
|
# Identify the connection that just failed for current fiber.
|
120
120
|
# Pass it to the given block, which must return a valid instance of connection.
|
121
121
|
# After that, put the new connection into the pool in failed connection's place.
|
122
|
-
# Raises
|
122
|
+
# Raises NoReservedConnection if cannot find the failed connection instance.
|
123
123
|
#
|
124
124
|
def with_failed_connection
|
125
|
-
|
126
|
-
|
127
|
-
raise NoBackupConnection.new if bad_conn.nil?
|
125
|
+
bad_conn = @reserved[Fiber.current]
|
126
|
+
raise NoReservedConnection.new if bad_conn.nil?
|
128
127
|
new_conn = yield bad_conn
|
129
|
-
release_backup f
|
130
128
|
@available.reject!{ |v| v == bad_conn }
|
131
129
|
@reserved.reject!{ |k,v| v == bad_conn }
|
132
130
|
@available.push new_conn
|
@@ -137,13 +135,13 @@ class FiberConnectionPool
|
|
137
135
|
end
|
138
136
|
end
|
139
137
|
|
140
|
-
# Delete any
|
138
|
+
# Delete any reserved held for dead fibers
|
141
139
|
#
|
142
|
-
def
|
143
|
-
@
|
144
|
-
|
140
|
+
def reserved_cleanup
|
141
|
+
@last_reserved_cleanup = Time.now
|
142
|
+
@reserved.dup.each do |k,v|
|
143
|
+
release(k) if not k.alive?
|
145
144
|
end
|
146
|
-
@last_backup_cleanup = Time.now
|
147
145
|
end
|
148
146
|
|
149
147
|
private
|
@@ -164,12 +162,18 @@ class FiberConnectionPool
|
|
164
162
|
# save anything requested
|
165
163
|
process_save_data(f, conn, method)
|
166
164
|
|
167
|
-
# successful run,
|
168
|
-
|
165
|
+
# successful run, release
|
166
|
+
release(f)
|
169
167
|
|
170
168
|
retval
|
171
|
-
|
169
|
+
rescue *treated_exceptions => ex
|
170
|
+
# do not release connection for these
|
171
|
+
# maybe prepare something here to be used on connection repair
|
172
|
+
raise ex
|
173
|
+
rescue Exception => ex
|
174
|
+
# not successful run, but not meant to be treated
|
172
175
|
release(f)
|
176
|
+
raise ex
|
173
177
|
end
|
174
178
|
end
|
175
179
|
|
@@ -191,8 +195,7 @@ class FiberConnectionPool
|
|
191
195
|
#
|
192
196
|
def acquire(fiber)
|
193
197
|
if conn = @available.pop
|
194
|
-
@reserved[fiber
|
195
|
-
@reserved_backup[fiber] = conn
|
198
|
+
@reserved[fiber] = conn
|
196
199
|
conn
|
197
200
|
else
|
198
201
|
Fiber.yield @pending.push fiber
|
@@ -200,21 +203,16 @@ class FiberConnectionPool
|
|
200
203
|
end
|
201
204
|
end
|
202
205
|
|
203
|
-
# Release connection from the backup hash
|
204
|
-
# Also perform cleanup if TTL is past
|
205
|
-
#
|
206
|
-
def release_backup(fiber)
|
207
|
-
@reserved_backup.delete(fiber)
|
208
|
-
# try cleanup
|
209
|
-
backup_cleanup if (Time.now - @last_backup_cleanup) >= RESERVED_BACKUP_TTL_SECS
|
210
|
-
end
|
211
|
-
|
212
206
|
# Release connection assigned to the supplied fiber and
|
213
207
|
# resume any other pending connections (which will
|
214
208
|
# immediately try to run acquire on the pool)
|
209
|
+
# Also perform cleanup if TTL is past
|
215
210
|
#
|
216
211
|
def release(fiber)
|
217
|
-
@available.push
|
212
|
+
@available.push @reserved.delete(fiber)
|
213
|
+
|
214
|
+
# try cleanup
|
215
|
+
reserved_cleanup if (Time.now - @last_reserved_cleanup) >= RESERVED_TTL_SECS
|
218
216
|
|
219
217
|
if pending = @pending.shift
|
220
218
|
pending.resume
|
@@ -1,11 +1,13 @@
|
|
1
1
|
require 'helper'
|
2
|
+
require 'set'
|
3
|
+
require 'yaml'
|
2
4
|
|
3
5
|
class TestFiberConnectionPool < Minitest::Test
|
4
6
|
|
5
7
|
def test_blocking_behaviour
|
6
8
|
# get pool and fibers
|
7
9
|
pool = FiberConnectionPool.new(:size => 5) { ::BlockingConnection.new(:delay => 0.05) }
|
8
|
-
info = { :threads => [], :fibers => [], :instances => []}
|
10
|
+
info = { :threads => [].to_set, :fibers => [].to_set, :instances => [].to_set}
|
9
11
|
|
10
12
|
fibers = Array.new(15){ Fiber.new { pool.do_something(info) } }
|
11
13
|
|
@@ -21,14 +23,13 @@ class TestFiberConnectionPool < Minitest::Test
|
|
21
23
|
# because as we are -blocking- it's always available
|
22
24
|
# again for the next request
|
23
25
|
# we should have visited 1 thread, 15 fibers and 1 instances
|
24
|
-
info.dup.each{ |k,v| info[k] = v.uniq }
|
25
26
|
assert_equal 1, info[:threads].count
|
26
27
|
assert_equal 15, info[:fibers].count
|
27
28
|
assert_equal 1, info[:instances].count
|
28
29
|
end
|
29
30
|
|
30
31
|
def test_em_synchrony_behaviour
|
31
|
-
info = { :threads => [], :fibers => [], :instances => []}
|
32
|
+
info = { :threads => [].to_set, :fibers => [].to_set, :instances => [].to_set}
|
32
33
|
|
33
34
|
# get pool and fibers
|
34
35
|
pool = FiberConnectionPool.new(:size => 5) { ::EMSynchronyConnection.new(:delay => 0.05) }
|
@@ -44,7 +45,6 @@ class TestFiberConnectionPool < Minitest::Test
|
|
44
45
|
assert_operator(lapse, :<, 0.20)
|
45
46
|
|
46
47
|
# we should have visited 1 thread, 15 fibers and 5 instances
|
47
|
-
info.dup.each{ |k,v| info[k] = v.uniq }
|
48
48
|
assert_equal 1, info[:threads].count
|
49
49
|
assert_equal 15, info[:fibers].count
|
50
50
|
assert_equal 5, info[:instances].count
|
@@ -68,19 +68,22 @@ class TestFiberConnectionPool < Minitest::Test
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def test_failure_reaction
|
71
|
-
info = { :instances => [] }
|
71
|
+
info = { :instances => [].to_set, :repaired_connections => [].to_set, :failing_connections => [].to_set }
|
72
72
|
|
73
73
|
# get pool and fibers
|
74
74
|
pool = FiberConnectionPool.new(:size => 5) { ::EMSynchronyConnection.new(:delay => 0.05) }
|
75
75
|
|
76
76
|
fibers = Array.new(14){ Fiber.new { pool.do_something(info) } }
|
77
77
|
|
78
|
+
# state which exceptions should be treated
|
79
|
+
pool.treated_exceptions = [ FakeException ]
|
80
|
+
|
78
81
|
failing_fiber = Fiber.new do
|
79
82
|
begin
|
80
83
|
pool.fail(info)
|
81
84
|
rescue
|
82
85
|
pool.with_failed_connection do |connection|
|
83
|
-
info[:
|
86
|
+
info[:repaired_connections] << connection
|
84
87
|
# replace it in the pool
|
85
88
|
::EMSynchronyConnection.new(:delay => 0.05)
|
86
89
|
end
|
@@ -88,62 +91,78 @@ class TestFiberConnectionPool < Minitest::Test
|
|
88
91
|
end
|
89
92
|
# put it among others, not the first or the last
|
90
93
|
# so we see it does not mistake the failing connection
|
91
|
-
fibers.insert
|
94
|
+
fibers.insert 2,failing_fiber
|
95
|
+
|
96
|
+
failing_fiber_not_treated = Fiber.new do
|
97
|
+
begin
|
98
|
+
pool.fail_not_treated(info)
|
99
|
+
rescue
|
100
|
+
# not meant to be treated
|
101
|
+
assert_raises NoReservedConnection do
|
102
|
+
pool.with_failed_connection{ |c| 'boo' }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
# put it among others, not the first or the last
|
107
|
+
# so we see it does not mistake the failing connection
|
108
|
+
fibers.insert 7,failing_fiber_not_treated
|
92
109
|
|
93
110
|
run_em_reactor fibers
|
94
111
|
|
95
|
-
|
96
|
-
|
112
|
+
|
113
|
+
# we should have visited 1 thread, 15 fibers and 6 instances (including repaired)
|
97
114
|
assert_equal 6, info[:instances].count
|
98
115
|
|
99
|
-
# assert we do not lose track of failing
|
100
|
-
assert_equal
|
116
|
+
# assert we do not lose track of failing connections, but only repair those that needed it
|
117
|
+
assert_equal 2, info[:failing_connections].count
|
118
|
+
assert_equal 1, info[:repaired_connections].count
|
119
|
+
assert info[:failing_connections].include?(info[:repaired_connections].first)
|
101
120
|
|
102
121
|
# assert we replaced it
|
103
|
-
refute pool.has_connection?(info[:
|
122
|
+
refute pool.has_connection?(info[:repaired_connections].first)
|
104
123
|
|
105
124
|
# nothing left
|
106
|
-
assert_equal(0, pool.
|
125
|
+
assert_equal(0, pool.instance_variable_get(:@reserved).count)
|
107
126
|
|
108
127
|
# if dealing with failed connection where you shouldn't...
|
109
|
-
assert_raises
|
128
|
+
assert_raises NoReservedConnection do
|
110
129
|
pool.with_failed_connection{ |c| 'boo' }
|
111
130
|
end
|
112
131
|
end
|
113
132
|
|
114
|
-
def
|
133
|
+
def test_reserved
|
115
134
|
# create pool, run fibers and gather info
|
116
|
-
pool, info =
|
135
|
+
pool, info = run_reserved
|
117
136
|
|
118
137
|
# one left
|
119
|
-
assert_equal(1, pool.
|
138
|
+
assert_equal(1, pool.instance_variable_get(:@reserved).count)
|
120
139
|
|
121
140
|
# fire cleanup
|
122
|
-
pool.
|
141
|
+
pool.reserved_cleanup
|
123
142
|
|
124
143
|
# nothing left
|
125
|
-
assert_equal(0, pool.
|
144
|
+
assert_equal(0, pool.instance_variable_get(:@reserved).count)
|
126
145
|
|
127
146
|
# assert we did not replace it
|
128
|
-
assert pool.has_connection?(
|
147
|
+
assert info[:failing_connections].all?{ |c| pool.has_connection?(c) }
|
129
148
|
end
|
130
149
|
|
131
|
-
def
|
150
|
+
def test_auto_cleanup_reserved
|
132
151
|
# lower ttl to force auto cleanup
|
133
|
-
prev_ttl = force_constant FiberConnectionPool, :
|
152
|
+
prev_ttl = force_constant FiberConnectionPool, :RESERVED_TTL_SECS, 0
|
134
153
|
|
135
154
|
# create pool, run fibers and gather info
|
136
|
-
pool, info =
|
155
|
+
pool, info = run_reserved
|
137
156
|
|
138
157
|
# nothing left, because failing fiber was not the last to run
|
139
158
|
# the following fiber made the cleanup
|
140
|
-
assert_equal(0, pool.
|
159
|
+
assert_equal(0, pool.instance_variable_get(:@reserved).count)
|
141
160
|
|
142
161
|
# assert we did not replace it
|
143
|
-
assert pool.has_connection?(
|
162
|
+
assert info[:failing_connections].all?{ |c| pool.has_connection?(c) }
|
144
163
|
ensure
|
145
164
|
# restore
|
146
|
-
force_constant FiberConnectionPool, :
|
165
|
+
force_constant FiberConnectionPool, :RESERVED_TTL_SECS, prev_ttl
|
147
166
|
end
|
148
167
|
|
149
168
|
def test_save_data
|
@@ -183,32 +202,41 @@ class TestFiberConnectionPool < Minitest::Test
|
|
183
202
|
|
184
203
|
private
|
185
204
|
|
186
|
-
def
|
187
|
-
info = { :instances => [] }
|
205
|
+
def run_reserved
|
206
|
+
info = { :instances => [].to_set, :failing_connections => [].to_set }
|
188
207
|
|
189
208
|
# get pool and fibers
|
190
209
|
pool = FiberConnectionPool.new(:size => 2) { ::EMSynchronyConnection.new(:delay => 0.05) }
|
191
210
|
|
192
211
|
fibers = Array.new(4){ Fiber.new { pool.do_something(info) } }
|
193
212
|
|
194
|
-
#
|
213
|
+
# state which exceptions should be treated
|
214
|
+
pool.treated_exceptions = [ FakeException ]
|
215
|
+
|
216
|
+
# we do not repair it, stays reserved
|
195
217
|
failing_fiber = Fiber.new { pool.fail(info) rescue nil }
|
196
218
|
|
197
219
|
# put it among others, not the first or the last
|
198
220
|
# so we see it does not mistake the failing connection
|
199
221
|
fibers.insert 2,failing_fiber
|
200
222
|
|
223
|
+
# we do not repair it, but we did not mean to, it's released again
|
224
|
+
failing_fiber_not_treated = Fiber.new { pool.fail_not_treated(info) rescue nil }
|
225
|
+
|
226
|
+
# put it among others, not the first or the last
|
227
|
+
# so we see it does not mistake the failing connection
|
228
|
+
fibers.insert 1,failing_fiber_not_treated
|
229
|
+
|
201
230
|
run_em_reactor fibers
|
202
231
|
|
203
|
-
# we should have visited only 2 instances (
|
204
|
-
info.dup.each{ |k,v| info[k] = v.uniq if v.is_a?(Array) }
|
232
|
+
# we should have visited only 2 instances (nothing was repaired this time)
|
205
233
|
assert_equal 2, info[:instances].count
|
206
234
|
|
207
235
|
[ pool, info ]
|
208
236
|
end
|
209
237
|
|
210
238
|
def run_saved_data
|
211
|
-
info = { :instances => [] }
|
239
|
+
info = { :instances => [].to_set }
|
212
240
|
|
213
241
|
# get pool and fibers
|
214
242
|
pool = FiberConnectionPool.new(:size => 2) { ::EMSynchronyConnection.new(:delay => 0.05) }
|
@@ -227,7 +255,6 @@ class TestFiberConnectionPool < Minitest::Test
|
|
227
255
|
run_em_reactor fibers
|
228
256
|
|
229
257
|
# we should have visited 2 instances
|
230
|
-
info.dup.each{ |k,v| info[k] = v.uniq if v.is_a?(Array) }
|
231
258
|
assert_equal 2, info[:instances].count
|
232
259
|
|
233
260
|
[ pool, fibers, info ]
|
data/test/helper.rb
CHANGED
@@ -21,6 +21,8 @@ class BlockingConnection
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
class FakeException < StandardError; end
|
25
|
+
|
24
26
|
class EMSynchronyConnection < BlockingConnection
|
25
27
|
def do_something(info = {})
|
26
28
|
fill_info info
|
@@ -29,7 +31,13 @@ class EMSynchronyConnection < BlockingConnection
|
|
29
31
|
|
30
32
|
def fail(info)
|
31
33
|
fill_info info
|
32
|
-
info[:
|
34
|
+
info[:failing_connections] << self
|
35
|
+
raise FakeException.new "Fakingly failing here..."
|
36
|
+
end
|
37
|
+
|
38
|
+
def fail_not_treated(info)
|
39
|
+
fill_info info
|
40
|
+
info[:failing_connections] << self
|
33
41
|
raise "Sadly failing here..."
|
34
42
|
end
|
35
43
|
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.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ruben Caro
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-09-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|