fiber_connection_pool 0.2.5 → 0.3.0
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.
- data/lib/fiber_connection_pool.rb +58 -37
- data/test/fiber_connection_pool_test.rb +96 -0
- metadata +2 -2
|
@@ -2,12 +2,14 @@ require 'fiber'
|
|
|
2
2
|
require_relative 'fiber_connection_pool/exceptions'
|
|
3
3
|
|
|
4
4
|
class FiberConnectionPool
|
|
5
|
-
VERSION = '0.
|
|
5
|
+
VERSION = '0.3.0'
|
|
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
10
|
attr_accessor :saved_data, :treated_exceptions
|
|
11
|
+
|
|
12
|
+
attr_reader :size
|
|
11
13
|
|
|
12
14
|
# Initializes the pool with 'size' instances
|
|
13
15
|
# running the given block to get each one. Ex:
|
|
@@ -16,6 +18,8 @@ class FiberConnectionPool
|
|
|
16
18
|
#
|
|
17
19
|
def initialize(opts)
|
|
18
20
|
raise ArgumentError.new('size > 0 is mandatory') if opts[:size].to_i <= 0
|
|
21
|
+
|
|
22
|
+
@size = opts[:size].to_i
|
|
19
23
|
|
|
20
24
|
@saved_data = {} # placeholder for requested save data
|
|
21
25
|
@reserved = {} # map of in-progress connections
|
|
@@ -25,8 +29,9 @@ class FiberConnectionPool
|
|
|
25
29
|
@pending = [] # pending reservations (FIFO)
|
|
26
30
|
@save_data_requests = {} # blocks to be yielded to save data
|
|
27
31
|
@last_data_cleanup = Time.now # saved_data cleanup trigger
|
|
32
|
+
@keep_connection = {} # keep reserved connections for fiber
|
|
28
33
|
|
|
29
|
-
@available = Array.new(
|
|
34
|
+
@available = Array.new(@size) { yield }
|
|
30
35
|
end
|
|
31
36
|
|
|
32
37
|
# DEPRECATED: use save_data
|
|
@@ -137,6 +142,52 @@ class FiberConnectionPool
|
|
|
137
142
|
@reserved.dup.each do |k,v|
|
|
138
143
|
release(k) if not k.alive?
|
|
139
144
|
end
|
|
145
|
+
@keep_connection.dup.each do |k,v|
|
|
146
|
+
@keep_connection.delete(k) if not k.alive?
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Acquire a lock on a connection and assign it to given fiber
|
|
151
|
+
# If no connection is available, yield the given fiber on the pending array
|
|
152
|
+
#
|
|
153
|
+
# If :keep => true is given (by default), connection is kept, you must call 'release' at some point
|
|
154
|
+
#
|
|
155
|
+
# Ex:
|
|
156
|
+
#
|
|
157
|
+
# ...
|
|
158
|
+
#
|
|
159
|
+
#
|
|
160
|
+
#
|
|
161
|
+
def acquire(fiber = nil, opts = { :keep => true })
|
|
162
|
+
fiber = Fiber.current if fiber.nil?
|
|
163
|
+
@keep_connection[fiber] = true if opts[:keep]
|
|
164
|
+
return @reserved[fiber] if @reserved[fiber] # already reserved? then use it
|
|
165
|
+
if conn = @available.pop
|
|
166
|
+
@reserved[fiber] = conn
|
|
167
|
+
conn
|
|
168
|
+
else
|
|
169
|
+
Fiber.yield @pending.push fiber
|
|
170
|
+
acquire(fiber,opts)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Release connection assigned to the supplied fiber and
|
|
175
|
+
# resume any other pending connections (which will
|
|
176
|
+
# immediately try to run acquire on the pool)
|
|
177
|
+
# Also perform cleanup if TTL is past
|
|
178
|
+
#
|
|
179
|
+
def release(fiber = nil)
|
|
180
|
+
fiber = Fiber.current if fiber.nil?
|
|
181
|
+
@keep_connection.delete fiber
|
|
182
|
+
|
|
183
|
+
@available.unshift @reserved.delete(fiber)
|
|
184
|
+
|
|
185
|
+
# try cleanup
|
|
186
|
+
reserved_cleanup if (Time.now - @last_reserved_cleanup) >= RESERVED_TTL_SECS
|
|
187
|
+
|
|
188
|
+
if pending = @pending.shift
|
|
189
|
+
pending.resume
|
|
190
|
+
end
|
|
140
191
|
end
|
|
141
192
|
|
|
142
193
|
private
|
|
@@ -151,14 +202,14 @@ class FiberConnectionPool
|
|
|
151
202
|
f = Fiber.current
|
|
152
203
|
begin
|
|
153
204
|
# get a connection and use it
|
|
154
|
-
conn = acquire(f)
|
|
205
|
+
conn = acquire(f, :keep => false)
|
|
155
206
|
retval = yield conn
|
|
156
207
|
|
|
157
208
|
# save anything requested
|
|
158
209
|
process_save_data(f, conn, method, args)
|
|
159
210
|
|
|
160
|
-
# successful run, release
|
|
161
|
-
release(f)
|
|
211
|
+
# successful run, release if not keeping
|
|
212
|
+
release(f) if not @keep_connection[f]
|
|
162
213
|
|
|
163
214
|
retval
|
|
164
215
|
rescue *treated_exceptions => ex
|
|
@@ -166,8 +217,8 @@ class FiberConnectionPool
|
|
|
166
217
|
# maybe prepare something here to be used on connection repair
|
|
167
218
|
raise ex
|
|
168
219
|
rescue Exception => ex
|
|
169
|
-
# not successful run, but not meant to be treated
|
|
170
|
-
release(f)
|
|
220
|
+
# not successful run, but not meant to be treated, release if not keeping
|
|
221
|
+
release(f) if not @keep_connection[f]
|
|
171
222
|
raise ex
|
|
172
223
|
end
|
|
173
224
|
end
|
|
@@ -185,36 +236,6 @@ class FiberConnectionPool
|
|
|
185
236
|
save_data_cleanup if (Time.now - @last_data_cleanup) >= SAVED_DATA_TTL_SECS
|
|
186
237
|
end
|
|
187
238
|
|
|
188
|
-
# Acquire a lock on a connection and assign it to given fiber
|
|
189
|
-
# If no connection is available, yield the given fiber on the pending array
|
|
190
|
-
#
|
|
191
|
-
def acquire(fiber)
|
|
192
|
-
return @reserved[fiber] if @reserved[fiber] # already reserved? then use it
|
|
193
|
-
if conn = @available.pop
|
|
194
|
-
@reserved[fiber] = conn
|
|
195
|
-
conn
|
|
196
|
-
else
|
|
197
|
-
Fiber.yield @pending.push fiber
|
|
198
|
-
acquire(fiber)
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
# Release connection assigned to the supplied fiber and
|
|
203
|
-
# resume any other pending connections (which will
|
|
204
|
-
# immediately try to run acquire on the pool)
|
|
205
|
-
# Also perform cleanup if TTL is past
|
|
206
|
-
#
|
|
207
|
-
def release(fiber)
|
|
208
|
-
@available.unshift @reserved.delete(fiber)
|
|
209
|
-
|
|
210
|
-
# try cleanup
|
|
211
|
-
reserved_cleanup if (Time.now - @last_reserved_cleanup) >= RESERVED_TTL_SECS
|
|
212
|
-
|
|
213
|
-
if pending = @pending.shift
|
|
214
|
-
pending.resume
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
239
|
# Allow the pool to behave as the underlying connection
|
|
219
240
|
#
|
|
220
241
|
# Yield the connection within execute method and release
|
|
@@ -164,6 +164,86 @@ class TestFiberConnectionPool < Minitest::Test
|
|
|
164
164
|
# restore
|
|
165
165
|
force_constant FiberConnectionPool, :RESERVED_TTL_SECS, prev_ttl
|
|
166
166
|
end
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_working_manual_reserve
|
|
170
|
+
info = { :instances => [].to_set, :failing_connections => [].to_set }
|
|
171
|
+
|
|
172
|
+
# get pool and fibers
|
|
173
|
+
pool = FiberConnectionPool.new(:size => 4) { ::EMSynchronyConnection.new(:delay => 0.05) }
|
|
174
|
+
|
|
175
|
+
fibers = []
|
|
176
|
+
fibers << Fiber.new do
|
|
177
|
+
begin
|
|
178
|
+
pool.acquire
|
|
179
|
+
pool.size.times{ pool.do_something(info) }
|
|
180
|
+
ensure
|
|
181
|
+
pool.release
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
fibers << Fiber.new do
|
|
186
|
+
n = 0
|
|
187
|
+
begin
|
|
188
|
+
pool.acquire
|
|
189
|
+
pool.fail(info)
|
|
190
|
+
rescue
|
|
191
|
+
n += 1
|
|
192
|
+
retry if n < pool.size
|
|
193
|
+
ensure
|
|
194
|
+
pool.release
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
run_em_reactor fibers
|
|
199
|
+
|
|
200
|
+
# we should have used only two instances
|
|
201
|
+
assert_equal 2, info[:instances].count
|
|
202
|
+
|
|
203
|
+
# only one failing connection
|
|
204
|
+
assert_equal 1, info[:failing_connections].count
|
|
205
|
+
|
|
206
|
+
# nothing left, because we made manual release
|
|
207
|
+
assert_equal(0, pool.instance_variable_get(:@reserved).count)
|
|
208
|
+
assert_equal(0, pool.instance_variable_get(:@keep_connection).count)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def test_manual_reserve_cleanup
|
|
212
|
+
# lower ttl to force auto cleanup
|
|
213
|
+
prev_ttl = force_constant FiberConnectionPool, :RESERVED_TTL_SECS, 0
|
|
214
|
+
|
|
215
|
+
info = { :instances => [].to_set, :failing_connections => [].to_set }
|
|
216
|
+
|
|
217
|
+
# get pool and fibers
|
|
218
|
+
pool = FiberConnectionPool.new(:size => 4) { ::EMSynchronyConnection.new(:delay => 0.05) }
|
|
219
|
+
|
|
220
|
+
fibers = []
|
|
221
|
+
fibers << Fiber.new do
|
|
222
|
+
begin
|
|
223
|
+
pool.acquire
|
|
224
|
+
pool.size.times{ pool.do_something(info) } # not releasing!
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
run_em_reactor fibers
|
|
229
|
+
|
|
230
|
+
# we should have used only one instance
|
|
231
|
+
assert_equal 1, info[:instances].count
|
|
232
|
+
# still kept, until cleanup is fired
|
|
233
|
+
assert_equal(1, pool.instance_variable_get(:@reserved).count)
|
|
234
|
+
assert_equal(1, pool.instance_variable_get(:@keep_connection).count)
|
|
235
|
+
|
|
236
|
+
# will fire cleanup (RESERVED_TTL_SECS = 0)
|
|
237
|
+
pool.release
|
|
238
|
+
|
|
239
|
+
# nothing left
|
|
240
|
+
assert_equal(0, pool.instance_variable_get(:@reserved).count)
|
|
241
|
+
assert_equal(0, pool.instance_variable_get(:@keep_connection).count)
|
|
242
|
+
|
|
243
|
+
ensure
|
|
244
|
+
# restore
|
|
245
|
+
force_constant FiberConnectionPool, :RESERVED_TTL_SECS, prev_ttl
|
|
246
|
+
end
|
|
167
247
|
|
|
168
248
|
def test_save_data
|
|
169
249
|
# create pool, run fibers and gather info
|
|
@@ -199,6 +279,22 @@ class TestFiberConnectionPool < Minitest::Test
|
|
|
199
279
|
# restore
|
|
200
280
|
force_constant FiberConnectionPool, :SAVED_DATA_TTL_SECS, prev_ttl
|
|
201
281
|
end
|
|
282
|
+
|
|
283
|
+
def test_round_robin
|
|
284
|
+
info = { :instances => [].to_set }
|
|
285
|
+
|
|
286
|
+
# get pool and fibers
|
|
287
|
+
pool = FiberConnectionPool.new(:size => 4) { ::EMSynchronyConnection.new(:delay => 0.05) }
|
|
288
|
+
|
|
289
|
+
fiber = Fiber.new do
|
|
290
|
+
pool.size.times{ pool.do_something(info) }
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
run_em_reactor [ fiber ]
|
|
294
|
+
|
|
295
|
+
# we should have passed through every instance
|
|
296
|
+
assert_equal pool.size, info[:instances].count
|
|
297
|
+
end
|
|
202
298
|
|
|
203
299
|
private
|
|
204
300
|
|
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.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: bin
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date: 2013-09-
|
|
13
|
+
date: 2013-09-25 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: minitest
|