em-pg-client-12 0.3.4

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