em-pg-client 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
data/Rakefile
CHANGED
@@ -4,40 +4,56 @@ task :default => [:test]
|
|
4
4
|
|
5
5
|
$gem_name = "em-pg-client"
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
def windows_os?
|
8
|
+
RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Run tests"
|
12
|
+
task :test => :'test:safe'
|
10
13
|
|
14
|
+
namespace :test do
|
11
15
|
env_common = {'PGDATABASE' => 'test'}
|
12
|
-
env_pg_013 = {'EM_PG_CLIENT_TEST_PG_VERSION' => '= 0.13.2'}
|
13
16
|
env_unix_socket = env_common.merge('PGHOST' => '/tmp')
|
14
17
|
env_tcpip = env_common.merge('PGHOST' => 'localhost')
|
15
18
|
|
16
|
-
|
19
|
+
task :warn do
|
20
|
+
puts "WARNING: The test needs to be run with an available local PostgreSQL server"
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Run specs only"
|
24
|
+
task :spec do
|
25
|
+
sh "rspec spec/pg_em_featured_deferrable.rb"
|
26
|
+
sh "rspec spec/pg_em_client_options.rb"
|
27
|
+
sh "rspec spec/pg_em_client_connect_finish.rb"
|
28
|
+
sh "rspec spec/pg_em_client_connect_timeout.rb"
|
29
|
+
sh "rspec spec/pg_em_connection_pool.rb"
|
30
|
+
end
|
17
31
|
|
18
|
-
|
32
|
+
desc "Run safe tests only"
|
33
|
+
task :safe => [:warn, :spec] do
|
19
34
|
%w[
|
20
|
-
spec/
|
21
|
-
spec/em_devel_client.rb
|
35
|
+
spec/em_client.rb
|
22
36
|
spec/em_synchrony_client.rb
|
23
37
|
].each do |spec|
|
24
|
-
sh env_unix_socket, "rspec #{spec}"
|
38
|
+
sh env_unix_socket, "rspec #{spec}" unless windows_os?
|
25
39
|
sh env_tcpip, "rspec #{spec}"
|
26
|
-
sh env_pg_013.merge(env_unix_socket), "rspec #{spec}"
|
27
|
-
sh env_pg_013.merge(env_tcpip), "rspec #{spec}"
|
28
40
|
end
|
29
41
|
end
|
30
42
|
|
31
|
-
|
43
|
+
desc "Run unsafe tests only"
|
44
|
+
task :unsafe => :warn do
|
32
45
|
raise "Set PGDATA environment variable before running the autoreconnect tests." unless ENV['PGDATA']
|
33
46
|
%w[
|
34
47
|
spec/em_client_autoreconnect.rb
|
35
48
|
spec/em_synchrony_client_autoreconnect.rb
|
36
49
|
].each do |spec|
|
37
|
-
sh env_unix_socket, "rspec #{spec}"
|
50
|
+
sh env_unix_socket, "rspec #{spec}" unless windows_os?
|
38
51
|
sh env_tcpip, "rspec #{spec}"
|
39
52
|
end
|
40
53
|
end
|
54
|
+
|
55
|
+
desc "Run safe and unsafe tests"
|
56
|
+
task :all => [:spec, :safe, :unsafe]
|
41
57
|
end
|
42
58
|
|
43
59
|
desc "Build the gem"
|
@@ -62,7 +78,7 @@ end
|
|
62
78
|
|
63
79
|
desc "Documentation"
|
64
80
|
task :doc do
|
65
|
-
sh "
|
81
|
+
sh "yardoc"
|
66
82
|
end
|
67
83
|
|
68
84
|
desc "Benchmark"
|
data/em-pg-client.gemspec
CHANGED
@@ -1,26 +1,27 @@
|
|
1
1
|
$:.unshift "lib"
|
2
|
+
require 'pg/em-version'
|
2
3
|
|
3
4
|
Gem::Specification.new do |s|
|
4
5
|
s.name = "em-pg-client"
|
5
|
-
s.version =
|
6
|
-
s.required_ruby_version = ">= 1.9.
|
6
|
+
s.version = PG::EM::VERSION
|
7
|
+
s.required_ruby_version = ">= 1.9.2"
|
7
8
|
s.date = "#{Time.now.strftime("%Y-%m-%d")}"
|
8
9
|
s.summary = "EventMachine PostgreSQL client"
|
9
10
|
s.email = "rafal@yeondir.com"
|
10
11
|
s.homepage = "http://github.com/royaltm/ruby-em-pg-client"
|
12
|
+
s.license = "MIT"
|
11
13
|
s.require_path = "lib"
|
12
14
|
s.description = "PostgreSQL asynchronous EventMachine client, based on pg interface (PG::Connection)"
|
13
15
|
s.authors = ["Rafal Michalski"]
|
14
16
|
s.files = `git ls-files`.split("\n") - ['.gitignore']
|
15
17
|
s.test_files = Dir.glob("spec/**/*")
|
16
18
|
s.rdoc_options << "--title" << "em-pg-client" <<
|
17
|
-
"--main" << "README.
|
19
|
+
"--main" << "README.md"
|
18
20
|
s.has_rdoc = true
|
19
|
-
s.extra_rdoc_files = ["README.
|
21
|
+
s.extra_rdoc_files = ["README.md", "BENCHMARKS.md", "LICENSE", "HISTORY.md"]
|
20
22
|
s.requirements << "PostgreSQL server"
|
21
|
-
s.add_runtime_dependency "pg", ">= 0.
|
22
|
-
s.add_runtime_dependency "eventmachine", "
|
23
|
+
s.add_runtime_dependency "pg", ">= 0.17.0"
|
24
|
+
s.add_runtime_dependency "eventmachine", "~> 1.0.0"
|
23
25
|
s.add_development_dependency "rspec", "~> 2.8.0"
|
24
|
-
s.add_development_dependency "eventmachine", ">= 1.0.0.beta.1"
|
25
26
|
s.add_development_dependency "em-synchrony", "~> 1.0.0"
|
26
27
|
end
|
data/lib/em-pg-client.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'pg/em'
|
data/lib/em-synchrony/pg.rb
CHANGED
@@ -1,108 +1,3 @@
|
|
1
|
+
# for backward compatibility with
|
2
|
+
# require 'em-synchrony/pg'
|
1
3
|
require 'pg/em'
|
2
|
-
module PG
|
3
|
-
module EM
|
4
|
-
class Client
|
5
|
-
# Author:: Rafal Michalski (mailto:royaltm75@gmail.com)
|
6
|
-
# Licence:: MIT License
|
7
|
-
#
|
8
|
-
# =PostgreSQL Client for EM-Synchrony/Fibered EventMachine
|
9
|
-
#
|
10
|
-
|
11
|
-
# conform to *standard*
|
12
|
-
alias_method :aquery, :async_query
|
13
|
-
|
14
|
-
# fiber aware methods:
|
15
|
-
# - exec (aliased as query)
|
16
|
-
# - exec_prepared
|
17
|
-
# - prepare
|
18
|
-
# - describe_prepared
|
19
|
-
# - describe_portal
|
20
|
-
# - reset
|
21
|
-
# - Client.connect
|
22
|
-
%w(exec
|
23
|
-
exec_prepared
|
24
|
-
prepare
|
25
|
-
describe_prepared
|
26
|
-
describe_portal
|
27
|
-
reset
|
28
|
-
self.connect).each do |name|
|
29
|
-
async_name = "async_#{name.split('.').last}"
|
30
|
-
blocking_call = case name
|
31
|
-
when 'reset'
|
32
|
-
'@async_command_aborted = false
|
33
|
-
super(*args, &blk)'
|
34
|
-
else
|
35
|
-
'super(*args, &blk)'
|
36
|
-
end
|
37
|
-
clear_method = case name
|
38
|
-
when 'reset', 'self.connect'
|
39
|
-
'finish'
|
40
|
-
else
|
41
|
-
'clear'
|
42
|
-
end
|
43
|
-
class_eval <<-EOD
|
44
|
-
def #{name}(*args, &blk)
|
45
|
-
if ::EM.reactor_running?
|
46
|
-
f = Fiber.current
|
47
|
-
#{async_name}(*args) do |res|
|
48
|
-
f.resume(res)
|
49
|
-
end
|
50
|
-
|
51
|
-
result = Fiber.yield
|
52
|
-
raise result if result.is_a?(::Exception)
|
53
|
-
if block_given?
|
54
|
-
begin
|
55
|
-
yield result
|
56
|
-
ensure
|
57
|
-
result.#{clear_method}
|
58
|
-
end
|
59
|
-
else
|
60
|
-
result
|
61
|
-
end
|
62
|
-
else
|
63
|
-
#{blocking_call}
|
64
|
-
end
|
65
|
-
end
|
66
|
-
EOD
|
67
|
-
end
|
68
|
-
|
69
|
-
class << self
|
70
|
-
alias_method :new, :connect
|
71
|
-
alias_method :open, :connect
|
72
|
-
alias_method :setdb, :connect
|
73
|
-
alias_method :setdblogin, :connect
|
74
|
-
end
|
75
|
-
|
76
|
-
alias_method :query, :exec
|
77
|
-
|
78
|
-
def async_autoreconnect!(deferrable, error, &send_proc)
|
79
|
-
if async_autoreconnect && self.status != PG::CONNECTION_OK
|
80
|
-
reset_df = async_reset
|
81
|
-
reset_df.errback { |ex| deferrable.fail(ex) }
|
82
|
-
reset_df.callback do
|
83
|
-
Fiber.new do
|
84
|
-
if on_autoreconnect
|
85
|
-
returned_df = on_autoreconnect.call(self, error)
|
86
|
-
if returned_df == false
|
87
|
-
::EM.next_tick { deferrable.fail(error) }
|
88
|
-
elsif returned_df.respond_to?(:callback) && returned_df.respond_to?(:errback)
|
89
|
-
returned_df.callback { deferrable.protect(&send_proc) }
|
90
|
-
returned_df.errback { |ex| deferrable.fail(ex) }
|
91
|
-
elsif returned_df.is_a?(Exception)
|
92
|
-
::EM.next_tick { deferrable.fail(returned_df) }
|
93
|
-
else
|
94
|
-
deferrable.protect(&send_proc)
|
95
|
-
end
|
96
|
-
else
|
97
|
-
deferrable.protect(&send_proc)
|
98
|
-
end
|
99
|
-
end.resume
|
100
|
-
end
|
101
|
-
else
|
102
|
-
::EM.next_tick { deferrable.fail(error) }
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
data/lib/pg/em.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'fiber'
|
1
2
|
begin
|
2
3
|
require 'pg'
|
3
4
|
rescue LoadError => error
|
@@ -10,366 +11,387 @@ unless defined? EventMachine
|
|
10
11
|
raise 'Missing EventMachine: gem install eventmachine'
|
11
12
|
end
|
12
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'
|
13
18
|
|
14
19
|
module PG
|
15
|
-
|
16
20
|
module EM
|
17
|
-
|
18
|
-
def initialize(&blk)
|
19
|
-
if block_given?
|
20
|
-
callback(&blk)
|
21
|
-
errback(&blk)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def protect(fail_value = nil)
|
26
|
-
yield
|
27
|
-
rescue Exception => e
|
28
|
-
::EM.next_tick { fail(e) }
|
29
|
-
fail_value
|
30
|
-
end
|
31
|
-
|
32
|
-
def protect_and_succeed(fail_value = nil)
|
33
|
-
ret = yield
|
34
|
-
rescue Exception => e
|
35
|
-
::EM.next_tick { fail(e) }
|
36
|
-
fail_value
|
37
|
-
else
|
38
|
-
::EM.next_tick { succeed(ret) }
|
39
|
-
ret
|
40
|
-
end
|
41
|
-
end
|
21
|
+
|
42
22
|
# == PostgreSQL EventMachine client
|
43
23
|
#
|
44
|
-
# Author:: Rafal Michalski
|
45
|
-
# Licence:: MIT License
|
24
|
+
# Author:: Rafal Michalski
|
46
25
|
#
|
26
|
+
# {PG::EM::Client} is a PG::Connection[http://deveiate.org/code/pg/PG/Connection.html]
|
27
|
+
# wrapper designed for EventMachine[http://rubyeventmachine.com/].
|
47
28
|
#
|
48
|
-
#
|
49
|
-
# which (re)defines methods:
|
29
|
+
# The following new methods:
|
50
30
|
#
|
51
|
-
# -
|
52
|
-
# -
|
53
|
-
# -
|
54
|
-
# -
|
55
|
-
# -
|
31
|
+
# - {#exec_defer} (alias: +query_defer+)
|
32
|
+
# - {#exec_params_defer}
|
33
|
+
# - {#prepare_defer}
|
34
|
+
# - {#exec_prepared_defer}
|
35
|
+
# - {#describe_prepared_defer}
|
36
|
+
# - {#describe_portal_defer}
|
56
37
|
#
|
57
|
-
#
|
38
|
+
# are added to execute queries asynchronously,
|
39
|
+
# returning +Deferrable+ object.
|
58
40
|
#
|
59
|
-
#
|
41
|
+
# The following methods of PG::Connection[http://deveiate.org/code/pg/PG/Connection.html]
|
42
|
+
# are overloaded:
|
60
43
|
#
|
61
|
-
# -
|
62
|
-
# -
|
63
|
-
# -
|
64
|
-
# -
|
65
|
-
# -
|
44
|
+
# - {#exec} (alias: +query+, +async_exec+, +async_query+)
|
45
|
+
# - {#exec_params}
|
46
|
+
# - {#prepare}
|
47
|
+
# - {#exec_prepared}
|
48
|
+
# - {#describe_prepared}
|
49
|
+
# - {#describe_portal}
|
66
50
|
#
|
67
|
-
#
|
68
|
-
# (
|
51
|
+
# and are now auto-detecting if EventMachine is running and
|
52
|
+
# performing commands asynchronously (blocking only current fiber) or
|
53
|
+
# calling parent thread-blocking methods.
|
69
54
|
#
|
70
|
-
#
|
71
|
-
#
|
55
|
+
# If {#async_autoreconnect} option is set to +true+, all of the above
|
56
|
+
# methods (in asynchronous mode) try to re-connect after a connection
|
57
|
+
# error occurs. It's performed behind the scenes, so no error is raised,
|
58
|
+
# except if there was a transaction in progress. In such instance the error
|
59
|
+
# is raised after establishing connection to signal that
|
60
|
+
# the transaction was aborted.
|
72
61
|
#
|
73
|
-
# -
|
74
|
-
#
|
75
|
-
#
|
76
|
-
# They are async equivalents of PG::Connection.connect (which is also
|
77
|
-
# aliased by PG::Connection as +new+, +open+, +setdb+, +setdblogin+) and
|
78
|
-
# +reset+.
|
79
|
-
#
|
80
|
-
# When #async_autoreconnect is +true+, async methods might try to
|
81
|
-
# re-connect after a connection error. You won't even notice that
|
82
|
-
# (except for warning message from PG).
|
83
|
-
# If you want to detect such event use #on_autoreconnect property.
|
62
|
+
# If you want to detect auto re-connect event use {#on_autoreconnect}
|
63
|
+
# property/option.
|
84
64
|
#
|
85
65
|
# To enable auto-reconnecting set:
|
86
66
|
# client.async_autoreconnect = true
|
87
67
|
#
|
88
|
-
# or pass as new
|
89
|
-
# ::new
|
68
|
+
# or pass as {new} hash argument:
|
69
|
+
# PG::EM::Client.new dbname: 'bar', async_autoreconnect: true
|
70
|
+
#
|
71
|
+
# There are also new methods:
|
72
|
+
#
|
73
|
+
# - {Client.connect_defer}
|
74
|
+
# - {#reset_defer}
|
75
|
+
#
|
76
|
+
# which are asynchronous versions of PG::Connection.new and
|
77
|
+
# PG:Connection#reset.
|
78
|
+
#
|
79
|
+
# Additionally the following methods are overloaded:
|
80
|
+
#
|
81
|
+
# - {new} (alias: +connect+, +open+, +setdb+, +setdblogin+ )
|
82
|
+
# - {#reset}
|
83
|
+
#
|
84
|
+
# providing auto-detecting asynchronous (fiber-synchronized) or
|
85
|
+
# thread-blocking methods for (re)connecting.
|
90
86
|
#
|
91
87
|
# Otherwise nothing changes in PG::Connection API.
|
92
88
|
# See PG::Connection[http://deveiate.org/code/pg/PG/Connection.html] docs
|
93
|
-
# for arguments to above methods.
|
89
|
+
# for explanation of arguments to the above methods.
|
94
90
|
#
|
95
91
|
# *Warning:*
|
96
92
|
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
# If you are using a connection pool, make sure to acquire single
|
93
|
+
# {#describe_prepared} and {#exec_prepared} after
|
94
|
+
# {#prepare} should only be invoked on the *same* connection.
|
95
|
+
# If you are using a connection pool, make sure to acquire a single
|
96
|
+
# connection first.
|
100
97
|
#
|
101
98
|
class Client < PG::Connection
|
102
99
|
|
100
|
+
ROOT_FIBER = Fiber.current
|
103
101
|
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
102
|
+
# @!attribute connect_timeout
|
103
|
+
# @return [Float] connection timeout in seconds
|
104
|
+
# Connection timeout. Affects {#reset} and {#reset_defer}.
|
105
|
+
#
|
106
|
+
# Changing this property does not affect thread-blocking {#reset}.
|
107
|
+
#
|
108
|
+
# However if passed as initialization option, it also affects blocking
|
109
|
+
# {#reset}.
|
110
|
+
#
|
111
|
+
# To enable it set to some positive value. To disable it: set to 0.
|
112
|
+
# You can also specify this as an option to {new} or {connect_defer}.
|
108
113
|
attr_accessor :connect_timeout
|
109
114
|
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
115
|
+
# @!attribute query_timeout
|
116
|
+
# @return [Float] query timeout in seconds
|
117
|
+
# Aborts async command processing if server response time
|
118
|
+
# exceedes +query_timeout+ seconds. This does not apply to
|
119
|
+
# {#reset} and {#reset_defer}.
|
114
120
|
#
|
115
|
-
#
|
116
|
-
#
|
121
|
+
# To enable it set to some positive value. To disable it: set to 0.
|
122
|
+
# You can also specify this as an option to {new} or {connect_defer}.
|
117
123
|
attr_accessor :query_timeout
|
118
124
|
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
125
|
+
# @!attribute async_autoreconnect
|
126
|
+
# @return [Boolean] asynchronous auto re-connect status
|
127
|
+
# Enable/disable auto re-connect feature (+true+/+false+).
|
128
|
+
# Defaults to +false+ unless {#on_autoreconnect} is specified
|
129
|
+
# as an initialization option.
|
130
|
+
#
|
131
|
+
# Changing {#on_autoreconnect} with accessor method doesn't change
|
132
|
+
# the state of {#async_autoreconnect}.
|
133
|
+
#
|
134
|
+
# You can also specify this as an option to {new} or {connect_defer}.
|
124
135
|
attr_accessor :async_autoreconnect
|
125
136
|
|
126
|
-
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
137
|
+
# @!attribute on_autoreconnect
|
138
|
+
# @return [Proc<Client, Error>] auto re-connect hook
|
139
|
+
# Proc that is called after a connection with the server has been
|
140
|
+
# automatically re-established. It's being invoked just before the
|
141
|
+
# pending command is sent to the server.
|
142
|
+
#
|
143
|
+
# The first argument it receives is the +connection+ instance.
|
144
|
+
# The second is the original +exception+ that caused the reconnecting
|
145
|
+
# process.
|
146
|
+
#
|
147
|
+
# The proc can control the later action with its return value:
|
130
148
|
#
|
131
|
-
#
|
149
|
+
# - +false+ (explicitly, +nil+ is ignored) - the original +exception+
|
150
|
+
# is raised/passed back and the pending query command is not sent
|
151
|
+
# again to the server.
|
152
|
+
# - +true+ (explicitly, truish values are ignored), the pending command
|
153
|
+
# is called regardless of the connection's last transaction status.
|
154
|
+
# - Exception object - is raised/passed back and the pending command
|
155
|
+
# is not sent.
|
156
|
+
# - Deferrable object - the chosen action will depend on the deferred
|
157
|
+
# status.
|
158
|
+
# - Other values are ignored and the pending query command is
|
159
|
+
# immediately sent to the server unless there was a pending
|
160
|
+
# transaction before the connection was reset.
|
132
161
|
#
|
133
|
-
#
|
134
|
-
# the original +exception+ is passed to Defferable's +errback+ and
|
135
|
-
# the send query command is not invoked at all.
|
136
|
-
# - If return value is an instance of exception, it is passed to
|
137
|
-
# Defferable's +errback+ and the send query command is not invoked at all.
|
138
|
-
# - If return value responds to +callback+ and +errback+ methods,
|
139
|
-
# the send query command will be bound to value's success +callback+
|
140
|
-
# and the original Defferable's +errback+ or value's +errback+.
|
141
|
-
# - Other return values are ignored and the send query command is called
|
142
|
-
# immediately after #on_autoreconnect proc is executed.
|
162
|
+
# It's possible to execute queries from inside of the proc.
|
143
163
|
#
|
144
|
-
#
|
164
|
+
# You may pass this proc as an option to {new} or {connect_defer}.
|
145
165
|
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
166
|
+
# @example How to use prepare in on_autoreconnect hook
|
167
|
+
# pg.on_autoreconnect = proc do |conn, ex|
|
168
|
+
# conn.prepare("species_by_name",
|
169
|
+
# "select id, name from animals where species=$1 order by name")
|
170
|
+
# end
|
151
171
|
#
|
152
172
|
attr_accessor :on_autoreconnect
|
153
173
|
|
174
|
+
# @!visibility private
|
154
175
|
# Used internally for marking connection as aborted on query timeout.
|
155
176
|
attr_accessor :async_command_aborted
|
156
177
|
|
157
|
-
|
158
|
-
|
159
|
-
@last_result = nil
|
160
|
-
@client = client
|
161
|
-
@deferrable = deferrable
|
162
|
-
@send_proc = send_proc
|
163
|
-
if (timeout = client.query_timeout) > 0
|
164
|
-
@notify_timestamp = Time.now
|
165
|
-
setup_timer timeout
|
166
|
-
else
|
167
|
-
@timer = nil
|
168
|
-
end
|
169
|
-
end
|
178
|
+
# environment variable name for connect_timeout fallback value
|
179
|
+
@@connect_timeout_envvar = conndefaults.find{|d| d[:keyword] == "connect_timeout" }[:envvar]
|
170
180
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
end
|
179
|
-
else
|
180
|
-
setup_timer timeout, last_interval
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
def notify_readable
|
186
|
-
result = false
|
187
|
-
@client.consume_input
|
188
|
-
until @client.is_busy
|
189
|
-
if (single_result = @client.get_result).nil?
|
190
|
-
if (result = @last_result).nil?
|
191
|
-
error = PG::Error.new(@client.error_message)
|
192
|
-
error.instance_variable_set(:@connection, @client)
|
193
|
-
raise error
|
194
|
-
end
|
195
|
-
result.check
|
196
|
-
detach
|
197
|
-
@timer.cancel if @timer
|
198
|
-
break
|
199
|
-
end
|
200
|
-
@last_result.clear if @last_result
|
201
|
-
@last_result = single_result
|
202
|
-
end
|
203
|
-
rescue Exception => e
|
204
|
-
detach
|
205
|
-
@timer.cancel if @timer
|
206
|
-
if e.is_a?(PG::Error)
|
207
|
-
@client.async_autoreconnect!(@deferrable, e, &@send_proc)
|
208
|
-
else
|
209
|
-
@deferrable.fail(e)
|
210
|
-
end
|
211
|
-
else
|
212
|
-
if result == false
|
213
|
-
@notify_timestamp = Time.now if @timer
|
214
|
-
else
|
215
|
-
@deferrable.succeed(result)
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
module ConnectWatcher
|
221
|
-
def initialize(client, deferrable, poll_method)
|
222
|
-
@client = client
|
223
|
-
@deferrable = deferrable
|
224
|
-
@poll_method = :"#{poll_method}_poll"
|
225
|
-
if (timeout = client.connect_timeout) > 0
|
226
|
-
@timer = ::EM::Timer.new(timeout) do
|
227
|
-
begin
|
228
|
-
detach
|
229
|
-
@deferrable.protect do
|
230
|
-
raise PG::Error, "timeout expired (async)"
|
231
|
-
end
|
232
|
-
ensure
|
233
|
-
@client.finish unless reconnecting?
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
def reconnecting?
|
240
|
-
@poll_method == :reset_poll
|
241
|
-
end
|
181
|
+
DEFAULT_ASYNC_VARS = {
|
182
|
+
:@async_autoreconnect => nil,
|
183
|
+
:@connect_timeout => nil,
|
184
|
+
:@query_timeout => 0,
|
185
|
+
:@on_autoreconnect => nil,
|
186
|
+
:@async_command_aborted => false,
|
187
|
+
}.freeze
|
242
188
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
def notify_readable
|
248
|
-
poll_connection_and_check
|
249
|
-
end
|
250
|
-
|
251
|
-
def poll_connection_and_check
|
252
|
-
case @client.__send__(@poll_method)
|
253
|
-
when PG::PGRES_POLLING_READING
|
254
|
-
self.notify_readable = true
|
255
|
-
self.notify_writable = false
|
256
|
-
when PG::PGRES_POLLING_WRITING
|
257
|
-
self.notify_writable = true
|
258
|
-
self.notify_readable = false
|
259
|
-
when PG::PGRES_POLLING_OK, PG::PGRES_POLLING_FAILED
|
260
|
-
@timer.cancel if @timer
|
261
|
-
detach
|
262
|
-
@deferrable.protect_and_succeed do
|
263
|
-
unless @client.status == PG::CONNECTION_OK
|
264
|
-
begin
|
265
|
-
raise PG::Error, @client.error_message
|
266
|
-
ensure
|
267
|
-
@client.finish unless reconnecting?
|
268
|
-
end
|
269
|
-
end
|
270
|
-
# mimic blocking connect behavior
|
271
|
-
@client.set_default_encoding unless reconnecting?
|
272
|
-
@client
|
273
|
-
end
|
274
|
-
end
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
def self.parse_async_args(args)
|
279
|
-
async_args = {
|
280
|
-
:@async_autoreconnect => nil,
|
281
|
-
:@connect_timeout => 0,
|
282
|
-
:@query_timeout => 0,
|
283
|
-
:@on_autoreconnect => nil,
|
284
|
-
:@async_command_aborted => false,
|
285
|
-
}
|
189
|
+
# @!visibility private
|
190
|
+
def self.parse_async_options(args)
|
191
|
+
options = DEFAULT_ASYNC_VARS.dup
|
286
192
|
if args.last.is_a? Hash
|
287
193
|
args[-1] = args.last.reject do |key, value|
|
288
|
-
case key.
|
289
|
-
when
|
290
|
-
|
194
|
+
case key.to_sym
|
195
|
+
when :async_autoreconnect
|
196
|
+
options[:@async_autoreconnect] = value
|
291
197
|
true
|
292
|
-
when
|
293
|
-
raise ArgumentError
|
294
|
-
when
|
198
|
+
when :on_reconnect
|
199
|
+
raise ArgumentError, "on_reconnect is no longer supported, use on_autoreconnect"
|
200
|
+
when :on_autoreconnect
|
295
201
|
if value.respond_to? :call
|
296
|
-
|
297
|
-
|
202
|
+
options[:@on_autoreconnect] = value
|
203
|
+
options[:@async_autoreconnect] = true if options[:@async_autoreconnect].nil?
|
204
|
+
else
|
205
|
+
raise ArgumentError, "on_autoreconnect must respond to `call'"
|
298
206
|
end
|
299
207
|
true
|
300
|
-
when
|
301
|
-
|
208
|
+
when :connect_timeout
|
209
|
+
options[:@connect_timeout] = value.to_f
|
302
210
|
false
|
303
|
-
when
|
304
|
-
|
211
|
+
when :query_timeout
|
212
|
+
options[:@query_timeout] = value.to_f
|
305
213
|
true
|
306
214
|
end
|
307
215
|
end
|
308
216
|
end
|
309
|
-
|
310
|
-
|
217
|
+
options[:@async_autoreconnect] = !!options[:@async_autoreconnect]
|
218
|
+
options[:@connect_timeout] ||= ENV[@@connect_timeout_envvar].to_f
|
219
|
+
options
|
311
220
|
end
|
312
221
|
|
222
|
+
# @!group Deferrable connection methods
|
223
|
+
|
313
224
|
# Attempts to establish the connection asynchronously.
|
314
|
-
#
|
315
|
-
#
|
316
|
-
#
|
317
|
-
#
|
318
|
-
#
|
319
|
-
#
|
320
|
-
#
|
321
|
-
#
|
322
|
-
#
|
323
|
-
#
|
324
|
-
|
225
|
+
#
|
226
|
+
# @return [FeaturedDeferrable]
|
227
|
+
# @yieldparam pg [Client|PG::Error] new and connected client instance on
|
228
|
+
# success or an instance of raised PG::Error
|
229
|
+
#
|
230
|
+
# Pass the block to the returned deferrable's +callback+ to obtain newly
|
231
|
+
# created and already connected {Client} object. In case of connection
|
232
|
+
# error +errback+ hook receives an error object as an argument.
|
233
|
+
# If the block is provided it's bound to both +callback+ and +errback+
|
234
|
+
# hooks of the returned deferrable.
|
235
|
+
#
|
236
|
+
# Special {Client} options (e.g.: {#async_autoreconnect}) must be
|
237
|
+
# provided as +connection_hash+ argument variant. They will be ignored
|
238
|
+
# if passed as a +connection_string+.
|
239
|
+
#
|
240
|
+
# +client_encoding+ *will* be set according to +Encoding.default_internal+.
|
241
|
+
#
|
242
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-c-new PG::Connection.new
|
243
|
+
def self.connect_defer(*args, &blk)
|
325
244
|
df = PG::EM::FeaturedDeferrable.new(&blk)
|
326
|
-
async_args =
|
245
|
+
async_args = parse_async_options(args)
|
327
246
|
conn = df.protect { connect_start(*args) }
|
328
247
|
if conn
|
329
248
|
async_args.each {|k, v| conn.instance_variable_set(k, v) }
|
330
|
-
::EM.watch(conn.
|
249
|
+
::EM.watch(conn.socket_io, ConnectWatcher, conn, df, false).
|
250
|
+
poll_connection_and_check
|
331
251
|
end
|
332
252
|
df
|
333
253
|
end
|
334
254
|
|
255
|
+
class << self
|
256
|
+
# @deprecated Use {connect_defer} instead.
|
257
|
+
alias_method :async_connect, :connect_defer
|
258
|
+
end
|
259
|
+
|
335
260
|
# Attempts to reset the connection asynchronously.
|
336
|
-
# There are no arguments, except block argument.
|
337
261
|
#
|
338
|
-
#
|
339
|
-
#
|
340
|
-
#
|
341
|
-
|
262
|
+
# @return [FeaturedDeferrable]
|
263
|
+
# @yieldparam pg [Client|PG::Error] reconnected client instance on
|
264
|
+
# success or an instance of raised PG::Error
|
265
|
+
#
|
266
|
+
# Pass the block to the returned deferrable's +callback+ to execute
|
267
|
+
# after successfull reset.
|
268
|
+
# If the block is provided it's bound to +callback+ and +errback+ hooks
|
269
|
+
# of the returned deferrable.
|
270
|
+
def reset_defer(&blk)
|
342
271
|
@async_command_aborted = false
|
343
272
|
df = PG::EM::FeaturedDeferrable.new(&blk)
|
273
|
+
# there can be only one watch handler over the socket
|
274
|
+
# apparently eventmachine has hard time dealing with more than one
|
275
|
+
# for blocking reset this is not needed
|
276
|
+
if @watcher
|
277
|
+
@watcher.detach if @watcher.watching?
|
278
|
+
@watcher = nil
|
279
|
+
end
|
344
280
|
ret = df.protect(:fail) { reset_start }
|
345
281
|
unless ret == :fail
|
346
|
-
::EM.watch(self.
|
282
|
+
::EM.watch(self.socket_io, ConnectWatcher, self, df, true).
|
283
|
+
poll_connection_and_check
|
347
284
|
end
|
348
285
|
df
|
349
286
|
end
|
350
287
|
|
351
|
-
#
|
288
|
+
# @deprecated Use {reset_defer} instead.
|
289
|
+
alias_method :async_reset, :reset_defer
|
290
|
+
|
291
|
+
# @!endgroup
|
292
|
+
|
293
|
+
# @!group Auto-sensing fiber-synchronized connection methods
|
294
|
+
|
295
|
+
# Attempts to reset the connection.
|
296
|
+
#
|
297
|
+
# Performs command asynchronously yielding from current fiber
|
298
|
+
# if EventMachine reactor is running and current fiber isn't the root
|
299
|
+
# fiber. Other fibers can process while waiting for the server to
|
300
|
+
# complete the request.
|
301
|
+
#
|
302
|
+
# Otherwise performs a thread-blocking call to the parent method.
|
303
|
+
#
|
304
|
+
# @raise [PG::Error]
|
305
|
+
#
|
306
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-reset PG::Connection#reset
|
352
307
|
def reset
|
353
308
|
@async_command_aborted = false
|
354
|
-
|
309
|
+
if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
|
310
|
+
reset_defer {|r| f.resume(r) }
|
311
|
+
|
312
|
+
conn = Fiber.yield
|
313
|
+
raise conn if conn.is_a?(::Exception)
|
314
|
+
conn
|
315
|
+
else
|
316
|
+
super
|
317
|
+
end
|
355
318
|
end
|
356
319
|
|
357
|
-
# Creates new instance of PG::EM::Client and attempts to establish
|
358
|
-
#
|
320
|
+
# Creates new instance of PG::EM::Client and attempts to establish
|
321
|
+
# connection.
|
359
322
|
#
|
360
|
-
#
|
361
|
-
#
|
362
|
-
#
|
363
|
-
#
|
364
|
-
#
|
323
|
+
# Performs command asynchronously yielding from current fiber
|
324
|
+
# if EventMachine reactor is running and current fiber isn't the root
|
325
|
+
# fiber. Other fibers can process while waiting for the server to
|
326
|
+
# complete the request.
|
327
|
+
#
|
328
|
+
# Otherwise performs a thread-blocking call to the parent method.
|
329
|
+
#
|
330
|
+
# @raise [PG::Error]
|
331
|
+
#
|
332
|
+
# Special {Client} options (e.g.: {#async_autoreconnect}) must be
|
333
|
+
# provided as +connection_hash+ argument variant. They will be ignored
|
334
|
+
# if passed as a +connection_string+.
|
335
|
+
#
|
336
|
+
# +client_encoding+ *will* be set according to +Encoding.default_internal+.
|
337
|
+
#
|
338
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-c-new PG::Connection.new
|
339
|
+
def self.new(*args, &blk)
|
340
|
+
if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
|
341
|
+
connect_defer(*args) {|r| f.resume(r) }
|
342
|
+
|
343
|
+
conn = Fiber.yield
|
344
|
+
raise conn if conn.is_a?(::Exception)
|
345
|
+
if block_given?
|
346
|
+
begin
|
347
|
+
yield conn
|
348
|
+
ensure
|
349
|
+
conn.finish
|
350
|
+
end
|
351
|
+
else
|
352
|
+
conn
|
353
|
+
end
|
354
|
+
else
|
355
|
+
super(*args)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
# @!visibility private
|
365
360
|
def initialize(*args)
|
366
|
-
Client.
|
361
|
+
Client.parse_async_options(args).each {|k, v| instance_variable_set(k, v) }
|
367
362
|
super(*args)
|
368
363
|
end
|
369
364
|
|
370
|
-
|
371
|
-
|
372
|
-
|
365
|
+
class << self
|
366
|
+
alias_method :connect, :new
|
367
|
+
alias_method :open, :new
|
368
|
+
alias_method :setdb, :new
|
369
|
+
alias_method :setdblogin, :new
|
370
|
+
end
|
371
|
+
|
372
|
+
# @!endgroup
|
373
|
+
|
374
|
+
# Closes the backend connection.
|
375
|
+
#
|
376
|
+
# Detaches watch handler to prevent memory leak then
|
377
|
+
# calls parent PG::Connection#finish[http://deveiate.org/code/pg/PG/Connection.html#method-i-finish].
|
378
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-finish PG::Connection#finish
|
379
|
+
def finish
|
380
|
+
super
|
381
|
+
if @watcher
|
382
|
+
@watcher.detach if @watcher.watching?
|
383
|
+
@watcher = nil
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
alias_method :close, :finish
|
388
|
+
|
389
|
+
# Returns status of connection: PG::CONNECTION_OK or PG::CONNECTION_BAD.
|
390
|
+
#
|
391
|
+
# @return [Number]
|
392
|
+
# Returns +PG::CONNECTION_BAD+ for connections with +async_command_aborted+
|
393
|
+
# flag set by expired query timeout. Otherwise return whatever PG::Connection#status returns.
|
394
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-status PG::Connection#status
|
373
395
|
def status
|
374
396
|
if @async_command_aborted
|
375
397
|
PG::CONNECTION_BAD
|
@@ -378,53 +400,155 @@ module PG
|
|
378
400
|
end
|
379
401
|
end
|
380
402
|
|
381
|
-
#
|
403
|
+
# @!visibility private
|
404
|
+
# Perform auto re-connect. Used internally.
|
382
405
|
def async_autoreconnect!(deferrable, error, &send_proc)
|
383
|
-
if
|
384
|
-
|
385
|
-
|
406
|
+
# reconnect only if connection is bad and flag is set
|
407
|
+
if self.status != PG::CONNECTION_OK && async_autoreconnect
|
408
|
+
# check if transaction was active
|
409
|
+
was_in_transaction = case @last_transaction_status
|
410
|
+
when PG::PQTRANS_IDLE, PG::PQTRANS_UNKNOWN
|
411
|
+
false
|
412
|
+
else
|
413
|
+
true
|
414
|
+
end
|
415
|
+
# reset asynchronously
|
416
|
+
reset_df = reset_defer
|
417
|
+
# just fail on reset failure
|
418
|
+
reset_df.errback { |ex| deferrable.fail ex }
|
419
|
+
# reset succeeds
|
386
420
|
reset_df.callback do
|
421
|
+
# handle on_autoreconnect
|
387
422
|
if on_autoreconnect
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
423
|
+
# wrap in a fiber, so on_autoreconnect code may yield from it
|
424
|
+
Fiber.new do
|
425
|
+
# call on_autoreconnect handler and fail if it raises an error
|
426
|
+
returned_df = begin
|
427
|
+
on_autoreconnect.call(self, error)
|
428
|
+
rescue => ex
|
429
|
+
ex
|
430
|
+
end
|
431
|
+
if returned_df.respond_to?(:callback) && returned_df.respond_to?(:errback)
|
432
|
+
# the handler returned a deferrable
|
433
|
+
returned_df.callback do
|
434
|
+
if was_in_transaction
|
435
|
+
# there was a transaction in progress, fail anyway
|
436
|
+
deferrable.fail error
|
437
|
+
else
|
438
|
+
# try to call failed query command again
|
439
|
+
deferrable.protect(&send_proc)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
# fail when handler's deferrable fails
|
443
|
+
returned_df.errback { |ex| deferrable.fail ex }
|
444
|
+
elsif returned_df.is_a?(Exception)
|
445
|
+
# tha handler returned an exception object, so fail with it
|
446
|
+
deferrable.fail returned_df
|
447
|
+
elsif returned_df == false || (was_in_transaction && returned_df != true)
|
448
|
+
# tha handler returned false or raised an exception
|
449
|
+
# or there was an active transaction and handler didn't return true
|
450
|
+
deferrable.fail error
|
451
|
+
else
|
452
|
+
# try to call failed query command again
|
453
|
+
deferrable.protect(&send_proc)
|
454
|
+
end
|
455
|
+
end.resume
|
456
|
+
elsif was_in_transaction
|
457
|
+
# there was a transaction in progress, fail anyway
|
458
|
+
deferrable.fail error
|
399
459
|
else
|
460
|
+
# no on_autoreconnect handler, no transaction, then
|
461
|
+
# try to call failed query command again
|
400
462
|
deferrable.protect(&send_proc)
|
401
463
|
end
|
402
464
|
end
|
403
465
|
else
|
404
|
-
|
466
|
+
# connection is good, or the async_autoreconnect is not set
|
467
|
+
deferrable.fail error
|
405
468
|
end
|
406
469
|
end
|
407
470
|
|
471
|
+
# @!macro deferrable_api
|
472
|
+
# @yieldparam result [PG::Result|Error] command result on success or a PG::Error instance on error.
|
473
|
+
# @return [FeaturedDeferrable]
|
474
|
+
# Use the returned deferrable's +callback+ and +errback+ method to get the result.
|
475
|
+
# If the block is provided it's bound to both the +callback+ and +errback+ hooks
|
476
|
+
# of the returned deferrable.
|
477
|
+
|
478
|
+
# @!group Deferrable command methods
|
479
|
+
|
480
|
+
# @!method exec_defer(sql, params=nil, result_format=nil, &blk)
|
481
|
+
# Sends SQL query request specified by +sql+ to PostgreSQL for asynchronous processing,
|
482
|
+
# and immediately returns with +deferrable+.
|
483
|
+
#
|
484
|
+
# @macro deferrable_api
|
485
|
+
#
|
486
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec PG::Connection#exec
|
487
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_params PG::Connection#exec_params
|
488
|
+
#
|
489
|
+
# @!method prepare_defer(stmt_name, sql, param_types=nil, &blk)
|
490
|
+
# Prepares statement +sql+ with name +stmt_name+ to be executed later asynchronously,
|
491
|
+
# and immediately returns with deferrable.
|
492
|
+
#
|
493
|
+
# @macro deferrable_api
|
494
|
+
#
|
495
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-prepare PG::Connection#prepare
|
496
|
+
#
|
497
|
+
# @!method exec_prepared_defer(statement_name, params=nil, result_format=nil, &blk)
|
498
|
+
# Execute prepared named statement specified by +statement_name+ asynchronously,
|
499
|
+
# and immediately returns with deferrable.
|
500
|
+
#
|
501
|
+
# @macro deferrable_api
|
502
|
+
#
|
503
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query_prepared PG::Connection#send_query_prepared
|
504
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared PG::Connection#send_exec_prepared
|
505
|
+
#
|
506
|
+
# @!method describe_prepared_defer(statement_name, &blk)
|
507
|
+
# Asynchronously sends command to retrieve information about the prepared statement +statement_name+,
|
508
|
+
# and immediately returns with deferrable.
|
509
|
+
#
|
510
|
+
# @macro deferrable_api
|
511
|
+
#
|
512
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_prepared PG::Connection#describe_prepared
|
513
|
+
#
|
514
|
+
# @!method describe_portal_defer(portal_name, &blk)
|
515
|
+
# Asynchronously sends command to retrieve information about the portal +portal_name+,
|
516
|
+
# and immediately returns with deferrable.
|
517
|
+
#
|
518
|
+
# @macro deferrable_api
|
519
|
+
#
|
520
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_portal PG::Connection#describe_portal
|
521
|
+
#
|
408
522
|
%w(
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
class_eval <<-EOD
|
417
|
-
def
|
523
|
+
exec_defer send_query
|
524
|
+
prepare_defer send_prepare
|
525
|
+
exec_prepared_defer send_query_prepared
|
526
|
+
describe_prepared_defer send_describe_prepared
|
527
|
+
describe_portal_defer send_describe_portal
|
528
|
+
).each_slice(2) do |defer_name, send_name|
|
529
|
+
|
530
|
+
class_eval <<-EOD, __FILE__, __LINE__
|
531
|
+
def #{defer_name}(*args, &blk)
|
418
532
|
df = PG::EM::FeaturedDeferrable.new(&blk)
|
419
533
|
send_proc = proc do
|
420
534
|
#{send_name}(*args)
|
421
|
-
|
535
|
+
if @watcher && @watcher.watching?
|
536
|
+
@watcher.watch_query(df, send_proc)
|
537
|
+
else
|
538
|
+
@watcher = ::EM.watch(self.socket_io, Watcher, self).
|
539
|
+
watch_query(df, send_proc)
|
540
|
+
end
|
422
541
|
end
|
423
542
|
begin
|
424
|
-
|
543
|
+
if @async_command_aborted
|
544
|
+
error = ConnectionBad.new("previous query expired, need connection reset")
|
545
|
+
error.instance_variable_set(:@connection, self)
|
546
|
+
raise error
|
547
|
+
end
|
548
|
+
@last_transaction_status = transaction_status
|
425
549
|
send_proc.call
|
426
550
|
rescue PG::Error => e
|
427
|
-
async_autoreconnect!(df, e, &send_proc)
|
551
|
+
::EM.next_tick { async_autoreconnect!(df, e, &send_proc) }
|
428
552
|
rescue Exception => e
|
429
553
|
::EM.next_tick { df.fail(e) }
|
430
554
|
end
|
@@ -432,61 +556,231 @@ module PG
|
|
432
556
|
end
|
433
557
|
EOD
|
434
558
|
|
435
|
-
class_eval <<-EOD
|
436
|
-
def #{name}(*args, &blk)
|
437
|
-
if ::EM.reactor_running?
|
438
|
-
async_#{name}(*args, &blk)
|
439
|
-
else
|
440
|
-
super(*args, &blk)
|
441
|
-
end
|
442
|
-
end
|
443
|
-
EOD
|
444
|
-
|
445
559
|
end
|
446
560
|
|
447
|
-
alias_method :
|
448
|
-
alias_method :
|
561
|
+
alias_method :query_defer, :exec_defer
|
562
|
+
alias_method :async_query_defer, :exec_defer
|
563
|
+
alias_method :async_exec_defer, :exec_defer
|
564
|
+
alias_method :exec_params_defer, :exec_defer
|
565
|
+
|
566
|
+
# @!endgroup
|
567
|
+
|
568
|
+
# @!macro auto_synchrony_api
|
569
|
+
# Performs command asynchronously yielding current fiber
|
570
|
+
# if EventMachine reactor is running and the current fiber isn't the
|
571
|
+
# root fiber. Other fibers can process while waiting for the server
|
572
|
+
# to complete the request.
|
573
|
+
#
|
574
|
+
# Otherwise performs a blocking call to parent method.
|
575
|
+
#
|
576
|
+
# @yieldparam result [PG::Result] command result on success
|
577
|
+
# @return [PG::Result] if block wasn't given
|
578
|
+
# @return [Object] result of the given block
|
579
|
+
# @raise [PG::Error]
|
580
|
+
|
581
|
+
# @!group Auto-sensing thread or fiber blocking command methods
|
582
|
+
|
583
|
+
# @!method exec(sql, &blk)
|
584
|
+
# Sends SQL query request specified by +sql+ to PostgreSQL.
|
585
|
+
#
|
586
|
+
# @macro auto_synchrony_api
|
587
|
+
#
|
588
|
+
# @see PG::EM::Client#exec_defer
|
589
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec PG::Connection#exec
|
590
|
+
#
|
591
|
+
# @!method exec_params(sql, params=nil, result_format=nil, &blk)
|
592
|
+
# Sends SQL query request specified by +sql+ with optional +params+ and +result_format+ to PostgreSQL.
|
593
|
+
#
|
594
|
+
# @macro auto_synchrony_api
|
595
|
+
#
|
596
|
+
# @see PG::EM::Client#exec_params_defer
|
597
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_params PG::Connection#exec_params
|
598
|
+
#
|
599
|
+
# @!method prepare(stmt_name, sql, param_types=nil, &blk)
|
600
|
+
# Prepares statement +sql+ with name +stmt_name+ to be executed later.
|
601
|
+
#
|
602
|
+
# @macro auto_synchrony_api
|
603
|
+
#
|
604
|
+
# @see PG::EM::Client#prepare_defer
|
605
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-prepare PG::Connection#prepare
|
606
|
+
#
|
607
|
+
# @!method exec_prepared(statement_name, params=nil, result_format=nil, &blk)
|
608
|
+
# Execute prepared named statement specified by +statement_name+.
|
609
|
+
#
|
610
|
+
# @macro auto_synchrony_api
|
611
|
+
#
|
612
|
+
# @see PG::EM::Client#exec_prepared_defer
|
613
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared PG::Connection#exec_prepared
|
614
|
+
#
|
615
|
+
# @!method describe_prepared(statement_name, &blk)
|
616
|
+
# Retrieve information about the prepared statement +statement_name+,
|
617
|
+
#
|
618
|
+
# @macro auto_synchrony_api
|
619
|
+
#
|
620
|
+
# @see PG::EM::Client#describe_prepared_defer
|
621
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_prepared PG::Connection#describe_prepared
|
622
|
+
#
|
623
|
+
# @!method describe_portal(portal_name, &blk)
|
624
|
+
# Retrieve information about the portal +portal_name+,
|
625
|
+
#
|
626
|
+
# @macro auto_synchrony_api
|
627
|
+
#
|
628
|
+
# @see PG::EM::Client#describe_portal_defer
|
629
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_portal PG::Connection#describe_portal
|
630
|
+
#
|
631
|
+
%w(
|
632
|
+
exec exec_defer
|
633
|
+
exec_params exec_defer
|
634
|
+
exec_prepared exec_prepared_defer
|
635
|
+
prepare prepare_defer
|
636
|
+
describe_prepared describe_prepared_defer
|
637
|
+
describe_portal describe_portal_defer
|
638
|
+
).each_slice(2) do |name, defer_name|
|
639
|
+
|
640
|
+
class_eval <<-EOD, __FILE__, __LINE__
|
641
|
+
def #{name}(*args, &blk)
|
642
|
+
if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
|
643
|
+
#{defer_name}(*args) do |res|
|
644
|
+
f.resume(res)
|
645
|
+
end
|
449
646
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
647
|
+
result = Fiber.yield
|
648
|
+
raise result if result.is_a?(::Exception)
|
649
|
+
if block_given?
|
650
|
+
begin
|
651
|
+
yield result
|
652
|
+
ensure
|
653
|
+
result.clear
|
654
|
+
end
|
655
|
+
else
|
656
|
+
result
|
657
|
+
end
|
658
|
+
else
|
659
|
+
super
|
660
|
+
end
|
455
661
|
end
|
456
|
-
|
457
|
-
warn "warning: Failed to set the default_internal encoding to #{Encoding.default_internal}: '#{self.error_message}'"
|
458
|
-
Encoding.default_internal
|
459
|
-
end
|
662
|
+
EOD
|
460
663
|
end
|
461
664
|
|
462
|
-
|
463
|
-
|
665
|
+
alias_method :query, :exec
|
666
|
+
alias_method :async_query, :exec
|
667
|
+
alias_method :async_exec, :exec
|
464
668
|
|
465
|
-
|
466
|
-
unless Result.method_defined? :check
|
467
|
-
class Result
|
468
|
-
def check
|
469
|
-
case result_status
|
470
|
-
when PG::PGRES_BAD_RESPONSE,
|
471
|
-
PG::PGRES_FATAL_ERROR,
|
472
|
-
PG::PGRES_NONFATAL_ERROR
|
473
|
-
error = PG::Error.new(error_message)
|
474
|
-
error.instance_variable_set(:@result, self)
|
475
|
-
error.instance_variable_set(:@connection, @connection)
|
476
|
-
raise error
|
477
|
-
end
|
478
|
-
end
|
479
|
-
alias_method :check_result, :check
|
480
|
-
end
|
669
|
+
# @!endgroup
|
481
670
|
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
671
|
+
TRAN_BEGIN_QUERY = 'BEGIN'
|
672
|
+
TRAN_ROLLBACK_QUERY = 'ROLLBACK'
|
673
|
+
TRAN_COMMIT_QUERY = 'COMMIT'
|
674
|
+
# Executes a BEGIN at the start of the block and a COMMIT at the end
|
675
|
+
# of the block or ROLLBACK if any exception occurs.
|
676
|
+
#
|
677
|
+
# @note Avoid using PG::EM::Client#*_defer calls inside the block or make sure
|
678
|
+
# all queries are completed before the provided block terminates.
|
679
|
+
# @return [Object] result of the block
|
680
|
+
# @yieldparam client [self]
|
681
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-transaction PG::Connection#transaction
|
682
|
+
#
|
683
|
+
# Calls to {#transaction} may be nested, however without sub-transactions
|
684
|
+
# (save points). If the innermost transaction block raises an error
|
685
|
+
# the transaction is rolled back to the state before the outermost
|
686
|
+
# transaction began.
|
687
|
+
#
|
688
|
+
# This is an extension to the +PG::Connection#transaction+ method
|
689
|
+
# as it does not support nesting in this way.
|
690
|
+
#
|
691
|
+
# The method is sensitive to the transaction status and will safely
|
692
|
+
# rollback on any sql error even when it was catched by some rescue block.
|
693
|
+
# But consider that rescuing any sql error within an utility method
|
694
|
+
# is a bad idea.
|
695
|
+
#
|
696
|
+
# This method works in both blocking/async modes (regardles of the reactor state)
|
697
|
+
# and is considered as a generic extension to the +PG::Connection#transaction+
|
698
|
+
# method.
|
699
|
+
#
|
700
|
+
# @example Nested transaction example
|
701
|
+
# def add_comment(user_id, text)
|
702
|
+
# db.transaction do
|
703
|
+
# cmt_id = db.query(
|
704
|
+
# 'insert into comments (text) where user_id=$1 values ($2) returning id',
|
705
|
+
# [user_id, text]).getvalue(0,0)
|
706
|
+
# db.query(
|
707
|
+
# 'update users set last_comment_id=$2 where id=$1', [user_id, cmt_id])
|
708
|
+
# cmt_id
|
709
|
+
# end
|
710
|
+
# end
|
711
|
+
#
|
712
|
+
# def update_comment_count(page_id)
|
713
|
+
# db.transaction do
|
714
|
+
# count = db.query('select count(*) from comments where page_id=$1', [page_id]).getvalue(0,0)
|
715
|
+
# db.query('update pages set comment_count=$2 where id=$1', [page_id, count])
|
716
|
+
# end
|
717
|
+
# end
|
718
|
+
#
|
719
|
+
# # to run add_comment and update_comment_count within the same transaction
|
720
|
+
# db.transaction do
|
721
|
+
# add_comment(user_id, some_text)
|
722
|
+
# update_comment_count(page_id)
|
723
|
+
# end
|
724
|
+
#
|
725
|
+
def transaction
|
726
|
+
raise ArgumentError, 'Must supply block for PG::EM::Client#transaction' unless block_given?
|
727
|
+
tcount = @client_tran_count.to_i
|
728
|
+
|
729
|
+
case transaction_status
|
730
|
+
when PG::PQTRANS_IDLE
|
731
|
+
# there is no transaction yet, so let's begin
|
732
|
+
exec(TRAN_BEGIN_QUERY)
|
733
|
+
# reset transaction count in case user code rolled it back before
|
734
|
+
tcount = 0 if tcount != 0
|
735
|
+
when PG::PQTRANS_INTRANS
|
736
|
+
# transaction in progress, leave it be
|
737
|
+
else
|
738
|
+
# transaction failed, is in unknown state or command is active
|
739
|
+
# in any case calling begin will raise server transaction error
|
740
|
+
exec(TRAN_BEGIN_QUERY) # raises PG::InFailedSqlTransaction
|
741
|
+
end
|
742
|
+
# memoize nested count
|
743
|
+
@client_tran_count = tcount + 1
|
744
|
+
begin
|
745
|
+
|
746
|
+
result = yield self
|
747
|
+
|
748
|
+
rescue
|
749
|
+
# error was raised
|
750
|
+
case transaction_status
|
751
|
+
when PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR
|
752
|
+
# do not rollback if transaction was rolled back before
|
753
|
+
# or is in unknown state, which means connection reset is needed
|
754
|
+
# and rollback only from the outermost transaction block
|
755
|
+
exec(TRAN_ROLLBACK_QUERY) if tcount.zero?
|
756
|
+
end
|
757
|
+
# raise again
|
758
|
+
raise
|
759
|
+
else
|
760
|
+
# we are good (but not out of woods yet)
|
761
|
+
case transaction_status
|
762
|
+
when PG::PQTRANS_INTRANS
|
763
|
+
# commit only from the outermost transaction block
|
764
|
+
exec(TRAN_COMMIT_QUERY) if tcount.zero?
|
765
|
+
when PG::PQTRANS_INERROR
|
766
|
+
# no ruby error was raised (or an error was rescued in code block)
|
767
|
+
# but there was an sql error anyway
|
768
|
+
# so rollback after the outermost block
|
769
|
+
exec(TRAN_ROLLBACK_QUERY) if tcount.zero?
|
770
|
+
when PG::PQTRANS_IDLE
|
771
|
+
# the code block has terminated the transaction on its own
|
772
|
+
# so just reset the counter
|
773
|
+
tcount = 0
|
774
|
+
else
|
775
|
+
# something isn't right, so provoke an error just in case
|
776
|
+
exec(TRAN_ROLLBACK_QUERY) if tcount.zero?
|
777
|
+
end
|
487
778
|
result
|
779
|
+
ensure
|
780
|
+
@client_tran_count = tcount
|
488
781
|
end
|
489
782
|
end
|
783
|
+
|
490
784
|
end
|
491
785
|
end
|
492
786
|
|