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.
@@ -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