em-pg-client-12 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,89 @@
1
+ module PG
2
+ module EM
3
+ class Client < PG::Connection
4
+
5
+ # This module is used as a handler to ::EM.watch connection socket and
6
+ # it performs connection handshake with postgres server asynchronously.
7
+ #
8
+ # Author:: Rafal Michalski
9
+ module ConnectWatcher
10
+
11
+ def initialize(client, deferrable, is_reset)
12
+ @client = client
13
+ @deferrable = deferrable
14
+ @is_reset = is_reset
15
+ @poll_method = is_reset ? :reset_poll : :connect_poll
16
+ if (timeout = client.connect_timeout) > 0
17
+ @timer = ::EM::Timer.new(timeout) do
18
+ detach
19
+ @deferrable.protect do
20
+ @client.raise_error ConnectionBad, "timeout expired (async)"
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def reconnecting?
27
+ @is_reset
28
+ end
29
+
30
+ def poll_connection_and_check
31
+ case @client.__send__(@poll_method)
32
+ when PG::PGRES_POLLING_READING
33
+ self.notify_readable = true
34
+ self.notify_writable = false
35
+ return
36
+ when PG::PGRES_POLLING_WRITING
37
+ self.notify_writable = true
38
+ self.notify_readable = false
39
+ return
40
+ when PG::PGRES_POLLING_OK
41
+ polling_ok = true if @client.status == PG::CONNECTION_OK
42
+ end
43
+ @timer.cancel if @timer
44
+ detach
45
+ @deferrable.protect do
46
+ @client.raise_error ConnectionBad unless polling_ok
47
+ @client.set_default_encoding unless reconnecting?
48
+ if on_connect = @client.on_connect
49
+ succeed_connection_with_hook(on_connect)
50
+ else
51
+ succeed_connection
52
+ end
53
+ end
54
+ end
55
+
56
+ def succeed_connection
57
+ ::EM.next_tick { @deferrable.succeed @client }
58
+ end
59
+
60
+ def succeed_connection_with_hook(on_connect)
61
+ ::EM.next_tick do
62
+ Fiber.new do
63
+ # call on_connect handler and fail if it raises an error
64
+ begin
65
+ returned_df = on_connect.call(@client, true, reconnecting?)
66
+ rescue => ex
67
+ @deferrable.fail ex
68
+ else
69
+ if returned_df.respond_to?(:callback) && returned_df.respond_to?(:errback)
70
+ # the handler returned a deferrable
71
+ returned_df.callback { @deferrable.succeed @client }
72
+ # fail when handler's deferrable fails
73
+ returned_df.errback { |ex| @deferrable.fail ex }
74
+ else
75
+ @deferrable.succeed @client
76
+ end
77
+ end
78
+ end.resume
79
+ end
80
+ end
81
+
82
+ alias_method :notify_writable, :poll_connection_and_check
83
+ alias_method :notify_readable, :poll_connection_and_check
84
+
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,204 @@
1
+ module PG
2
+ module EM
3
+ class Client < PG::Connection
4
+
5
+ # This module is used as a handler to ::EM.watch connection socket and
6
+ # it extracts query results in a non-blocking manner.
7
+ #
8
+ # Author:: Rafal Michalski
9
+ module Watcher
10
+
11
+ def initialize(client)
12
+ @client = client
13
+ @is_connected = true
14
+ @one_result_mode = false
15
+ @deferrable = nil
16
+ @notify_deferrable = nil
17
+ @timer = nil
18
+ @notify_timer = nil
19
+ end
20
+
21
+ def watching?
22
+ @is_connected
23
+ end
24
+
25
+ def one_result_mode?
26
+ @one_result_mode
27
+ end
28
+
29
+ def watch_results(deferrable, send_proc = nil, one_result_mode = false)
30
+ @one_result_mode = one_result_mode
31
+ @last_result = nil
32
+ @deferrable = deferrable
33
+ @send_proc = send_proc
34
+ cancel_timer
35
+ self.notify_readable = true unless notify_readable?
36
+ if (timeout = @client.query_timeout) > 0
37
+ @readable_timestamp = Time.now
38
+ setup_timer timeout
39
+ end
40
+ fetch_results
41
+ end
42
+
43
+ def watch_notify(deferrable, timeout = nil)
44
+ notify_df = @notify_deferrable
45
+ @notify_deferrable = deferrable
46
+ cancel_notify_timer
47
+ self.notify_readable = true unless notify_readable?
48
+ if timeout
49
+ @notify_timer = ::EM::Timer.new(timeout) do
50
+ @notify_timer = nil
51
+ succeed_notify
52
+ end
53
+ end
54
+ notify_df.fail nil if notify_df
55
+ check_notify
56
+ end
57
+
58
+ def setup_timer(timeout, adjustment = 0)
59
+ @timer = ::EM::Timer.new(timeout - adjustment) do
60
+ if (last_interval = Time.now - @readable_timestamp) >= timeout
61
+ @timer = nil
62
+ cancel_notify_timer
63
+ self.notify_readable = false
64
+ @client.async_command_aborted = true
65
+ @send_proc = nil
66
+ begin
67
+ @client.raise_error ConnectionBad, "query timeout expired (async)"
68
+ rescue Exception => e
69
+ fail_result e
70
+ # notify should also fail: query timeout is like connection error
71
+ fail_notify e
72
+ end
73
+ else
74
+ setup_timer timeout, last_interval
75
+ end
76
+ end
77
+ end
78
+
79
+ def cancel_notify_timer
80
+ if @notify_timer
81
+ @notify_timer.cancel
82
+ @notify_timer = nil
83
+ end
84
+ end
85
+
86
+ def cancel_timer
87
+ if @timer
88
+ @timer.cancel
89
+ @timer = nil
90
+ end
91
+ end
92
+
93
+ def notify_readable
94
+ @client.consume_input
95
+ rescue Exception => e
96
+ handle_error e
97
+ else
98
+ fetch_results if @deferrable
99
+ check_notify if @notify_deferrable
100
+ end
101
+
102
+ def check_notify
103
+ if notify_hash = @client.notifies
104
+ cancel_notify_timer
105
+ succeed_notify notify_hash
106
+ end
107
+ end
108
+
109
+ # Carefully extract results without
110
+ # blocking the EventMachine reactor.
111
+ def fetch_results
112
+ result = false
113
+ until @client.is_busy
114
+ single_result = @client.blocking_get_result
115
+ if one_result_mode?
116
+ result = single_result
117
+ break
118
+ elsif single_result.nil?
119
+ if result = @last_result
120
+ result.check
121
+ end
122
+ break
123
+ end
124
+ @last_result.clear if @last_result
125
+ @last_result = single_result
126
+ end
127
+ rescue Exception => e
128
+ handle_error e
129
+ else
130
+ if result == false
131
+ @readable_timestamp = Time.now if @timer
132
+ else
133
+ cancel_timer
134
+ self.notify_readable = false unless @notify_deferrable
135
+ df = @deferrable
136
+ @deferrable = @send_proc = nil
137
+ df.succeed result
138
+ end
139
+ end
140
+
141
+ def unbind
142
+ @is_connected = false
143
+ cancel_timer
144
+ cancel_notify_timer
145
+ if @deferrable || @notify_deferrable
146
+ @client.raise_error ConnectionBad, "connection reset"
147
+ end
148
+ rescue Exception => e
149
+ fail_result e
150
+ fail_notify e
151
+ end
152
+
153
+ private
154
+
155
+ def fail_result(e)
156
+ df = @deferrable
157
+ @deferrable = nil
158
+ df.fail e if df
159
+ end
160
+
161
+ def succeed_notify(notify_hash = nil)
162
+ self.notify_readable = false unless @deferrable
163
+ df = @notify_deferrable
164
+ @notify_deferrable = nil
165
+ df.succeed notify_hash
166
+ end
167
+
168
+ def fail_notify(e)
169
+ df = @notify_deferrable
170
+ @notify_deferrable = nil
171
+ df.fail e if df
172
+ end
173
+
174
+ def handle_error(e)
175
+ cancel_timer
176
+ send_proc = @send_proc
177
+ @send_proc = nil
178
+ df = @deferrable || FeaturedDeferrable.new
179
+ # prevent unbind error on auto re-connect
180
+ @deferrable = nil
181
+ notify_df = @notify_deferrable
182
+ self.notify_readable = false unless notify_df
183
+ if e.is_a?(PG::Error)
184
+ @client.async_autoreconnect!(df, e, send_proc) do
185
+ # there was a connection error so stop any remaining activity
186
+ if notify_df
187
+ @notify_deferrable = nil
188
+ cancel_notify_timer
189
+ self.notify_readable = false
190
+ # fail notify_df after deferrable completes
191
+ # handler might setup listen again then immediately
192
+ df.completion { notify_df.fail e }
193
+ end
194
+ end
195
+ else
196
+ df.fail e
197
+ end
198
+ end
199
+
200
+ end
201
+
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,480 @@
1
+ require 'pg/em'
2
+
3
+ module PG
4
+ module EM
5
+
6
+ # Connection pool for PG::EM::Client
7
+ #
8
+ # Author:: Rafal Michalski
9
+ #
10
+ # The ConnectionPool allocates new connections asynchronously when
11
+ # there are no free connections left up to the {#max_size} number.
12
+ #
13
+ # If {Client#async_autoreconnect} option is not set or the re-connect fails
14
+ # the failed connection is dropped from the pool.
15
+ #
16
+ # @example Basic usage
17
+ # pg = PG::EM::ConnectionPool.new size: 10, dbname: 'foo'
18
+ # res = pg.query 'select * from bar'
19
+ #
20
+ # The list of {Client} command methods that are available in {ConnectionPool}:
21
+ #
22
+ # Fiber synchronized methods:
23
+ #
24
+ # * {Client#exec}
25
+ # * {Client#query}
26
+ # * {Client#async_exec}
27
+ # * {Client#async_query}
28
+ # * {Client#exec_params}
29
+ # * {Client#exec_prepared}
30
+ # * {Client#prepare}
31
+ # * {Client#describe_prepared}
32
+ # * {Client#describe_portal}
33
+ #
34
+ # The asynchronous command methods:
35
+ #
36
+ # * {Client#exec_defer}
37
+ # * {Client#query_defer}
38
+ # * {Client#async_exec_defer}
39
+ # * {Client#async_query_defer}
40
+ # * {Client#exec_params_defer}
41
+ # * {Client#exec_prepared_defer}
42
+ # * {Client#prepare_defer}
43
+ # * {Client#describe_prepared_defer}
44
+ # * {Client#describe_portal_defer}
45
+ #
46
+ # The pool will only allow for {#max_size} commands (both deferred and
47
+ # fiber synchronized) to be performed concurrently. The pending requests
48
+ # will be queued and executed when connections become available.
49
+ #
50
+ # Please keep in mind, that the above methods may send commands to
51
+ # different clients from the pool each time they are called. You can't
52
+ # assume anything about which connection is acquired even if the
53
+ # {#max_size} of the pool is set to one. This is because no connection
54
+ # will be shared between two concurrent requests and the connections
55
+ # maight occasionally fail and they will be dropped from the pool.
56
+ #
57
+ # This prevents the `*_defer` commands to execute transactions.
58
+ #
59
+ # For transactions use {#transaction} and fiber synchronized methods.
60
+ class ConnectionPool
61
+
62
+ DEFAULT_SIZE = 4
63
+
64
+ # Maximum number of connections in the connection pool
65
+ # @return [Integer]
66
+ attr_reader :max_size
67
+
68
+ attr_reader :available, :allocated
69
+
70
+ # Creates and initializes a new connection pool.
71
+ #
72
+ # The connection pool allocates its first connection upon initialization
73
+ # unless +lazy: true+ option is given.
74
+ #
75
+ # Pass PG::EM::Client +options+ together with ConnectionPool +options+:
76
+ #
77
+ # - +:size+ = +4+ - the maximum number of concurrent connections
78
+ # - +:lazy+ = false - should lazy allocate first connection
79
+ # - +:connection_class+ = {PG::EM::Client}
80
+ #
81
+ # For convenience the given block will be set as the +on_connect+ option.
82
+ #
83
+ # @yieldparam pg [Client] connected client instance on each newly
84
+ # created connection
85
+ # @yieldparam is_async [Boolean] always +true+ in a connection pool
86
+ # context
87
+ # @yieldparam is_reset [Boolean] always +false+ unless
88
+ # +async_autoreconnect+ options is +true+ and
89
+ # was actually re-connecting
90
+ #
91
+ # @raise [PG::Error]
92
+ # @raise [ArgumentError]
93
+ # @see Client#on_connect
94
+ def initialize(options = {}, &on_connect)
95
+ @available = []
96
+ @pending = []
97
+ @allocated = {}
98
+ @max_size = DEFAULT_SIZE
99
+ @connection_class = Client
100
+
101
+ if block_given?
102
+ options = {on_connect: on_connect}.merge(options)
103
+ end
104
+
105
+ lazy = false
106
+ @options = options.reject do |key, value|
107
+ case key.to_sym
108
+ when :size, :max_size
109
+ @max_size = value.to_i
110
+ true
111
+ when :connection_class
112
+ @connection_class = value
113
+ true
114
+ when :lazy
115
+ lazy = value
116
+ true
117
+ end
118
+ end
119
+
120
+ raise ArgumentError, "#{self.class}.new: pool size must be >= 1" if @max_size < 1
121
+
122
+ # allocate first connection, unless we are lazy
123
+ hold unless lazy
124
+ end
125
+
126
+ # Creates and initializes new connection pool.
127
+ #
128
+ # Attempts to establish the first connection asynchronously.
129
+ #
130
+ # @return [FeaturedDeferrable]
131
+ # @yieldparam pg [Client|PG::Error] new and connected client instance
132
+ # on success or a raised PG::Error
133
+ #
134
+ # Use the returned deferrable's +callback+ hook to obtain newly created
135
+ # {ConnectionPool}.
136
+ # In case of a connection error +errback+ hook is called with
137
+ # a raised error object as its argument.
138
+ #
139
+ # If the block is provided it's bound to both +callback+ and +errback+
140
+ # hooks of the returned deferrable.
141
+ #
142
+ # Pass PG::EM::Client +options+ together with ConnectionPool +options+:
143
+ #
144
+ # - +:size+ = +4+ - the maximum number of concurrent connections
145
+ # - +:connection_class+ = {PG::EM::Client}
146
+ #
147
+ # @raise [ArgumentError]
148
+ def self.connect_defer(options = {}, &blk)
149
+ pool = new options.merge(lazy: true)
150
+ pool.__send__(:hold_deferred, blk) do
151
+ ::EM::DefaultDeferrable.new.tap { |df| df.succeed pool }
152
+ end
153
+ end
154
+
155
+ class << self
156
+ alias_method :connect, :new
157
+ alias_method :async_connect, :connect_defer
158
+ end
159
+
160
+ # Current number of connections in the connection pool
161
+ #
162
+ # @return [Integer]
163
+ def size
164
+ @available.length + @allocated.length
165
+ end
166
+
167
+ # Finishes all available connections and clears the available pool.
168
+ #
169
+ # After call to this method the pool is still usable and will try to
170
+ # allocate new client connections on subsequent query commands.
171
+ def finish
172
+ @available.each { |c| c.finish }
173
+ @available.clear
174
+ self
175
+ end
176
+
177
+ alias_method :close, :finish
178
+
179
+ class DeferredOptions < Hash
180
+ def apply(conn)
181
+ each_pair { |n,v| conn.__send__(n, v) }
182
+ end
183
+ end
184
+ # @!attribute [rw] connect_timeout
185
+ # @return [Float] connection timeout in seconds
186
+ # Set {Client#connect_timeout} on all present and future connections
187
+ # in this pool or read value from options
188
+ # @!attribute [rw] query_timeout
189
+ # @return [Float] query timeout in seconds
190
+ # Set {Client#query_timeout} on all present and future connections
191
+ # in this pool or read value from options
192
+ # @!attribute [rw] async_autoreconnect
193
+ # @return [Boolean] asynchronous auto re-connect status
194
+ # Set {Client#async_autoreconnect} on all present and future connections
195
+ # in this pool or read value from options
196
+ # @!attribute [rw] on_connect
197
+ # @return [Proc<Client,is_async,is_reset>] connect hook
198
+ # Set {Client#on_connect} on all present and future connections
199
+ # in this pool or read value from options
200
+ # @!attribute [rw] on_autoreconnect
201
+ # @return [Proc<Client, Error>] auto re-connect hook
202
+ # Set {Client#on_autoreconnect} on all present and future connections
203
+ # in this pool or read value from options
204
+ %w[connect_timeout
205
+ query_timeout
206
+ async_autoreconnect
207
+ on_connect
208
+ on_autoreconnect].each do |name|
209
+ class_eval <<-EOD, __FILE__, __LINE__
210
+ def #{name}=(value)
211
+ @options[:#{name}] = value
212
+ b = proc { |c| c.#{name} = value }
213
+ @available.each(&b)
214
+ @allocated.each_value(&b)
215
+ end
216
+ EOD
217
+ if name.start_with?('on_')
218
+ class_eval <<-EOD, __FILE__, __LINE__
219
+ def #{name}(&hook)
220
+ if block_given?
221
+ self.#{name} = hook
222
+ else
223
+ @options[:#{name}] || @options['#{name}']
224
+ end
225
+ end
226
+ EOD
227
+ else
228
+ class_eval <<-EOD, __FILE__, __LINE__
229
+ def #{name}
230
+ @options[:#{name}] || @options['#{name}']
231
+ end
232
+ EOD
233
+ end
234
+ DeferredOptions.class_eval <<-EOD, __FILE__, __LINE__
235
+ def #{name}=(value)
236
+ self[:#{name}=] = value
237
+ end
238
+ EOD
239
+ end
240
+
241
+ %w(
242
+ exec
243
+ query
244
+ async_exec
245
+ async_query
246
+ exec_params
247
+ exec_prepared
248
+ prepare
249
+ describe_prepared
250
+ describe_portal
251
+ ).each do |name|
252
+
253
+ class_eval <<-EOD, __FILE__, __LINE__
254
+ def #{name}(*args, &blk)
255
+ hold { |c| c.#{name}(*args, &blk) }
256
+ end
257
+ EOD
258
+ end
259
+
260
+ %w(
261
+ exec_defer
262
+ query_defer
263
+ async_query_defer
264
+ async_exec_defer
265
+ exec_params_defer
266
+ exec_prepared_defer
267
+ prepare_defer
268
+ describe_prepared_defer
269
+ describe_portal_defer
270
+ ).each do |name|
271
+
272
+ class_eval <<-EOD, __FILE__, __LINE__
273
+ def #{name}(*args, &blk)
274
+ hold_deferred(blk) { |c| c.#{name}(*args) }
275
+ end
276
+ EOD
277
+ end
278
+
279
+ # Executes a BEGIN at the start of the block
280
+ # and a COMMIT at the end of the block
281
+ # or ROLLBACK if any exception occurs.
282
+ # Calls to transaction may be nested,
283
+ # however without sub-transactions (save points).
284
+ #
285
+ # @example Transactions
286
+ # pg = PG::EM::ConnectionPool.new size: 10
287
+ # pg.transaction do
288
+ # pg.exec('insert into animals (family, species) values ($1,$2)',
289
+ # [family, species])
290
+ # num = pg.query('select count(*) from people where family=$1',
291
+ # [family]).get_value(0,0)
292
+ # pg.exec('update stats set count = $1 where family=$2',
293
+ # [num, family])
294
+ # end
295
+ #
296
+ # @see Client#transaction
297
+ # @see #hold
298
+ def transaction(&blk)
299
+ hold do |pg|
300
+ pg.transaction(&blk)
301
+ end
302
+ end
303
+
304
+ # Acquires {Client} connection and passes it to the given block.
305
+ #
306
+ # The connection is allocated to the current fiber and ensures that
307
+ # any subsequent query from the same fiber will be performed on
308
+ # the connection.
309
+ #
310
+ # It is possible to nest hold calls from the same fiber,
311
+ # so each time the block will be given the same {Client} instance.
312
+ # This feature is needed e.g. for nesting transaction calls.
313
+ # @yieldparam [Client] pg
314
+ def hold
315
+ fiber = Fiber.current
316
+ id = fiber.object_id
317
+
318
+ if conn = @allocated[id]
319
+ skip_release = true
320
+ else
321
+ conn = acquire(fiber) until conn
322
+ end
323
+
324
+ begin
325
+ yield conn if block_given?
326
+
327
+ rescue PG::Error
328
+ if conn.status != PG::CONNECTION_OK
329
+ conn.finish unless conn.finished?
330
+ drop_failed(id)
331
+ skip_release = true
332
+ end
333
+ raise
334
+ ensure
335
+ release(id) unless skip_release
336
+ end
337
+ end
338
+
339
+ alias_method :execute, :hold
340
+
341
+ def method_missing(*a, &b)
342
+ hold { |c| c.__send__(*a, &b) }
343
+ end
344
+
345
+ def respond_to_missing?(m, priv = false)
346
+ hold { |c| c.respond_to?(m, priv) }
347
+ end
348
+
349
+ private
350
+
351
+ # Get available connection or create a new one, or put on hold
352
+ # @return [Client] on success
353
+ # @return [nil] when dropped connection creates a free slot
354
+ def acquire(fiber)
355
+ if conn = @available.pop
356
+ @allocated[fiber.object_id] = conn
357
+ else
358
+ if size < max_size
359
+ begin
360
+ id = fiber.object_id
361
+ # mark allocated pool for proper #size value
362
+ # the connection is made asynchronously
363
+ @allocated[id] = opts = DeferredOptions.new
364
+ conn = @connection_class.new(@options)
365
+ ensure
366
+ if conn
367
+ opts.apply conn
368
+ @allocated[id] = conn
369
+ else
370
+ drop_failed(id)
371
+ end
372
+ end
373
+ else
374
+ @pending << fiber
375
+ Fiber.yield
376
+ end
377
+ end
378
+ end
379
+
380
+ # Asynchronously acquires {Client} connection and passes it to the
381
+ # given block on success.
382
+ #
383
+ # The block will receive the acquired connection as its argument and
384
+ # should return a deferrable object which is either returned from
385
+ # this method or is being status-bound to another deferrable returned
386
+ # from this method.
387
+ #
388
+ # @param blk [Proc] optional block passed to +callback+ and +errback+
389
+ # of the returned deferrable object
390
+ # @yieldparam pg [Client] a connected client instance
391
+ # @yieldreturn [EM::Deferrable]
392
+ # @return [EM::Deferrable]
393
+ def hold_deferred(blk = nil)
394
+ if conn = @available.pop
395
+ id = conn.object_id
396
+ @allocated[id] = conn
397
+ df = yield conn
398
+ else
399
+ df = FeaturedDeferrable.new
400
+ id = df.object_id
401
+ acquire_deferred(df) do |nc|
402
+ @allocated[id] = conn = nc
403
+ df.bind_status yield conn
404
+ end
405
+ end
406
+ df.callback { release(id) }
407
+ df.errback do |err|
408
+ if conn
409
+ if err.is_a?(PG::Error) &&
410
+ conn.status != PG::CONNECTION_OK
411
+ conn.finish unless conn.finished?
412
+ drop_failed(id)
413
+ else
414
+ release(id)
415
+ end
416
+ end
417
+ end
418
+ df.completion(&blk) if blk
419
+ df
420
+ end
421
+
422
+ # Asynchronously create a new connection or get the released one
423
+ #
424
+ # @param df [EM::Deferrable] - the acquiring object and the one to fail
425
+ # when establishing connection fails
426
+ # @return [EM::Deferrable] the deferrable that will succeed with either
427
+ # new or released connection
428
+ def acquire_deferred(df, &blk)
429
+ id = df.object_id
430
+ if size < max_size
431
+ # mark allocated pool for proper #size value
432
+ # the connection is made asynchronously
433
+ @allocated[id] = opts = DeferredOptions.new
434
+ @connection_class.connect_defer(@options).callback {|conn|
435
+ opts.apply conn
436
+ }.errback do |err|
437
+ drop_failed(id)
438
+ df.fail(err)
439
+ end
440
+ else
441
+ @pending << (conn_df = ::EM::DefaultDeferrable.new)
442
+ conn_df.errback do
443
+ # a dropped connection made a free slot
444
+ acquire_deferred(df, &blk)
445
+ end
446
+ end.callback(&blk)
447
+ end
448
+
449
+ # drop a failed connection (or a mark) from the pool and
450
+ # ensure that the pending requests won't starve
451
+ def drop_failed(id)
452
+ @allocated.delete(id)
453
+ if pending = @pending.shift
454
+ if pending.is_a?(Fiber)
455
+ pending.resume
456
+ else
457
+ pending.fail
458
+ end
459
+ end
460
+ end
461
+
462
+ # release connection and pass it to the next pending
463
+ # request or back to the free pool
464
+ def release(id)
465
+ conn = @allocated.delete(id)
466
+ if pending = @pending.shift
467
+ if pending.is_a?(Fiber)
468
+ @allocated[pending.object_id] = conn
469
+ pending.resume conn
470
+ else
471
+ pending.succeed conn
472
+ end
473
+ else
474
+ @available << conn
475
+ end
476
+ end
477
+
478
+ end
479
+ end
480
+ end