fiber_connection_pool 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,12 +2,14 @@ require 'fiber'
2
2
  require_relative 'fiber_connection_pool/exceptions'
3
3
 
4
4
  class FiberConnectionPool
5
- VERSION = '0.2.5'
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(opts[:size].to_i) { yield }
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.2.5
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-24 00:00:00.000000000 Z
13
+ date: 2013-09-25 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: minitest