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