em-pg-client 0.1.1 → 0.2.0.pre.1
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/HISTORY.rdoc +16 -9
- data/README.rdoc +294 -258
- data/benchmarks/em_pg.rb +93 -93
- data/em-pg-client.gemspec +1 -1
- data/lib/em-synchrony/pg.rb +81 -45
- data/lib/pg/em.rb +348 -175
- metadata +5 -5
data/benchmarks/em_pg.rb
CHANGED
@@ -1,93 +1,93 @@
|
|
1
|
-
$:.unshift('./lib')
|
2
|
-
require 'eventmachine'
|
3
|
-
require 'em-synchrony'
|
4
|
-
require 'em-synchrony/pg'
|
5
|
-
require "em-synchrony/fiber_iterator"
|
6
|
-
require 'pp'
|
7
|
-
require 'benchmark'
|
8
|
-
|
9
|
-
$dbname = 'alpha'
|
10
|
-
|
11
|
-
def benchmark(repeat=100)
|
12
|
-
Benchmark.bm(20) do |b|
|
13
|
-
b.report('single:') { single(repeat) }
|
14
|
-
puts
|
15
|
-
b.report('parallel 90000/1:') { parallel(repeat, 90000, 1) }
|
16
|
-
b.report('parallel 5000/5:') { parallel(repeat, 5000, 5) }
|
17
|
-
b.report('parallel 2000/10:') { parallel(repeat, 2000, 10) }
|
18
|
-
b.report('parallel 1000/20:') { parallel(repeat, 1000, 20) }
|
19
|
-
puts
|
20
|
-
patch_blocking
|
21
|
-
b.report('blocking 90000/1:') { parallel(repeat, 90000, 1) }
|
22
|
-
b.report('blocking 5000/5:') { parallel(repeat, 5000, 5) }
|
23
|
-
b.report('blocking 2000/10:') { parallel(repeat, 2000, 10) }
|
24
|
-
b.report('blocking 1000/20:') { parallel(repeat, 1000, 20) }
|
25
|
-
patch_remove_blocking
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def patch_remove_blocking
|
30
|
-
PG::EM::Client::Watcher.module_eval <<-EOE
|
31
|
-
alias_method :notify_readable, :original_notify_readable
|
32
|
-
undef :original_notify_readable
|
33
|
-
EOE
|
34
|
-
end
|
35
|
-
|
36
|
-
def patch_blocking
|
37
|
-
PG::EM::Client::Watcher.module_eval <<-EOE
|
38
|
-
alias_method :original_notify_readable, :notify_readable
|
39
|
-
def notify_readable
|
40
|
-
detach
|
41
|
-
begin
|
42
|
-
result = @client.get_last_result
|
43
|
-
rescue Exception => e
|
44
|
-
@deferrable.fail(e)
|
45
|
-
else
|
46
|
-
@deferrable.succeed(result)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
EOE
|
50
|
-
end
|
51
|
-
|
52
|
-
# retrieve resources using single select query
|
53
|
-
def single(repeat=1)
|
54
|
-
rowcount = 0
|
55
|
-
p = PGconn.new :dbname => $dbname
|
56
|
-
p.query('select count(*) from resources') do |result|
|
57
|
-
rowcount = result.getvalue(0,0).to_i
|
58
|
-
end
|
59
|
-
repeat.times do
|
60
|
-
p.query('select * from resources order by cdate') do |result|
|
61
|
-
$resources = result.values
|
62
|
-
end
|
63
|
-
end
|
64
|
-
# raise "invalid count #{$resources.length} != #{rowcount}" if $resources.length != rowcount
|
65
|
-
end
|
66
|
-
|
67
|
-
# retrieve resources using parallel queries
|
68
|
-
def parallel(repeat=1, chunk_size=2000, concurrency=10)
|
69
|
-
resources = []
|
70
|
-
rowcount = 0
|
71
|
-
EM.synchrony do
|
72
|
-
p = EM::Synchrony::ConnectionPool.new(size: concurrency) { PG::EM::Client.new :dbname => $dbname }
|
73
|
-
p.query('select count(*) from resources') do |result|
|
74
|
-
rowcount = result.getvalue(0,0).to_i
|
75
|
-
end
|
76
|
-
offsets = (rowcount / chunk_size.to_f).ceil.times.map {|n| n*chunk_size }
|
77
|
-
repeat.times do
|
78
|
-
EM::Synchrony::FiberIterator.new(offsets, concurrency).each do |offset|
|
79
|
-
p.query('select * from resources order by cdate limit $1 offset $2', [chunk_size, offset]) do |result|
|
80
|
-
resources[offset, chunk_size] = result.values
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
EM.stop
|
85
|
-
end
|
86
|
-
# raise "invalid count #{resources.length} != #{rowcount}" if resources.length != rowcount
|
87
|
-
# raise "resources != $resources" if resources != $resources
|
88
|
-
resources
|
89
|
-
end
|
90
|
-
|
91
|
-
if $0 == __FILE__
|
92
|
-
benchmark (ARGV.first || 10).to_i
|
93
|
-
end
|
1
|
+
$:.unshift('./lib')
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'em-synchrony'
|
4
|
+
require 'em-synchrony/pg'
|
5
|
+
require "em-synchrony/fiber_iterator"
|
6
|
+
require 'pp'
|
7
|
+
require 'benchmark'
|
8
|
+
|
9
|
+
$dbname = 'alpha'
|
10
|
+
|
11
|
+
def benchmark(repeat=100)
|
12
|
+
Benchmark.bm(20) do |b|
|
13
|
+
b.report('single:') { single(repeat) }
|
14
|
+
puts
|
15
|
+
b.report('parallel 90000/1:') { parallel(repeat, 90000, 1) }
|
16
|
+
b.report('parallel 5000/5:') { parallel(repeat, 5000, 5) }
|
17
|
+
b.report('parallel 2000/10:') { parallel(repeat, 2000, 10) }
|
18
|
+
b.report('parallel 1000/20:') { parallel(repeat, 1000, 20) }
|
19
|
+
puts
|
20
|
+
patch_blocking
|
21
|
+
b.report('blocking 90000/1:') { parallel(repeat, 90000, 1) }
|
22
|
+
b.report('blocking 5000/5:') { parallel(repeat, 5000, 5) }
|
23
|
+
b.report('blocking 2000/10:') { parallel(repeat, 2000, 10) }
|
24
|
+
b.report('blocking 1000/20:') { parallel(repeat, 1000, 20) }
|
25
|
+
patch_remove_blocking
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def patch_remove_blocking
|
30
|
+
PG::EM::Client::Watcher.module_eval <<-EOE
|
31
|
+
alias_method :notify_readable, :original_notify_readable
|
32
|
+
undef :original_notify_readable
|
33
|
+
EOE
|
34
|
+
end
|
35
|
+
|
36
|
+
def patch_blocking
|
37
|
+
PG::EM::Client::Watcher.module_eval <<-EOE
|
38
|
+
alias_method :original_notify_readable, :notify_readable
|
39
|
+
def notify_readable
|
40
|
+
detach
|
41
|
+
begin
|
42
|
+
result = @client.get_last_result
|
43
|
+
rescue Exception => e
|
44
|
+
@deferrable.fail(e)
|
45
|
+
else
|
46
|
+
@deferrable.succeed(result)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
EOE
|
50
|
+
end
|
51
|
+
|
52
|
+
# retrieve resources using single select query
|
53
|
+
def single(repeat=1)
|
54
|
+
rowcount = 0
|
55
|
+
p = PGconn.new :dbname => $dbname, :host => 'localhost'
|
56
|
+
p.query('select count(*) from resources') do |result|
|
57
|
+
rowcount = result.getvalue(0,0).to_i
|
58
|
+
end
|
59
|
+
repeat.times do
|
60
|
+
p.query('select * from resources order by cdate') do |result|
|
61
|
+
$resources = result.values
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# raise "invalid count #{$resources.length} != #{rowcount}" if $resources.length != rowcount
|
65
|
+
end
|
66
|
+
|
67
|
+
# retrieve resources using parallel queries
|
68
|
+
def parallel(repeat=1, chunk_size=2000, concurrency=10)
|
69
|
+
resources = []
|
70
|
+
rowcount = 0
|
71
|
+
EM.synchrony do
|
72
|
+
p = EM::Synchrony::ConnectionPool.new(size: concurrency) { PG::EM::Client.new :dbname => $dbname }
|
73
|
+
p.query('select count(*) from resources') do |result|
|
74
|
+
rowcount = result.getvalue(0,0).to_i
|
75
|
+
end
|
76
|
+
offsets = (rowcount / chunk_size.to_f).ceil.times.map {|n| n*chunk_size }
|
77
|
+
repeat.times do
|
78
|
+
EM::Synchrony::FiberIterator.new(offsets, concurrency).each do |offset|
|
79
|
+
p.query('select * from resources order by cdate limit $1 offset $2', [chunk_size, offset]) do |result|
|
80
|
+
resources[offset, chunk_size] = result.values
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
EM.stop
|
85
|
+
end
|
86
|
+
# raise "invalid count #{resources.length} != #{rowcount}" if resources.length != rowcount
|
87
|
+
# raise "resources != $resources" if resources != $resources
|
88
|
+
resources
|
89
|
+
end
|
90
|
+
|
91
|
+
if $0 == __FILE__
|
92
|
+
benchmark (ARGV.first || 10).to_i
|
93
|
+
end
|
data/em-pg-client.gemspec
CHANGED
data/lib/em-synchrony/pg.rb
CHANGED
@@ -1,45 +1,81 @@
|
|
1
|
-
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
|
9
|
-
#
|
10
|
-
|
11
|
-
# conform to *standard*
|
12
|
-
alias_method :aquery, :async_query
|
13
|
-
|
14
|
-
# fiber untangled version of theese methods:
|
15
|
-
# - exec (aliased as query)
|
16
|
-
# - exec_prepared
|
17
|
-
# - prepare
|
18
|
-
%w(exec exec_prepared prepare).each do |name|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
df.
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
if
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
1
|
+
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
|
9
|
+
#
|
10
|
+
|
11
|
+
# conform to *standard*
|
12
|
+
alias_method :aquery, :async_query
|
13
|
+
|
14
|
+
# fiber untangled version of theese methods:
|
15
|
+
# - exec (aliased as query)
|
16
|
+
# - exec_prepared
|
17
|
+
# - prepare
|
18
|
+
%w(exec exec_prepared prepare reset self.connect).each do |name|
|
19
|
+
async_name = "async_#{name.split('.').last}"
|
20
|
+
class_eval <<-EOD
|
21
|
+
def #{name}(*args, &blk)
|
22
|
+
if ::EM.reactor_running?
|
23
|
+
df = #{async_name}(*args)
|
24
|
+
f = Fiber.current
|
25
|
+
df.callback { |res| f.resume(res) }
|
26
|
+
df.errback { |err| f.resume(err) }
|
27
|
+
|
28
|
+
result = Fiber.yield
|
29
|
+
raise result if result.is_a?(::Exception)
|
30
|
+
if block_given?
|
31
|
+
yield result
|
32
|
+
else
|
33
|
+
result
|
34
|
+
end
|
35
|
+
else
|
36
|
+
super(*args, &blk)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
EOD
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
alias_method :new, :connect
|
44
|
+
alias_method :open, :connect
|
45
|
+
alias_method :setdb, :connect
|
46
|
+
alias_method :setdblogin, :connect
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :query, :exec
|
50
|
+
|
51
|
+
def async_autoreconnect!(deferrable, error, &send_proc)
|
52
|
+
if async_autoreconnect && (@async_command_aborted || self.status != PG::CONNECTION_OK)
|
53
|
+
reset_df = async_reset
|
54
|
+
reset_df.errback { |ex| deferrable.fail(ex) }
|
55
|
+
reset_df.callback do
|
56
|
+
Fiber.new do
|
57
|
+
if on_autoreconnect
|
58
|
+
returned_df = on_autoreconnect.call(self, error)
|
59
|
+
if returned_df == false
|
60
|
+
::EM.next_tick { deferrable.fail(error) }
|
61
|
+
elsif returned_df.respond_to?(:callback) && returned_df.respond_to?(:errback)
|
62
|
+
returned_df.callback { deferrable.protect(&send_proc) }
|
63
|
+
returned_df.errback { |ex| deferrable.fail(ex) }
|
64
|
+
elsif returned_df.is_a?(Exception)
|
65
|
+
::EM.next_tick { deferrable.fail(returned_df) }
|
66
|
+
else
|
67
|
+
deferrable.protect(&send_proc)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
deferrable.protect(&send_proc)
|
71
|
+
end
|
72
|
+
end.resume
|
73
|
+
end
|
74
|
+
else
|
75
|
+
::EM.next_tick { deferrable.fail(error) }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/pg/em.rb
CHANGED
@@ -1,175 +1,348 @@
|
|
1
|
-
require 'pg'
|
2
|
-
module PG
|
3
|
-
module EM
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
# +async_exec_prepared+
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
1
|
+
require 'pg'
|
2
|
+
module PG
|
3
|
+
module EM
|
4
|
+
class FeaturedDeferrable < ::EM::DefaultDeferrable
|
5
|
+
def initialize(&blk)
|
6
|
+
if block_given?
|
7
|
+
callback(&blk)
|
8
|
+
errback(&blk)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def protect(fail_value = nil)
|
13
|
+
yield
|
14
|
+
rescue Exception => e
|
15
|
+
::EM.next_tick { fail(e) }
|
16
|
+
fail_value
|
17
|
+
end
|
18
|
+
|
19
|
+
def protect_and_succeed(fail_value = nil)
|
20
|
+
ret = yield
|
21
|
+
rescue Exception => e
|
22
|
+
::EM.next_tick { fail(e) }
|
23
|
+
fail_value
|
24
|
+
else
|
25
|
+
::EM.next_tick { succeed(ret) }
|
26
|
+
ret
|
27
|
+
end
|
28
|
+
end
|
29
|
+
# == PostgreSQL EventMachine client
|
30
|
+
#
|
31
|
+
# Author:: Rafal Michalski (mailto:royaltm75@gmail.com)
|
32
|
+
# Licence:: MIT License
|
33
|
+
#
|
34
|
+
#
|
35
|
+
# PG::EM::Client is a wrapper for PG::Connection which (re)defines methods:
|
36
|
+
#
|
37
|
+
# - +async_exec+ (alias: +async_query+)
|
38
|
+
# - +async_prepare+
|
39
|
+
# - +async_exec_prepared+
|
40
|
+
#
|
41
|
+
# and following:
|
42
|
+
#
|
43
|
+
# - +exec+ (alias: +query+)
|
44
|
+
# - +exec_prepared+
|
45
|
+
# - +prepare+
|
46
|
+
#
|
47
|
+
# which autodetects if EventMachine is running and uses appropriate
|
48
|
+
# (async or sync) method version.
|
49
|
+
#
|
50
|
+
# Additionally to the above, there are asynchronous methods defined for
|
51
|
+
# establishing connection and reseting it:
|
52
|
+
#
|
53
|
+
# - +Client.async_connect+
|
54
|
+
# - +async_reset+
|
55
|
+
#
|
56
|
+
# They are async equivalents of +Client.connect+ (which is also aliased
|
57
|
+
# by PG::Connection as +new+, +open+, +setdb+, +setdblogin+) and +reset+.
|
58
|
+
#
|
59
|
+
# Async methods might try to reset connection on connection error.
|
60
|
+
# You won't even notice that (except for warning message from PG).
|
61
|
+
# If you want to detect such event use +on_autoreconnect+ property.
|
62
|
+
#
|
63
|
+
# To disable such behavior set:
|
64
|
+
# client.async_autoreconnect = false
|
65
|
+
#
|
66
|
+
# or pass as new() hash argument:
|
67
|
+
# PG::EM::Client.new database: 'bar', async_autoreconnect: false
|
68
|
+
#
|
69
|
+
# Otherwise nothing changes in PG::Connect API.
|
70
|
+
# See PG::Connect docs for arguments to above methods.
|
71
|
+
#
|
72
|
+
# *Warning:*
|
73
|
+
#
|
74
|
+
# +async_exec_prepared+ after +async_prepare+ should only be invoked on
|
75
|
+
# the *same* connection.
|
76
|
+
# If you are using connection pool, make sure to acquire single connection first.
|
77
|
+
#
|
78
|
+
class Client < PG::Connection
|
79
|
+
|
80
|
+
|
81
|
+
# Connection timeout. Changing this property only affects
|
82
|
+
# +Client.async_connect+ and +async_reset+.
|
83
|
+
# However if passed as initialization option also affects blocking
|
84
|
+
# +Client.connect+ and +reset+.
|
85
|
+
attr_accessor :connect_timeout
|
86
|
+
|
87
|
+
# (EXPERIMENTAL)
|
88
|
+
# Aborts async command processing if waiting for response from server
|
89
|
+
# exceedes +query_timeout+ seconds. This does not apply to
|
90
|
+
# +Client.async_connect+ and +async_reset+. For those two use
|
91
|
+
# +connect_timeout+ instead.
|
92
|
+
#
|
93
|
+
# To enable it set to seconds (> 0). To disable: set to 0.
|
94
|
+
# You can also specify this as initialization option.
|
95
|
+
attr_accessor :query_timeout
|
96
|
+
|
97
|
+
# Enable/disable auto-reconnect feature (+true+/+false+).
|
98
|
+
# Default is +true+.
|
99
|
+
attr_accessor :async_autoreconnect
|
100
|
+
|
101
|
+
# +on_autoreconnect+ is a user defined Proc that is called after a connection
|
102
|
+
# with the server has been re-established.
|
103
|
+
# It's invoked with +connection+ as first argument and original
|
104
|
+
# +exception+ that caused the reconnecting process as second argument.
|
105
|
+
#
|
106
|
+
# Certain rules should apply to on_autoreconnect proc:
|
107
|
+
#
|
108
|
+
# - If proc returns +false+ (explicitly, +nil+ is ignored)
|
109
|
+
# the original +exception+ is passed to +Defferable#fail+ and the send
|
110
|
+
# query command is not invoked at all.
|
111
|
+
# - If return value is an instance of exception it is passed to
|
112
|
+
# +Defferable#fail+ and the send query command is not invoked at all.
|
113
|
+
# - If return value responds to +callback+ and +errback+ methods
|
114
|
+
# (like +Deferrable+), the send query command will be bound to this
|
115
|
+
# deferrable's success callback. Otherwise the send query command is
|
116
|
+
# called immediately after on_autoreconnect proc is executed.
|
117
|
+
# - Other return values are ignored and the send query command is called
|
118
|
+
# immediately after on_autoreconnect proc is executed.
|
119
|
+
#
|
120
|
+
# You may pass this proc as +:on_autoreconnect+ option to PG::EM::Client.new.
|
121
|
+
#
|
122
|
+
# Example:
|
123
|
+
# pg.on_autoreconnect = proc do |conn, ex|
|
124
|
+
# conn.prepare("birds_by_name", "select id, name from animals order by name where species=$1", ['birds'])
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
attr_accessor :on_autoreconnect
|
128
|
+
|
129
|
+
# Used internally for marking connection as aborted on query timeout.
|
130
|
+
attr_accessor :async_command_aborted
|
131
|
+
|
132
|
+
module Watcher
|
133
|
+
def initialize(client, deferrable, send_proc)
|
134
|
+
@client = client
|
135
|
+
@deferrable = deferrable
|
136
|
+
@send_proc = send_proc
|
137
|
+
if (timeout = client.query_timeout) > 0
|
138
|
+
@timer = ::EM::Timer.new(timeout) do
|
139
|
+
detach
|
140
|
+
@client.async_command_aborted = true
|
141
|
+
IO.for_fd(@client.socket).close # break connection now (hack)
|
142
|
+
@deferrable.protect do
|
143
|
+
raise PG::Error, "query timeout expired (async)"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def notify_readable
|
150
|
+
@client.consume_input
|
151
|
+
return if @client.is_busy
|
152
|
+
@timer.cancel if @timer
|
153
|
+
detach
|
154
|
+
begin
|
155
|
+
result = @client.get_last_result
|
156
|
+
rescue PG::Error => e
|
157
|
+
@client.async_autoreconnect!(@deferrable, e, &@send_proc)
|
158
|
+
rescue Exception => e
|
159
|
+
@deferrable.fail(e)
|
160
|
+
else
|
161
|
+
@deferrable.succeed(result)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
module ConnectWatcher
|
167
|
+
def initialize(client, deferrable, poll_method)
|
168
|
+
@client = client
|
169
|
+
@deferrable = deferrable
|
170
|
+
@poll_method = :"#{poll_method}_poll"
|
171
|
+
if (timeout = client.connect_timeout) > 0
|
172
|
+
@timer = ::EM::Timer.new(timeout) do
|
173
|
+
detach
|
174
|
+
@deferrable.protect do
|
175
|
+
raise PG::Error, "timeout expired (async)"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def notify_writable
|
182
|
+
poll_connection_and_check
|
183
|
+
end
|
184
|
+
|
185
|
+
def notify_readable
|
186
|
+
poll_connection_and_check
|
187
|
+
end
|
188
|
+
|
189
|
+
def poll_connection_and_check
|
190
|
+
case @client.__send__(@poll_method)
|
191
|
+
when PG::PGRES_POLLING_READING
|
192
|
+
self.notify_readable = true
|
193
|
+
self.notify_writable = false
|
194
|
+
when PG::PGRES_POLLING_WRITING
|
195
|
+
self.notify_writable = true
|
196
|
+
self.notify_readable = false
|
197
|
+
when PG::PGRES_POLLING_OK, PG::PGRES_POLLING_FAILED
|
198
|
+
@timer.cancel if @timer
|
199
|
+
detach
|
200
|
+
success = @deferrable.protect_and_succeed do
|
201
|
+
unless @client.status == PG::CONNECTION_OK
|
202
|
+
raise PG::Error, @client.error_message
|
203
|
+
end
|
204
|
+
@client
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.parse_async_args(*args)
|
211
|
+
async_args = {
|
212
|
+
:@async_autoreconnect => true,
|
213
|
+
:@connect_timeout => 0,
|
214
|
+
:@query_timeout => 0,
|
215
|
+
:@on_autoreconnect => nil,
|
216
|
+
:@async_command_aborted => false,
|
217
|
+
}
|
218
|
+
if args.last.is_a? Hash
|
219
|
+
args.last.reject! do |key, value|
|
220
|
+
case key.to_s
|
221
|
+
when 'async_autoreconnect'
|
222
|
+
async_args[:@async_autoreconnect] = !!value
|
223
|
+
true
|
224
|
+
when 'on_reconnect'
|
225
|
+
raise ArgumentError.new("on_reconnect is no longer supported, use on_autoreconnect")
|
226
|
+
when 'on_autoreconnect'
|
227
|
+
async_args[:@on_autoreconnect] = value if value.respond_to? :call
|
228
|
+
true
|
229
|
+
when 'connect_timeout'
|
230
|
+
async_args[:@connect_timeout] = value.to_f
|
231
|
+
false
|
232
|
+
when 'query_timeout'
|
233
|
+
async_args[:@query_timeout] = value.to_f
|
234
|
+
true
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
async_args
|
239
|
+
end
|
240
|
+
|
241
|
+
# Attempt connection asynchronously.
|
242
|
+
# Pass the same arguments as to +Client.new+.
|
243
|
+
#
|
244
|
+
# Returns +deferrable+. Use it's +callback+ to obtain newly created and
|
245
|
+
# already connected +Client+ object.
|
246
|
+
# If block is provided it's bound to +callback+ and +errback+ of returned
|
247
|
+
# +deferrable+.
|
248
|
+
def self.async_connect(*args, &blk)
|
249
|
+
df = PG::EM::FeaturedDeferrable.new(&blk)
|
250
|
+
async_args = parse_async_args(*args)
|
251
|
+
conn = df.protect { connect_start(*args) }
|
252
|
+
if conn
|
253
|
+
async_args.each {|k, v| conn.instance_variable_set(k, v) }
|
254
|
+
::EM.watch(conn.socket, ConnectWatcher, conn, df, :connect).poll_connection_and_check
|
255
|
+
end
|
256
|
+
df
|
257
|
+
end
|
258
|
+
|
259
|
+
# Attempt connection reset asynchronously.
|
260
|
+
# There are no arguments.
|
261
|
+
#
|
262
|
+
# Returns +deferrable+. Use it's +callback+ to handle success.
|
263
|
+
# If block is provided it's bound to +callback+ and +errback+ of returned
|
264
|
+
# +deferrable+.
|
265
|
+
def async_reset(&blk)
|
266
|
+
@async_command_aborted = false
|
267
|
+
df = PG::EM::FeaturedDeferrable.new(&blk)
|
268
|
+
ret = df.protect(:fail) { reset_start }
|
269
|
+
unless ret == :fail
|
270
|
+
::EM.watch(self.socket, ConnectWatcher, self, df, :reset).poll_connection_and_check
|
271
|
+
end
|
272
|
+
df
|
273
|
+
end
|
274
|
+
|
275
|
+
def initialize(*args)
|
276
|
+
Client.parse_async_args(*args).each {|k, v| self.instance_variable_set(k, v) }
|
277
|
+
super(*args)
|
278
|
+
end
|
279
|
+
|
280
|
+
# Perform autoreconnect. Used internally.
|
281
|
+
def async_autoreconnect!(deferrable, error, &send_proc)
|
282
|
+
if async_autoreconnect && (@async_command_aborted || self.status != PG::CONNECTION_OK)
|
283
|
+
reset_df = async_reset
|
284
|
+
reset_df.errback { |ex| deferrable.fail(ex) }
|
285
|
+
reset_df.callback do
|
286
|
+
if on_autoreconnect
|
287
|
+
returned_df = on_autoreconnect.call(self, error)
|
288
|
+
if returned_df == false
|
289
|
+
::EM.next_tick { deferrable.fail(error) }
|
290
|
+
elsif returned_df.respond_to?(:callback) && returned_df.respond_to?(:errback)
|
291
|
+
returned_df.callback { deferrable.protect(&send_proc) }
|
292
|
+
returned_df.errback { |ex| deferrable.fail(ex) }
|
293
|
+
elsif returned_df.is_a?(Exception)
|
294
|
+
::EM.next_tick { deferrable.fail(returned_df) }
|
295
|
+
else
|
296
|
+
deferrable.protect(&send_proc)
|
297
|
+
end
|
298
|
+
else
|
299
|
+
deferrable.protect(&send_proc)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
else
|
303
|
+
::EM.next_tick { deferrable.fail(error) }
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
%w(
|
308
|
+
exec send_query
|
309
|
+
prepare send_prepare
|
310
|
+
exec_prepared send_query_prepared
|
311
|
+
).each_slice(2) do |name, send_name|
|
312
|
+
|
313
|
+
class_eval <<-EOD
|
314
|
+
def async_#{name}(*args, &blk)
|
315
|
+
df = PG::EM::FeaturedDeferrable.new(&blk)
|
316
|
+
send_proc = proc do
|
317
|
+
#{send_name}(*args)
|
318
|
+
::EM.watch(self.socket, Watcher, self, df, send_proc).notify_readable = true
|
319
|
+
end
|
320
|
+
begin
|
321
|
+
raise PG::Error, "previous query expired, need connection reset" if @async_command_aborted
|
322
|
+
send_proc.call
|
323
|
+
rescue PG::Error => e
|
324
|
+
async_autoreconnect!(df, e, &send_proc)
|
325
|
+
rescue Exception => e
|
326
|
+
::EM.next_tick { df.fail(e) }
|
327
|
+
end
|
328
|
+
df
|
329
|
+
end
|
330
|
+
EOD
|
331
|
+
|
332
|
+
class_eval <<-EOD
|
333
|
+
def #{name}(*args, &blk)
|
334
|
+
if ::EM.reactor_running?
|
335
|
+
async_#{name}(*args, &blk)
|
336
|
+
else
|
337
|
+
super(*args, &blk)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
EOD
|
341
|
+
|
342
|
+
end
|
343
|
+
|
344
|
+
alias_method :query, :exec
|
345
|
+
alias_method :async_query, :async_exec
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|