em-pg-client 0.1.1 → 0.2.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -2,7 +2,7 @@ $:.unshift "lib"
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "em-pg-client"
5
- s.version = "0.1.1"
5
+ s.version = "0.2.0.pre.1"
6
6
  s.required_ruby_version = ">= 1.9.1"
7
7
  s.date = "#{Time.now.strftime("%Y-%m-%d")}"
8
8
  s.summary = "EventMachine PostgreSQL client"
@@ -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
- class_eval <<-EOD
20
- def #{name}(*args, &blk)
21
- if ::EM.reactor_running?
22
- df = async_#{name}(*args)
23
- f = Fiber.current
24
- df.callback { |res| f.resume(res) }
25
- df.errback { |err| f.resume(err) }
26
-
27
- result = Fiber.yield
28
- raise result if result.is_a?(::Exception)
29
- if block_given?
30
- yield result
31
- else
32
- result
33
- end
34
- else
35
- super(*args, &blk)
36
- end
37
- end
38
- EOD
39
-
40
- end
41
-
42
- alias_method :query, :exec
43
- end
44
- end
45
- end
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
- # == PostgreSQL EventMachine client
5
- #
6
- # Author:: Rafal Michalski (mailto:royaltm75@gmail.com)
7
- # Licence:: MIT License
8
- #
9
- #
10
- # PG::EM::Client is a wrapper for PG::Connection which (re)defines methods:
11
- #
12
- # - +async_exec+ (alias: +async_query+)
13
- # - +async_prepare+
14
- # - +async_exec_prepared+
15
- #
16
- # and following:
17
- #
18
- # - +exec+ (alias: +query+)
19
- # - +exec_prepared+
20
- # - +prepare+
21
- #
22
- # which autodetects if EventMachine is running and uses appropriate
23
- # (async or sync) method version.
24
- #
25
- # Async methods might try to reset connection on connection error,
26
- # you won't even notice that (except for warning message from PG).
27
- #
28
- # To disable such behavior set:
29
- # client.async_autoreconnect = false
30
- #
31
- # or pass as new() hash argument:
32
- # PG::EM::Client.new database: 'bar', async_autoreconnect: false
33
- #
34
- # Otherwise nothing changes in PG::Connect API.
35
- # See PG::Connect docs for arguments to above methods.
36
- #
37
- # *Warning:*
38
- #
39
- # +async_exec_prepared+ after +async_prepare+ should only be invoked on
40
- # the *same* connection.
41
- # If you are using connection pool, make sure to acquire single connection first.
42
- #
43
- class Client < PG::Connection
44
-
45
- attr_accessor :async_autoreconnect
46
-
47
- # +on_reconnect+ is a user defined Proc that is called after a connection
48
- # with the server has been re-established.
49
- # It's invoked with +connection+ as first argument and original
50
- # +exception+ that caused the reconnecting process as second argument.
51
- #
52
- # Certain rules should apply to on_reconnect proc:
53
- #
54
- # - +async_autoreconnect+ is switched off (do not try to change it from
55
- # inside on_reconnect proc).
56
- # - If proc returns +false+ (explicitly, +nil+ is ignored)
57
- # the original +exception+ is raised and the send query command is
58
- # not invoked at all.
59
- # - If return value responds to +callback+ and +errback+ methods
60
- # (like +Deferrable+), the send query command will be bound to this
61
- # deferrable's success callback. Otherwise the send query command is called
62
- # immediately after on_reconnect proc is executed.
63
- # - Other return values are ignored.
64
- #
65
- # You may pass this proc as +:on_reconnect+ option to PG::EM::Client.new.
66
- #
67
- # Example:
68
- # pg.on_reconnect = proc do |conn, ex|
69
- # conn.prepare("birds_by_name", "select id, name from animals order by name where species=$1", ['birds'])
70
- # end
71
- #
72
- attr_accessor :on_reconnect
73
-
74
- module Watcher
75
- def initialize(client, deferrable)
76
- @client = client
77
- @deferrable = deferrable
78
- end
79
-
80
- def notify_readable
81
- @client.consume_input
82
- return if @client.is_busy
83
- detach
84
- begin
85
- result = @client.get_last_result
86
- rescue Exception => e
87
- @deferrable.fail(e)
88
- else
89
- @deferrable.succeed(result)
90
- end
91
- end
92
- end
93
-
94
- def initialize(*args)
95
- @async_autoreconnect = true
96
- @on_reconnect = nil
97
- if args.last.is_a? Hash
98
- args.last.reject! do |key, value|
99
- case key.to_s
100
- when 'async_autoreconnect'
101
- @async_autoreconnect = !!value
102
- true
103
- when 'on_reconnect'
104
- @on_reconnect = value if value.respond_to? :call
105
- true
106
- end
107
- end
108
- end
109
- super(*args)
110
- end
111
-
112
- %w(
113
- exec send_query
114
- prepare send_prepare
115
- exec_prepared send_query_prepared
116
- ).each_slice(2) do |name, send_name|
117
-
118
- class_eval <<-EOD
119
- def async_#{name}(*args, &blk)
120
- df = ::EM::DefaultDeferrable.new
121
- if block_given?
122
- df.callback(&blk)
123
- df.errback(&blk)
124
- end
125
- begin
126
- #{send_name}(*args)
127
- rescue PG::Error => e
128
- if self.status != PG::CONNECTION_OK && async_autoreconnect
129
- reset
130
- if on_reconnect
131
- begin
132
- self.async_autoreconnect = false
133
- returned_df = on_reconnect.call(self, e)
134
- raise e if returned_df == false
135
- if returned_df.respond_to?(:callback) && returned_df.respond_to?(:errback)
136
- returned_df.callback do
137
- #{send_name}(*args)
138
- ::EM.watch(self.socket, Watcher, self, df).notify_readable = true
139
- end
140
- returned_df.errback do |ex|
141
- df.fail(ex)
142
- end
143
- return df
144
- end
145
- ensure
146
- self.async_autoreconnect = true
147
- end
148
- end
149
- #{send_name}(*args)
150
- else
151
- raise e
152
- end
153
- end
154
- ::EM.watch(self.socket, Watcher, self, df).notify_readable = true
155
- df
156
- end
157
- EOD
158
-
159
- class_eval <<-EOD
160
- def #{name}(*args, &blk)
161
- if ::EM.reactor_running?
162
- async_#{name}(*args, &blk)
163
- else
164
- super(*args, &blk)
165
- end
166
- end
167
- EOD
168
-
169
- end
170
-
171
- alias_method :query, :exec
172
- alias_method :async_query, :async_exec
173
- end
174
- end
175
- end
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