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.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.travis.yml +32 -0
- data/.yardopts +1 -0
- data/BENCHMARKS.md +91 -0
- data/Gemfile +5 -0
- data/HISTORY.md +107 -0
- data/LICENSE +21 -0
- data/README.md +456 -0
- data/Rakefile +157 -0
- data/benchmarks/em_pg.rb +96 -0
- data/benchmarks/single_row_mode.rb +88 -0
- data/em-pg-client.gemspec +34 -0
- data/examples/single_row_mode.rb +57 -0
- data/lib/em-pg-client.rb +1 -0
- data/lib/em-synchrony/pg.rb +3 -0
- data/lib/pg/em-version.rb +5 -0
- data/lib/pg/em.rb +1129 -0
- data/lib/pg/em/client/connect_watcher.rb +89 -0
- data/lib/pg/em/client/watcher.rb +204 -0
- data/lib/pg/em/connection_pool.rb +480 -0
- data/lib/pg/em/featured_deferrable.rb +43 -0
- data/spec/connection_pool_helpers.rb +89 -0
- data/spec/em_client.rb +33 -0
- data/spec/em_client_autoreconnect.rb +672 -0
- data/spec/em_client_common.rb +619 -0
- data/spec/em_client_on_connect.rb +171 -0
- data/spec/em_connection_pool.rb +200 -0
- data/spec/em_synchrony_client.rb +787 -0
- data/spec/em_synchrony_client_autoreconnect.rb +560 -0
- 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 +133 -0
- data/spec/pg_em_connection_pool.rb +679 -0
- data/spec/pg_em_featured_deferrable.rb +125 -0
- data/spec/spec_helper.rb +9 -0
- metadata +187 -0
data/lib/pg/em.rb
ADDED
@@ -0,0 +1,1129 @@
|
|
1
|
+
require 'fiber'
|
2
|
+
begin
|
3
|
+
require 'pg'
|
4
|
+
rescue LoadError => error
|
5
|
+
raise 'Missing pg driver: gem install pg'
|
6
|
+
end
|
7
|
+
unless defined? EventMachine
|
8
|
+
begin
|
9
|
+
require 'eventmachine'
|
10
|
+
rescue LoadError => error
|
11
|
+
raise 'Missing EventMachine: gem install eventmachine'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
require 'pg/em-version'
|
15
|
+
require 'pg/em/featured_deferrable'
|
16
|
+
require 'pg/em/client/watcher'
|
17
|
+
require 'pg/em/client/connect_watcher'
|
18
|
+
|
19
|
+
module PG
|
20
|
+
module EM
|
21
|
+
ROOT_FIBER = Fiber.current
|
22
|
+
|
23
|
+
# == PostgreSQL EventMachine client
|
24
|
+
#
|
25
|
+
# Author:: Rafal Michalski
|
26
|
+
#
|
27
|
+
# {PG::EM::Client} is a PG::Connection[http://deveiate.org/code/pg/PG/Connection.html]
|
28
|
+
# wrapper designed for EventMachine[http://rubyeventmachine.com/].
|
29
|
+
#
|
30
|
+
# The following new methods:
|
31
|
+
#
|
32
|
+
# - {#exec_defer} (alias: +query_defer+)
|
33
|
+
# - {#exec_params_defer}
|
34
|
+
# - {#prepare_defer}
|
35
|
+
# - {#exec_prepared_defer}
|
36
|
+
# - {#describe_prepared_defer}
|
37
|
+
# - {#describe_portal_defer}
|
38
|
+
# - {#get_result_defer}
|
39
|
+
# - {#get_last_result_defer}
|
40
|
+
#
|
41
|
+
# are added to execute queries asynchronously,
|
42
|
+
# returning +Deferrable+ object.
|
43
|
+
#
|
44
|
+
# The following methods of PG::Connection[http://deveiate.org/code/pg/PG/Connection.html]
|
45
|
+
# are overloaded:
|
46
|
+
#
|
47
|
+
# - {#exec} (alias: +query+, +async_exec+, +async_query+)
|
48
|
+
# - {#exec_params}
|
49
|
+
# - {#prepare}
|
50
|
+
# - {#exec_prepared}
|
51
|
+
# - {#describe_prepared}
|
52
|
+
# - {#describe_portal}
|
53
|
+
# - {#get_result}
|
54
|
+
# - {#get_last_result}
|
55
|
+
#
|
56
|
+
# and are now auto-detecting if EventMachine is running and
|
57
|
+
# performing commands asynchronously (blocking only current fiber) or
|
58
|
+
# calling parent thread-blocking methods.
|
59
|
+
#
|
60
|
+
# If {#async_autoreconnect} option is set to +true+, all of the above
|
61
|
+
# methods (in asynchronous mode) try to re-connect after a connection
|
62
|
+
# error occurs. It's performed behind the scenes, so no error is raised,
|
63
|
+
# except if there was a transaction in progress. In such instance the error
|
64
|
+
# is raised after establishing connection to signal that
|
65
|
+
# the transaction was aborted.
|
66
|
+
#
|
67
|
+
# If you want to detect auto re-connect event use {#on_autoreconnect}
|
68
|
+
# property/option.
|
69
|
+
#
|
70
|
+
# To enable auto-reconnecting set:
|
71
|
+
# client.async_autoreconnect = true
|
72
|
+
#
|
73
|
+
# or pass as {new} hash argument:
|
74
|
+
# PG::EM::Client.new dbname: 'bar', async_autoreconnect: true
|
75
|
+
#
|
76
|
+
# There are also new methods:
|
77
|
+
#
|
78
|
+
# - {Client.connect_defer}
|
79
|
+
# - {#reset_defer}
|
80
|
+
#
|
81
|
+
# which are asynchronous versions of PG::Connection.new and
|
82
|
+
# PG:Connection#reset.
|
83
|
+
#
|
84
|
+
# Additionally the following methods are overloaded:
|
85
|
+
#
|
86
|
+
# - {new} (alias: +connect+, +open+, +setdb+, +setdblogin+ )
|
87
|
+
# - {#reset}
|
88
|
+
#
|
89
|
+
# providing auto-detecting asynchronous (fiber-synchronized) or
|
90
|
+
# thread-blocking methods for (re)connecting.
|
91
|
+
#
|
92
|
+
# Otherwise nothing changes in PG::Connection API.
|
93
|
+
# See PG::Connection[http://deveiate.org/code/pg/PG/Connection.html] docs
|
94
|
+
# for explanation of arguments to the above methods.
|
95
|
+
#
|
96
|
+
# *Warning:*
|
97
|
+
#
|
98
|
+
# {#describe_prepared} and {#exec_prepared} after
|
99
|
+
# {#prepare} should only be invoked on the *same* connection.
|
100
|
+
# If you are using a {ConnectionPool}, make sure to acquire a single
|
101
|
+
# connection first or execute +prepare+ command on every connection
|
102
|
+
# using +#on_connect+ hook.
|
103
|
+
#
|
104
|
+
class Client < PG::Connection
|
105
|
+
|
106
|
+
# @!attribute connect_timeout
|
107
|
+
# @return [Float] connection timeout in seconds
|
108
|
+
# Connection timeout. Affects {#reset} and {#reset_defer}.
|
109
|
+
#
|
110
|
+
# Changing this property does not affect thread-blocking {#reset} unless
|
111
|
+
# passed as a +connection_hash+.
|
112
|
+
#
|
113
|
+
# To enable it set to some positive value. To disable it: set to 0.
|
114
|
+
#
|
115
|
+
# You may set +:connect_timeout+ in a +connection_hash+ argument
|
116
|
+
# passed to {new} or {connect_defer}.
|
117
|
+
attr_accessor :connect_timeout
|
118
|
+
|
119
|
+
# @!attribute query_timeout
|
120
|
+
# @return [Float] query timeout in seconds
|
121
|
+
# Aborts async command processing if server response time
|
122
|
+
# exceedes +query_timeout+ seconds. This does not apply to
|
123
|
+
# {#reset} and {#reset_defer}.
|
124
|
+
#
|
125
|
+
# To enable it set to some positive value. To disable it: set to 0.
|
126
|
+
#
|
127
|
+
# You may set +:query_timeout+ in a +connection_hash+ argument
|
128
|
+
# passed to {new} or {connect_defer}.
|
129
|
+
attr_accessor :query_timeout
|
130
|
+
|
131
|
+
# @!attribute async_autoreconnect
|
132
|
+
# @return [Boolean] asynchronous auto re-connect status
|
133
|
+
# Enable/disable auto re-connect feature (+true+/+false+).
|
134
|
+
# Defaults to +false+ unless {#on_autoreconnect} is specified
|
135
|
+
# in a +connection_hash+.
|
136
|
+
#
|
137
|
+
# Changing {#on_autoreconnect} with accessor method doesn't change
|
138
|
+
# the state of {#async_autoreconnect}.
|
139
|
+
#
|
140
|
+
# You may set +:async_autoreconnect+ in a +connection_hash+ argument
|
141
|
+
# passed to {new} or {connect_defer}.
|
142
|
+
attr_accessor :async_autoreconnect
|
143
|
+
|
144
|
+
# @!attribute on_autoreconnect
|
145
|
+
# @return [Proc<Client, Error>] auto re-connect hook
|
146
|
+
# A proc like object that is being called after a connection with the
|
147
|
+
# server has been automatically re-established. It's being invoked
|
148
|
+
# just before the pending command is sent to the server.
|
149
|
+
#
|
150
|
+
# @yieldparam pg [Client] re-connected client instance
|
151
|
+
# @yieldparam error [Exception] an error after which the auto re-connect
|
152
|
+
# process began.
|
153
|
+
# @yieldreturn [false|true|Exception|EM::Deferrable|*]
|
154
|
+
#
|
155
|
+
# The first argument it receives is the connected {Client} instance.
|
156
|
+
# The second is the original +error+ that caused the reconnecting
|
157
|
+
# process.
|
158
|
+
#
|
159
|
+
# It's possible to execute queries from the +on_autoreconnect+ hook.
|
160
|
+
# Code is being executed in a fiber context, so both deferrable and
|
161
|
+
# fiber-synchronized query commands may be used.
|
162
|
+
#
|
163
|
+
# If exception is raised during execution of the +on_autoreconnect+
|
164
|
+
# hook the reset operation will fail with that exception.
|
165
|
+
#
|
166
|
+
# The hook can control later actions with its return value:
|
167
|
+
#
|
168
|
+
# - +false+ (explicitly, +nil+ is ignored) - the original +exception+
|
169
|
+
# is raised/passed back and the pending query command is not sent
|
170
|
+
# again to the server.
|
171
|
+
# - +true+ (explicitly, truish values are ignored), the pending command
|
172
|
+
# is called regardless of the connection's last transaction status.
|
173
|
+
# - +Exception+ object - is raised/passed back and the pending command
|
174
|
+
# is not sent.
|
175
|
+
# - +Deferrable+ object - the chosen action will depend on the returned
|
176
|
+
# deferrable status.
|
177
|
+
# - Other values are ignored and the pending query command is
|
178
|
+
# immediately sent to the server unless there was a transaction in
|
179
|
+
# progress before the connection was reset.
|
180
|
+
#
|
181
|
+
# If both +on_connect+ and +on_autoreconnect+ hooks are set,
|
182
|
+
# the +on_connect+ is being called first and +on_autoreconnect+ is
|
183
|
+
# called only when +on_connect+ succeeds.
|
184
|
+
#
|
185
|
+
# You may set +:on_autoreconnect+ hook in a +connection_hash+ argument
|
186
|
+
# passed to {new} or {connect_defer}.
|
187
|
+
#
|
188
|
+
# @example How to use deferrable in on_autoreconnect hook
|
189
|
+
# pg.on_autoreconnect do |pg, e|
|
190
|
+
# logger.warn "PG connection was reset: #{e.inspect}, delaying 1 sec."
|
191
|
+
# EM::DefaultDeferrable.new.tap do |df|
|
192
|
+
# EM.add_timer(1) { df.succeed }
|
193
|
+
# end
|
194
|
+
# end
|
195
|
+
#
|
196
|
+
attr_writer :on_autoreconnect
|
197
|
+
|
198
|
+
def on_autoreconnect(&hook)
|
199
|
+
if block_given?
|
200
|
+
@on_autoreconnect = hook
|
201
|
+
else
|
202
|
+
@on_autoreconnect
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# @!attribute on_connect
|
207
|
+
# @return [Proc<Client,is_async,is_reset>] connect hook
|
208
|
+
# A proc like object that is being called after a connection with
|
209
|
+
# the server has been established.
|
210
|
+
#
|
211
|
+
# @yieldparam pg [Client] connected client instance
|
212
|
+
# @yieldparam is_async [Boolean] flag indicating if the connection
|
213
|
+
# was established asynchronously
|
214
|
+
# @yieldparam is_reset [Boolean] flag indicating if the connection
|
215
|
+
# client was reset
|
216
|
+
# @yieldreturn [EM::Deferrable|*]
|
217
|
+
#
|
218
|
+
# The first argument it receives is the connected {Client} instance.
|
219
|
+
# The second argument is +true+ if the connection was established in
|
220
|
+
# asynchronous manner, +false+ otherwise.
|
221
|
+
# The third argument is +true+ when the connection has been reset or
|
222
|
+
# +false+ on new connection.
|
223
|
+
#
|
224
|
+
# It's possible to execute queries from the +on_connect+ hook.
|
225
|
+
# Code is being executed in a fiber context, so both deferrable and
|
226
|
+
# fiber-synchronized query commands may be used.
|
227
|
+
# However deferrable commands will work only if eventmachine reactor
|
228
|
+
# is running, so check if +is_async+ is +true+.
|
229
|
+
#
|
230
|
+
# If exception is raised during execution of the +on_connect+ hook
|
231
|
+
# the connecting/reset operation will fail with that exception.
|
232
|
+
#
|
233
|
+
# The hook can control later actions with its return value:
|
234
|
+
#
|
235
|
+
# - +Deferrable+ object - the connection establishing status will depend
|
236
|
+
# on the returned deferrable status (only in asynchronous mode).
|
237
|
+
# - Other values are ignored.
|
238
|
+
#
|
239
|
+
# If both +on_connect+ and +on_autoreconnect+ hooks are set,
|
240
|
+
# the +on_connect+ is being called first and +on_autoreconnect+ is
|
241
|
+
# called only when +on_connect+ succeeds.
|
242
|
+
#
|
243
|
+
# You may set +:on_connect+ hook in a +connection_hash+ argument
|
244
|
+
# passed to {new} or {connect_defer}.
|
245
|
+
#
|
246
|
+
# @example How to use prepare in on_connect hook
|
247
|
+
# PG::EM::Client.new(on_connect: proc {|pg|
|
248
|
+
# pg.prepare("species_by_name",
|
249
|
+
# "select id, name from animals where species=$1 order by name")
|
250
|
+
# })
|
251
|
+
#
|
252
|
+
attr_writer :on_connect
|
253
|
+
|
254
|
+
def on_connect(&hook)
|
255
|
+
if block_given?
|
256
|
+
@on_connect = hook
|
257
|
+
else
|
258
|
+
@on_connect
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# @!visibility private
|
263
|
+
# Used internally for marking connection as aborted on query timeout.
|
264
|
+
attr_accessor :async_command_aborted
|
265
|
+
|
266
|
+
# Returns +true+ if +pg+ supports single row mode or +false+ otherwise.
|
267
|
+
# Single row mode is available since +libpq+ 9.2.
|
268
|
+
# @return [Boolean]
|
269
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-set_single_row_mode PG::Connection#set_single_row_mode
|
270
|
+
def self.single_row_mode?
|
271
|
+
method_defined? :set_single_row_mode
|
272
|
+
end
|
273
|
+
|
274
|
+
# Returns +true+ if +pg+ supports single row mode or +false+ otherwise.
|
275
|
+
# @return [Boolean]
|
276
|
+
# @see single_row_mode?
|
277
|
+
def single_row_mode?
|
278
|
+
self.class.single_row_mode?
|
279
|
+
end
|
280
|
+
|
281
|
+
# environment variable name for connect_timeout fallback value
|
282
|
+
@@connect_timeout_envvar = conndefaults.find{|d| d[:keyword] == "connect_timeout" }[:envvar]
|
283
|
+
|
284
|
+
DEFAULT_ASYNC_VARS = {
|
285
|
+
:@async_autoreconnect => nil,
|
286
|
+
:@connect_timeout => nil,
|
287
|
+
:@query_timeout => 0,
|
288
|
+
:@on_connect => nil,
|
289
|
+
:@on_autoreconnect => nil,
|
290
|
+
:@async_command_aborted => false,
|
291
|
+
}.freeze
|
292
|
+
|
293
|
+
# @!visibility private
|
294
|
+
def self.parse_async_options(args)
|
295
|
+
options = DEFAULT_ASYNC_VARS.dup
|
296
|
+
if args.last.is_a? Hash
|
297
|
+
args[-1] = args.last.reject do |key, value|
|
298
|
+
case key.to_sym
|
299
|
+
when :async_autoreconnect
|
300
|
+
options[:@async_autoreconnect] = value
|
301
|
+
true
|
302
|
+
when :on_connect
|
303
|
+
if value.respond_to? :call
|
304
|
+
options[:@on_connect] = value
|
305
|
+
else
|
306
|
+
raise ArgumentError, "on_connect must respond to `call'"
|
307
|
+
end
|
308
|
+
true
|
309
|
+
when :on_reconnect
|
310
|
+
raise ArgumentError, "on_reconnect is no longer supported, use on_autoreconnect"
|
311
|
+
when :on_autoreconnect
|
312
|
+
if value.respond_to? :call
|
313
|
+
options[:@on_autoreconnect] = value
|
314
|
+
options[:@async_autoreconnect] = true if options[:@async_autoreconnect].nil?
|
315
|
+
else
|
316
|
+
raise ArgumentError, "on_autoreconnect must respond to `call'"
|
317
|
+
end
|
318
|
+
true
|
319
|
+
when :connect_timeout
|
320
|
+
options[:@connect_timeout] = value.to_f
|
321
|
+
false
|
322
|
+
when :query_timeout
|
323
|
+
options[:@query_timeout] = value.to_f
|
324
|
+
true
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
options[:@async_autoreconnect] = !!options[:@async_autoreconnect]
|
329
|
+
options[:@connect_timeout] ||= ENV[@@connect_timeout_envvar].to_f
|
330
|
+
options
|
331
|
+
end
|
332
|
+
|
333
|
+
# @!group Deferrable connection methods
|
334
|
+
|
335
|
+
# Attempts to establish the connection asynchronously.
|
336
|
+
#
|
337
|
+
# @return [FeaturedDeferrable]
|
338
|
+
# @yieldparam pg [Client|PG::Error] new and connected client instance on
|
339
|
+
# success or an instance of raised PG::Error
|
340
|
+
#
|
341
|
+
# Pass the block to the returned deferrable's +callback+ to obtain newly
|
342
|
+
# created and already connected {Client} object. In case of connection
|
343
|
+
# error +errback+ hook receives an error object as an argument.
|
344
|
+
# If the block is provided it's bound to both +callback+ and +errback+
|
345
|
+
# hooks of the returned deferrable.
|
346
|
+
#
|
347
|
+
# Special {Client} options (e.g.: {#async_autoreconnect}) must be
|
348
|
+
# provided in a +connection_hash+ argument variant. They will be ignored
|
349
|
+
# if passed in a +connection_string+.
|
350
|
+
#
|
351
|
+
# +client_encoding+ *will* be set according to +Encoding.default_internal+.
|
352
|
+
#
|
353
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-c-new PG::Connection.new
|
354
|
+
def self.connect_defer(*args, &blk)
|
355
|
+
df = FeaturedDeferrable.new(&blk)
|
356
|
+
async_args = parse_async_options(args)
|
357
|
+
conn = df.protect { connect_start(*args) }
|
358
|
+
if conn
|
359
|
+
async_args.each {|k, v| conn.instance_variable_set(k, v) }
|
360
|
+
::EM.watch(conn.socket_io, ConnectWatcher, conn, df, false).
|
361
|
+
poll_connection_and_check
|
362
|
+
end
|
363
|
+
df
|
364
|
+
end
|
365
|
+
|
366
|
+
class << self
|
367
|
+
# @deprecated Use {connect_defer} instead.
|
368
|
+
alias_method :async_connect, :connect_defer
|
369
|
+
end
|
370
|
+
|
371
|
+
# Attempts to reset the connection asynchronously.
|
372
|
+
#
|
373
|
+
# @return [FeaturedDeferrable]
|
374
|
+
# @yieldparam pg [Client|PG::Error] reconnected client instance on
|
375
|
+
# success or an instance of raised PG::Error
|
376
|
+
#
|
377
|
+
# Pass the block to the returned deferrable's +callback+ to execute
|
378
|
+
# after successfull reset.
|
379
|
+
# If the block is provided it's bound to +callback+ and +errback+ hooks
|
380
|
+
# of the returned deferrable.
|
381
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-reset PG::Connection#reset
|
382
|
+
def reset_defer(&blk)
|
383
|
+
@async_command_aborted = false
|
384
|
+
df = FeaturedDeferrable.new(&blk)
|
385
|
+
# there can be only one watch handler over the socket
|
386
|
+
# apparently eventmachine has hard time dealing with more than one
|
387
|
+
if @watcher
|
388
|
+
@watcher.detach if @watcher.watching?
|
389
|
+
@watcher = nil
|
390
|
+
end
|
391
|
+
ret = df.protect(:fail) { reset_start }
|
392
|
+
unless ret == :fail
|
393
|
+
::EM.watch(self.socket_io, ConnectWatcher, self, df, true).
|
394
|
+
poll_connection_and_check
|
395
|
+
end
|
396
|
+
df
|
397
|
+
end
|
398
|
+
|
399
|
+
# @deprecated Use {reset_defer} instead.
|
400
|
+
alias_method :async_reset, :reset_defer
|
401
|
+
|
402
|
+
# @!endgroup
|
403
|
+
|
404
|
+
# @!group Auto-sensing fiber-synchronized connection methods
|
405
|
+
|
406
|
+
# Attempts to reset the connection.
|
407
|
+
#
|
408
|
+
# Performs command asynchronously yielding from current fiber
|
409
|
+
# if EventMachine reactor is running and current fiber isn't the root
|
410
|
+
# fiber. Other fibers can process while waiting for the server to
|
411
|
+
# complete the request.
|
412
|
+
#
|
413
|
+
# Otherwise performs a thread-blocking call to the parent method.
|
414
|
+
#
|
415
|
+
# @raise [PG::Error]
|
416
|
+
# @see #reset_defer
|
417
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-reset PG::Connection#reset
|
418
|
+
def reset
|
419
|
+
if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
|
420
|
+
reset_defer {|r| f.resume(r) }
|
421
|
+
|
422
|
+
conn = Fiber.yield
|
423
|
+
raise conn if conn.is_a?(::Exception)
|
424
|
+
conn
|
425
|
+
else
|
426
|
+
@async_command_aborted = false
|
427
|
+
if @watcher
|
428
|
+
@watcher.detach if @watcher.watching?
|
429
|
+
@watcher = nil
|
430
|
+
end
|
431
|
+
super
|
432
|
+
@on_connect.call(self, false, true) if @on_connect
|
433
|
+
self
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
# Creates new instance of PG::EM::Client and attempts to establish
|
438
|
+
# connection.
|
439
|
+
#
|
440
|
+
# Performs command asynchronously yielding from current fiber
|
441
|
+
# if EventMachine reactor is running and current fiber isn't the root
|
442
|
+
# fiber. Other fibers can process while waiting for the server to
|
443
|
+
# complete the request.
|
444
|
+
#
|
445
|
+
# Otherwise performs a thread-blocking call to the parent method.
|
446
|
+
#
|
447
|
+
# @raise [PG::Error]
|
448
|
+
#
|
449
|
+
# Special {Client} options (e.g.: {#async_autoreconnect}) must be
|
450
|
+
# provided in a +connection_hash+ argument variant. They will be ignored
|
451
|
+
# if passed in a +connection_string+.
|
452
|
+
#
|
453
|
+
# +client_encoding+ *will* be set according to +Encoding.default_internal+.
|
454
|
+
#
|
455
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-c-new PG::Connection.new
|
456
|
+
def self.new(*args, &blk)
|
457
|
+
if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
|
458
|
+
connect_defer(*args) {|r| f.resume(r) }
|
459
|
+
|
460
|
+
conn = Fiber.yield
|
461
|
+
raise conn if conn.is_a?(::Exception)
|
462
|
+
if block_given?
|
463
|
+
begin
|
464
|
+
yield conn
|
465
|
+
ensure
|
466
|
+
conn.finish
|
467
|
+
end
|
468
|
+
else
|
469
|
+
conn
|
470
|
+
end
|
471
|
+
else
|
472
|
+
conn = super(*args)
|
473
|
+
if on_connect = conn.on_connect
|
474
|
+
on_connect.call(conn, false, false)
|
475
|
+
end
|
476
|
+
conn
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
# @!visibility private
|
481
|
+
def initialize(*args)
|
482
|
+
Client.parse_async_options(args).each {|k, v| instance_variable_set(k, v) }
|
483
|
+
super(*args)
|
484
|
+
end
|
485
|
+
|
486
|
+
class << self
|
487
|
+
alias_method :connect, :new
|
488
|
+
alias_method :open, :new
|
489
|
+
alias_method :setdb, :new
|
490
|
+
alias_method :setdblogin, :new
|
491
|
+
end
|
492
|
+
|
493
|
+
# @!endgroup
|
494
|
+
|
495
|
+
# Closes the backend connection.
|
496
|
+
#
|
497
|
+
# Detaches watch handler to prevent memory leak after
|
498
|
+
# calling parent PG::Connection#finish[http://deveiate.org/code/pg/PG/Connection.html#method-i-finish].
|
499
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-finish PG::Connection#finish
|
500
|
+
def finish
|
501
|
+
super
|
502
|
+
if @watcher
|
503
|
+
@watcher.detach if @watcher.watching?
|
504
|
+
@watcher = nil
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
alias_method :close, :finish
|
509
|
+
|
510
|
+
# Returns status of connection: PG::CONNECTION_OK or PG::CONNECTION_BAD.
|
511
|
+
#
|
512
|
+
# @return [Number]
|
513
|
+
# Returns +PG::CONNECTION_BAD+ for connections with +async_command_aborted+
|
514
|
+
# flag set by expired query timeout. Otherwise return whatever PG::Connection#status returns.
|
515
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-status PG::Connection#status
|
516
|
+
def status
|
517
|
+
if @async_command_aborted
|
518
|
+
CONNECTION_BAD
|
519
|
+
else
|
520
|
+
super
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
# @!visibility private
|
525
|
+
# Perform auto re-connect. Used internally.
|
526
|
+
def async_autoreconnect!(deferrable, error, send_proc = nil, &on_connection_bad)
|
527
|
+
# reconnect only if connection is bad and flag is set
|
528
|
+
if self.status == CONNECTION_BAD
|
529
|
+
|
530
|
+
yield if block_given?
|
531
|
+
|
532
|
+
if async_autoreconnect
|
533
|
+
# check if transaction was active
|
534
|
+
was_in_transaction = case @last_transaction_status
|
535
|
+
when PQTRANS_IDLE, PQTRANS_UNKNOWN
|
536
|
+
false
|
537
|
+
else
|
538
|
+
true
|
539
|
+
end
|
540
|
+
# reset asynchronously
|
541
|
+
reset_df = reset_defer
|
542
|
+
# just fail on reset failure
|
543
|
+
reset_df.errback { |ex| deferrable.fail ex }
|
544
|
+
# reset succeeds
|
545
|
+
reset_df.callback do
|
546
|
+
# handle on_autoreconnect
|
547
|
+
if on_autoreconnect
|
548
|
+
# wrap in a fiber, so on_autoreconnect code may yield from it
|
549
|
+
Fiber.new do
|
550
|
+
# call on_autoreconnect handler and fail if it raises an error
|
551
|
+
returned_df = begin
|
552
|
+
on_autoreconnect.call(self, error)
|
553
|
+
rescue => ex
|
554
|
+
ex
|
555
|
+
end
|
556
|
+
if returned_df.respond_to?(:callback) && returned_df.respond_to?(:errback)
|
557
|
+
# the handler returned a deferrable
|
558
|
+
returned_df.callback do
|
559
|
+
if was_in_transaction || !send_proc
|
560
|
+
# fail anyway, there was a transaction in progress or in single result mode
|
561
|
+
deferrable.fail error
|
562
|
+
else
|
563
|
+
# try to call failed query command again
|
564
|
+
deferrable.protect(&send_proc)
|
565
|
+
end
|
566
|
+
end
|
567
|
+
# fail when handler's deferrable fails
|
568
|
+
returned_df.errback { |ex| deferrable.fail ex }
|
569
|
+
elsif returned_df.is_a?(Exception)
|
570
|
+
# tha handler returned an exception object, so fail with it
|
571
|
+
deferrable.fail returned_df
|
572
|
+
elsif returned_df == false || !send_proc || (was_in_transaction && returned_df != true)
|
573
|
+
# tha handler returned false or in single result mode
|
574
|
+
# or there was an active transaction and handler didn't return true
|
575
|
+
deferrable.fail error
|
576
|
+
else
|
577
|
+
# try to call failed query command again
|
578
|
+
deferrable.protect(&send_proc)
|
579
|
+
end
|
580
|
+
end.resume
|
581
|
+
elsif was_in_transaction || !send_proc
|
582
|
+
# there was a transaction in progress or in single result mode;
|
583
|
+
# fail anyway
|
584
|
+
deferrable.fail error
|
585
|
+
else
|
586
|
+
# no on_autoreconnect handler, no transaction
|
587
|
+
# try to call failed query command again
|
588
|
+
deferrable.protect(&send_proc)
|
589
|
+
end
|
590
|
+
end
|
591
|
+
# connection is bad, reset in progress, all done
|
592
|
+
return
|
593
|
+
end
|
594
|
+
end
|
595
|
+
# connection is either good or bad, the async_autoreconnect is not set
|
596
|
+
deferrable.fail error
|
597
|
+
end
|
598
|
+
|
599
|
+
# @!macro deferrable_api
|
600
|
+
# @return [FeaturedDeferrable]
|
601
|
+
# Use the returned Deferrable's +callback+ and +errback+ methods to
|
602
|
+
# get the result. If the block is provided it's bound to both the
|
603
|
+
# +callback+ and +errback+ hooks of the returned deferrable.
|
604
|
+
|
605
|
+
# @!macro deferrable_query_api
|
606
|
+
# @yieldparam result [PG::Result|Error] command result on success or a PG::Error instance on error.
|
607
|
+
# @macro deferrable_api
|
608
|
+
|
609
|
+
# @!group Deferrable command methods
|
610
|
+
|
611
|
+
# @!method exec_defer(sql, params=nil, result_format=nil, &blk)
|
612
|
+
# Sends SQL query request specified by +sql+ to PostgreSQL for asynchronous processing,
|
613
|
+
# and immediately returns with +deferrable+.
|
614
|
+
#
|
615
|
+
# @macro deferrable_query_api
|
616
|
+
#
|
617
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec PG::Connection#exec
|
618
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_params PG::Connection#exec_params
|
619
|
+
#
|
620
|
+
# @!method prepare_defer(stmt_name, sql, param_types=nil, &blk)
|
621
|
+
# Prepares statement +sql+ with name +stmt_name+ to be executed later asynchronously,
|
622
|
+
# and immediately returns with a Deferrable.
|
623
|
+
#
|
624
|
+
# @macro deferrable_query_api
|
625
|
+
#
|
626
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-prepare PG::Connection#prepare
|
627
|
+
#
|
628
|
+
# @!method exec_prepared_defer(statement_name, params=nil, result_format=nil, &blk)
|
629
|
+
# Execute prepared named statement specified by +statement_name+ asynchronously,
|
630
|
+
# and immediately returns with a Deferrable.
|
631
|
+
#
|
632
|
+
# @macro deferrable_query_api
|
633
|
+
#
|
634
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query_prepared PG::Connection#send_query_prepared
|
635
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared PG::Connection#send_exec_prepared
|
636
|
+
#
|
637
|
+
# @!method describe_prepared_defer(statement_name, &blk)
|
638
|
+
# Asynchronously sends command to retrieve information about the prepared statement +statement_name+,
|
639
|
+
# and immediately returns with a Deferrable.
|
640
|
+
#
|
641
|
+
# @macro deferrable_query_api
|
642
|
+
#
|
643
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_prepared PG::Connection#describe_prepared
|
644
|
+
#
|
645
|
+
# @!method describe_portal_defer(portal_name, &blk)
|
646
|
+
# Asynchronously sends command to retrieve information about the portal +portal_name+,
|
647
|
+
# and immediately returns with a Deferrable.
|
648
|
+
#
|
649
|
+
# @macro deferrable_query_api
|
650
|
+
#
|
651
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_portal PG::Connection#describe_portal
|
652
|
+
#
|
653
|
+
%w(
|
654
|
+
exec_defer send_query
|
655
|
+
prepare_defer send_prepare
|
656
|
+
exec_prepared_defer send_query_prepared
|
657
|
+
describe_prepared_defer send_describe_prepared
|
658
|
+
describe_portal_defer send_describe_portal
|
659
|
+
).each_slice(2) do |defer_name, send_name|
|
660
|
+
|
661
|
+
class_eval <<-EOD, __FILE__, __LINE__
|
662
|
+
def #{defer_name}(*args, &blk)
|
663
|
+
df = FeaturedDeferrable.new
|
664
|
+
send_proc = proc do
|
665
|
+
#{send_name}(*args)
|
666
|
+
setup_emio_watcher.watch_results(df, send_proc)
|
667
|
+
end
|
668
|
+
begin
|
669
|
+
check_async_command_aborted!
|
670
|
+
@last_transaction_status = transaction_status
|
671
|
+
send_proc.call
|
672
|
+
rescue Error => e
|
673
|
+
::EM.next_tick { async_autoreconnect!(df, e, send_proc) }
|
674
|
+
rescue Exception => e
|
675
|
+
::EM.next_tick { df.fail(e) }
|
676
|
+
end
|
677
|
+
df.completion(&blk) if block_given?
|
678
|
+
df
|
679
|
+
end
|
680
|
+
EOD
|
681
|
+
|
682
|
+
end
|
683
|
+
|
684
|
+
alias_method :query_defer, :exec_defer
|
685
|
+
alias_method :async_query_defer, :exec_defer
|
686
|
+
alias_method :async_exec_defer, :exec_defer
|
687
|
+
alias_method :exec_params_defer, :exec_defer
|
688
|
+
|
689
|
+
# Asynchronously waits for notification or until the optional
|
690
|
+
# +timeout+ is reached, whichever comes first. +timeout+ is
|
691
|
+
# measured in seconds and can be fractional.
|
692
|
+
# Returns immediately with a Deferrable.
|
693
|
+
#
|
694
|
+
# Pass the block to the returned deferrable's +callback+ to obtain notification
|
695
|
+
# hash. In case of connection error +errback+ hook is called with an error object.
|
696
|
+
# If the +timeout+ is reached +nil+ is passed to deferrable's +callback+.
|
697
|
+
# If the block is provided it's bound to both the +callback+ and +errback+ hooks
|
698
|
+
# of the returned deferrable.
|
699
|
+
# If another call is made to this method before the notification is received
|
700
|
+
# (or before reaching timeout) the previous deferrable's +errback+ will be called
|
701
|
+
# with +nil+ argument.
|
702
|
+
#
|
703
|
+
# @return [FeaturedDeferrable]
|
704
|
+
# @yieldparam notification [Hash|nil|Error] notification hash or a PG::Error instance on error
|
705
|
+
# or nil when timeout is reached or canceled.
|
706
|
+
#
|
707
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-notifies PG::Connection#notifies
|
708
|
+
def wait_for_notify_defer(timeout = nil, &blk)
|
709
|
+
df = FeaturedDeferrable.new
|
710
|
+
begin
|
711
|
+
check_async_command_aborted!
|
712
|
+
if status == CONNECTION_OK
|
713
|
+
setup_emio_watcher.watch_notify(df, timeout)
|
714
|
+
else
|
715
|
+
raise_error ConnectionBad
|
716
|
+
end
|
717
|
+
rescue Error => e
|
718
|
+
::EM.next_tick { async_autoreconnect!(df, e) }
|
719
|
+
rescue Exception => e
|
720
|
+
::EM.next_tick { df.fail(e) }
|
721
|
+
end
|
722
|
+
df.completion(&blk) if block_given?
|
723
|
+
df
|
724
|
+
end
|
725
|
+
|
726
|
+
alias_method :notifies_wait_defer, :wait_for_notify_defer
|
727
|
+
|
728
|
+
# Asynchronously retrieves the next result from a call to
|
729
|
+
# #send_query (or another asynchronous command) and immediately
|
730
|
+
# returns with a Deferrable.
|
731
|
+
# It then receives the result object on :succeed, or +nil+
|
732
|
+
# if no results are available.
|
733
|
+
#
|
734
|
+
# @macro deferrable_api
|
735
|
+
# @yieldparam result [PG::Result|Error|nil] command result on success or a PG::Error instance on error
|
736
|
+
# or +nil+ if no results are available.
|
737
|
+
#
|
738
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query PG::Connection#send_query
|
739
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_result PG::Connection#get_result
|
740
|
+
#
|
741
|
+
def get_result_defer(&blk)
|
742
|
+
df = FeaturedDeferrable.new
|
743
|
+
begin
|
744
|
+
if status == CONNECTION_OK
|
745
|
+
if is_busy
|
746
|
+
setup_emio_watcher.watch_results(df, nil, true)
|
747
|
+
else
|
748
|
+
df.succeed blocking_get_result
|
749
|
+
end
|
750
|
+
else
|
751
|
+
df.succeed
|
752
|
+
end
|
753
|
+
rescue Error => e
|
754
|
+
::EM.next_tick { async_autoreconnect!(df, e) }
|
755
|
+
rescue Exception => e
|
756
|
+
::EM.next_tick { df.fail(e) }
|
757
|
+
end
|
758
|
+
df.completion(&blk) if block_given?
|
759
|
+
df
|
760
|
+
end
|
761
|
+
|
762
|
+
# Asynchronously retrieves all available results on the current
|
763
|
+
# connection (from previously issued asynchronous commands like
|
764
|
+
# +send_query()+) and immediately returns with a Deferrable.
|
765
|
+
# It then receives the last non-NULL result on :succeed, or +nil+
|
766
|
+
# if no results are available.
|
767
|
+
#
|
768
|
+
# @macro deferrable_api
|
769
|
+
# @yieldparam result [PG::Result|Error|nil] command result on success or a PG::Error instance on error
|
770
|
+
# or +nil+ if no results are available.
|
771
|
+
#
|
772
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query PG::Connection#send_query
|
773
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_last_result PG::Connection#get_last_result
|
774
|
+
#
|
775
|
+
def get_last_result_defer(&blk)
|
776
|
+
df = FeaturedDeferrable.new
|
777
|
+
begin
|
778
|
+
if status == CONNECTION_OK
|
779
|
+
setup_emio_watcher.watch_results(df)
|
780
|
+
else
|
781
|
+
df.succeed
|
782
|
+
end
|
783
|
+
rescue Error => e
|
784
|
+
::EM.next_tick { async_autoreconnect!(df, e) }
|
785
|
+
rescue Exception => e
|
786
|
+
::EM.next_tick { df.fail(e) }
|
787
|
+
end
|
788
|
+
df.completion(&blk) if block_given?
|
789
|
+
df
|
790
|
+
end
|
791
|
+
|
792
|
+
# @!endgroup
|
793
|
+
|
794
|
+
alias_method :blocking_wait_for_notify, :wait_for_notify
|
795
|
+
alias_method :blocking_get_result, :get_result
|
796
|
+
|
797
|
+
def raise_error(klass=Error, message=error_message)
|
798
|
+
error = klass.new(message)
|
799
|
+
error.instance_variable_set(:@connection, self)
|
800
|
+
raise error
|
801
|
+
end
|
802
|
+
|
803
|
+
private
|
804
|
+
|
805
|
+
def fiber_sync(df, fiber)
|
806
|
+
f = nil
|
807
|
+
df.completion do |res|
|
808
|
+
if f then f.resume res else return res end
|
809
|
+
end
|
810
|
+
f = fiber
|
811
|
+
Fiber.yield
|
812
|
+
end
|
813
|
+
|
814
|
+
def check_async_command_aborted!
|
815
|
+
if @async_command_aborted
|
816
|
+
raise_error ConnectionBad, "previous query expired, need connection reset"
|
817
|
+
end
|
818
|
+
end
|
819
|
+
|
820
|
+
def setup_emio_watcher
|
821
|
+
if @watcher && @watcher.watching?
|
822
|
+
@watcher
|
823
|
+
else
|
824
|
+
@watcher = ::EM.watch(self.socket_io, Watcher, self)
|
825
|
+
end
|
826
|
+
end
|
827
|
+
|
828
|
+
public
|
829
|
+
|
830
|
+
# @!macro auto_synchrony_api_intro
|
831
|
+
# If EventMachine reactor is running and the current fiber isn't the
|
832
|
+
# root fiber this method performs command asynchronously yielding
|
833
|
+
# current fiber. Other fibers can process while waiting for the server
|
834
|
+
# to complete the request.
|
835
|
+
#
|
836
|
+
# Otherwise performs a blocking call to a parent method.
|
837
|
+
#
|
838
|
+
# @yieldparam result [PG::Result] command result on success
|
839
|
+
# @raise [PG::Error]
|
840
|
+
|
841
|
+
# @!macro auto_synchrony_api
|
842
|
+
# @macro auto_synchrony_api_intro
|
843
|
+
# @return [PG::Result] if block wasn't given
|
844
|
+
# @return [Object] result of the given block
|
845
|
+
|
846
|
+
# @!group Auto-sensing fiber-synchronized command methods
|
847
|
+
|
848
|
+
# @!method exec(sql, &blk)
|
849
|
+
# Sends SQL query request specified by +sql+ to PostgreSQL.
|
850
|
+
#
|
851
|
+
# @macro auto_synchrony_api
|
852
|
+
#
|
853
|
+
# @see PG::EM::Client#exec_defer
|
854
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec PG::Connection#exec
|
855
|
+
#
|
856
|
+
# @!method exec_params(sql, params=nil, result_format=nil, &blk)
|
857
|
+
# Sends SQL query request specified by +sql+ with optional +params+ and +result_format+ to PostgreSQL.
|
858
|
+
#
|
859
|
+
# @macro auto_synchrony_api
|
860
|
+
#
|
861
|
+
# @see PG::EM::Client#exec_params_defer
|
862
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_params PG::Connection#exec_params
|
863
|
+
#
|
864
|
+
# @!method prepare(stmt_name, sql, param_types=nil, &blk)
|
865
|
+
# Prepares statement +sql+ with name +stmt_name+ to be executed later.
|
866
|
+
#
|
867
|
+
# @macro auto_synchrony_api
|
868
|
+
#
|
869
|
+
# @see PG::EM::Client#prepare_defer
|
870
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-prepare PG::Connection#prepare
|
871
|
+
#
|
872
|
+
# @!method exec_prepared(statement_name, params=nil, result_format=nil, &blk)
|
873
|
+
# Executes prepared named statement specified by +statement_name+.
|
874
|
+
#
|
875
|
+
# @macro auto_synchrony_api
|
876
|
+
#
|
877
|
+
# @see PG::EM::Client#exec_prepared_defer
|
878
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared PG::Connection#exec_prepared
|
879
|
+
#
|
880
|
+
# @!method describe_prepared(statement_name, &blk)
|
881
|
+
# Retrieves information about the prepared statement +statement_name+,
|
882
|
+
#
|
883
|
+
# @macro auto_synchrony_api
|
884
|
+
#
|
885
|
+
# @see PG::EM::Client#describe_prepared_defer
|
886
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_prepared PG::Connection#describe_prepared
|
887
|
+
#
|
888
|
+
# @!method describe_portal(portal_name, &blk)
|
889
|
+
# Retrieves information about the portal +portal_name+,
|
890
|
+
#
|
891
|
+
# @macro auto_synchrony_api
|
892
|
+
#
|
893
|
+
# @see PG::EM::Client#describe_portal_defer
|
894
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_portal PG::Connection#describe_portal
|
895
|
+
#
|
896
|
+
# @!method get_last_result
|
897
|
+
# Retrieves all available results on the current connection
|
898
|
+
# (from previously issued asynchronous commands like +send_query()+)
|
899
|
+
# and returns the last non-NULL result, or +nil+ if no results are
|
900
|
+
# available.
|
901
|
+
#
|
902
|
+
# @macro auto_synchrony_api
|
903
|
+
# @return [nil] if no more results
|
904
|
+
#
|
905
|
+
# @see #get_last_result_defer
|
906
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_last_result PG::Connection#get_last_result
|
907
|
+
%w(
|
908
|
+
exec exec_defer
|
909
|
+
exec_params exec_defer
|
910
|
+
exec_prepared exec_prepared_defer
|
911
|
+
prepare prepare_defer
|
912
|
+
describe_prepared describe_prepared_defer
|
913
|
+
describe_portal describe_portal_defer
|
914
|
+
get_last_result get_last_result_defer
|
915
|
+
).each_slice(2) do |name, defer_name|
|
916
|
+
|
917
|
+
class_eval <<-EOD, __FILE__, __LINE__
|
918
|
+
def #{name}(*args, &blk)
|
919
|
+
if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
|
920
|
+
if (result = fiber_sync #{defer_name}(*args), f).is_a?(::Exception)
|
921
|
+
raise result
|
922
|
+
end
|
923
|
+
if block_given? && result
|
924
|
+
begin
|
925
|
+
yield result
|
926
|
+
ensure
|
927
|
+
result.clear
|
928
|
+
end
|
929
|
+
else
|
930
|
+
result
|
931
|
+
end
|
932
|
+
else
|
933
|
+
super
|
934
|
+
end
|
935
|
+
end
|
936
|
+
EOD
|
937
|
+
end
|
938
|
+
|
939
|
+
alias_method :query, :exec
|
940
|
+
alias_method :async_query, :exec
|
941
|
+
alias_method :async_exec, :exec
|
942
|
+
|
943
|
+
# Blocks while waiting for notification(s), or until the optional
|
944
|
+
# +timeout+ is reached, whichever comes first.
|
945
|
+
# Returns +nil+ if +timeout+ is reached, the name of the +NOTIFY+
|
946
|
+
# event otherwise.
|
947
|
+
#
|
948
|
+
# If EventMachine reactor is running and the current fiber isn't the
|
949
|
+
# root fiber this method performs command asynchronously yielding
|
950
|
+
# current fiber. Other fibers can process while the current one is
|
951
|
+
# waiting for notifications.
|
952
|
+
#
|
953
|
+
# Otherwise performs a blocking call to a parent method.
|
954
|
+
# @return [String|nil]
|
955
|
+
# @yieldparam name [String] the name of the +NOTIFY+ event
|
956
|
+
# @yieldparam pid [Number] the generating pid
|
957
|
+
# @yieldparam payload [String] the optional payload
|
958
|
+
# @raise [PG::Error]
|
959
|
+
#
|
960
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-wait_for_notify PG::Connection#wait_for_notify
|
961
|
+
def wait_for_notify(timeout = nil)
|
962
|
+
if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
|
963
|
+
unless notify_hash = notifies
|
964
|
+
if (notify_hash = fiber_sync wait_for_notify_defer(timeout), f).is_a?(::Exception)
|
965
|
+
raise notify_hash
|
966
|
+
end
|
967
|
+
end
|
968
|
+
if notify_hash
|
969
|
+
if block_given?
|
970
|
+
yield notify_hash.values_at(:relname, :be_pid, :extra)
|
971
|
+
end
|
972
|
+
notify_hash[:relname]
|
973
|
+
end
|
974
|
+
else
|
975
|
+
super
|
976
|
+
end
|
977
|
+
end
|
978
|
+
|
979
|
+
alias_method :notifies_wait, :wait_for_notify
|
980
|
+
|
981
|
+
# Retrieves the next result from a call to #send_query (or another
|
982
|
+
# asynchronous command). If no more results are available returns
|
983
|
+
# +nil+ and the block (if given) is never called.
|
984
|
+
#
|
985
|
+
# @macro auto_synchrony_api
|
986
|
+
# @return [nil] if no more results
|
987
|
+
#
|
988
|
+
# @see #get_result_defer
|
989
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_result PG::Connection#get_result
|
990
|
+
def get_result
|
991
|
+
if is_busy && ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
|
992
|
+
if (result = fiber_sync get_result_defer, f).is_a?(::Exception)
|
993
|
+
raise result
|
994
|
+
end
|
995
|
+
if block_given? && result
|
996
|
+
begin
|
997
|
+
yield result
|
998
|
+
ensure
|
999
|
+
result.clear
|
1000
|
+
end
|
1001
|
+
else
|
1002
|
+
result
|
1003
|
+
end
|
1004
|
+
else
|
1005
|
+
super
|
1006
|
+
end
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
# @!endgroup
|
1010
|
+
|
1011
|
+
|
1012
|
+
TRAN_BEGIN_QUERY = 'BEGIN'
|
1013
|
+
TRAN_ROLLBACK_QUERY = 'ROLLBACK'
|
1014
|
+
TRAN_COMMIT_QUERY = 'COMMIT'
|
1015
|
+
|
1016
|
+
# Executes a BEGIN at the start of the block and a COMMIT at the end
|
1017
|
+
# of the block or ROLLBACK if any exception occurs.
|
1018
|
+
#
|
1019
|
+
# @note Avoid using PG::EM::Client#*_defer calls inside the block or make sure
|
1020
|
+
# all queries are completed before the provided block terminates.
|
1021
|
+
# @return [Object] result of the block
|
1022
|
+
# @yieldparam client [self]
|
1023
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-transaction PG::Connection#transaction
|
1024
|
+
#
|
1025
|
+
# Calls to {#transaction} may be nested, however without sub-transactions
|
1026
|
+
# (save points). If the innermost transaction block raises an error
|
1027
|
+
# the transaction is rolled back to the state before the outermost
|
1028
|
+
# transaction began.
|
1029
|
+
#
|
1030
|
+
# This is an extension to the +PG::Connection#transaction+ method
|
1031
|
+
# as it does not support nesting in this way.
|
1032
|
+
#
|
1033
|
+
# The method is sensitive to the transaction status and will safely
|
1034
|
+
# rollback on any sql error even when it was catched by some rescue block.
|
1035
|
+
# But consider that rescuing any sql error within an utility method
|
1036
|
+
# is a bad idea.
|
1037
|
+
#
|
1038
|
+
# This method works in both blocking/async modes (regardles of the reactor state)
|
1039
|
+
# and is considered as a generic extension to the +PG::Connection#transaction+
|
1040
|
+
# method.
|
1041
|
+
#
|
1042
|
+
# @example Nested transaction example
|
1043
|
+
# def add_comment(user_id, text)
|
1044
|
+
# db.transaction do
|
1045
|
+
# cmt_id = db.query(
|
1046
|
+
# 'insert into comments (text) where user_id=$1 values ($2) returning id',
|
1047
|
+
# [user_id, text]).getvalue(0,0)
|
1048
|
+
# db.query(
|
1049
|
+
# 'update users set last_comment_id=$2 where id=$1', [user_id, cmt_id])
|
1050
|
+
# cmt_id
|
1051
|
+
# end
|
1052
|
+
# end
|
1053
|
+
#
|
1054
|
+
# def update_comment_count(page_id)
|
1055
|
+
# db.transaction do
|
1056
|
+
# count = db.query('select count(*) from comments where page_id=$1', [page_id]).getvalue(0,0)
|
1057
|
+
# db.query('update pages set comment_count=$2 where id=$1', [page_id, count])
|
1058
|
+
# end
|
1059
|
+
# end
|
1060
|
+
#
|
1061
|
+
# # to run add_comment and update_comment_count within the same transaction
|
1062
|
+
# db.transaction do
|
1063
|
+
# add_comment(user_id, some_text)
|
1064
|
+
# update_comment_count(page_id)
|
1065
|
+
# end
|
1066
|
+
#
|
1067
|
+
def transaction
|
1068
|
+
raise ArgumentError, 'Must supply block for PG::EM::Client#transaction' unless block_given?
|
1069
|
+
tcount = @client_tran_count.to_i
|
1070
|
+
|
1071
|
+
case transaction_status
|
1072
|
+
when PQTRANS_IDLE
|
1073
|
+
# there is no transaction yet, so let's begin
|
1074
|
+
exec(TRAN_BEGIN_QUERY)
|
1075
|
+
# reset transaction count in case user code rolled it back before
|
1076
|
+
tcount = 0 if tcount != 0
|
1077
|
+
when PQTRANS_INTRANS
|
1078
|
+
# transaction in progress, leave it be
|
1079
|
+
else
|
1080
|
+
# transaction failed, is in unknown state or command is active
|
1081
|
+
# in any case calling begin will raise server transaction error
|
1082
|
+
exec(TRAN_BEGIN_QUERY) # raises PG::InFailedSqlTransaction
|
1083
|
+
end
|
1084
|
+
# memoize nested count
|
1085
|
+
@client_tran_count = tcount + 1
|
1086
|
+
begin
|
1087
|
+
|
1088
|
+
result = yield self
|
1089
|
+
|
1090
|
+
rescue
|
1091
|
+
# error was raised
|
1092
|
+
case transaction_status
|
1093
|
+
when PQTRANS_INTRANS, PQTRANS_INERROR
|
1094
|
+
# do not rollback if transaction was rolled back before
|
1095
|
+
# or is in unknown state, which means connection reset is needed
|
1096
|
+
# and rollback only from the outermost transaction block
|
1097
|
+
exec(TRAN_ROLLBACK_QUERY) if tcount.zero?
|
1098
|
+
end
|
1099
|
+
# raise again
|
1100
|
+
raise
|
1101
|
+
else
|
1102
|
+
# we are good (but not out of woods yet)
|
1103
|
+
case transaction_status
|
1104
|
+
when PQTRANS_INTRANS
|
1105
|
+
# commit only from the outermost transaction block
|
1106
|
+
exec(TRAN_COMMIT_QUERY) if tcount.zero?
|
1107
|
+
when PQTRANS_INERROR
|
1108
|
+
# no ruby error was raised (or an error was rescued in code block)
|
1109
|
+
# but there was an sql error anyway
|
1110
|
+
# so rollback after the outermost block
|
1111
|
+
exec(TRAN_ROLLBACK_QUERY) if tcount.zero?
|
1112
|
+
when PQTRANS_IDLE
|
1113
|
+
# the code block has terminated the transaction on its own
|
1114
|
+
# so just reset the counter
|
1115
|
+
tcount = 0
|
1116
|
+
else
|
1117
|
+
# something isn't right, so provoke an error just in case
|
1118
|
+
exec(TRAN_ROLLBACK_QUERY) if tcount.zero?
|
1119
|
+
end
|
1120
|
+
result
|
1121
|
+
ensure
|
1122
|
+
@client_tran_count = tcount
|
1123
|
+
end
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
end
|
1127
|
+
end
|
1128
|
+
|
1129
|
+
end
|