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/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