em-pg-client 0.3.0 → 0.3.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/.rspec +1 -0
- data/.travis.yml +31 -0
- data/Gemfile +5 -0
- data/HISTORY.md +10 -0
- data/README.md +31 -10
- data/Rakefile +62 -25
- data/benchmarks/em_pg.rb +15 -12
- data/em-pg-client.gemspec +3 -1
- data/examples/single_row_mode.rb +67 -0
- data/lib/pg/em-version.rb +1 -1
- data/lib/pg/em.rb +187 -54
- data/lib/pg/em/client/connect_watcher.rb +2 -8
- data/lib/pg/em/client/watcher.rb +45 -21
- data/spec/em_client_autoreconnect.rb +140 -8
- data/spec/em_client_common.rb +77 -0
- data/spec/em_synchrony_client.rb +77 -9
- data/spec/em_synchrony_client_autoreconnect.rb +120 -6
- data/spec/pg_em_client_connect_finish.rb +1 -1
- data/spec/pg_em_client_options.rb +13 -3
- data/spec/spec_helper.rb +9 -0
- metadata +42 -4
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.travis.yml
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 1.9.2
|
4
|
+
- 1.9.3
|
5
|
+
- 2.0.0
|
6
|
+
env:
|
7
|
+
- PGVERSION=9.3 PGBUILD=9.3.2-1-linux-x64
|
8
|
+
- PGVERSION=9.0 PGBUILD=9.0.15-1-linux-x64
|
9
|
+
- PGVERSION=8.4 PGBUILD=8.4.19-1-linux-x64
|
10
|
+
before_install:
|
11
|
+
- sudo /etc/init.d/postgresql stop
|
12
|
+
- export PGPREFIX=/opt/PostgreSQL/$PGVERSION
|
13
|
+
- export PGDATA=$PGPREFIX/data
|
14
|
+
- export PATH="$PGPREFIX/bin:$PATH"
|
15
|
+
- wget http://get.enterprisedb.com/postgresql/postgresql-$PGBUILD.run
|
16
|
+
- chmod +x postgresql-$PGBUILD.run
|
17
|
+
- sudo ./postgresql-$PGBUILD.run --mode unattended --unattendedmodeui minimal --prefix
|
18
|
+
$PGPREFIX --datadir $PGDATA;
|
19
|
+
- sudo sed s/md5\$/trust/g $PGDATA/pg_hba.conf >/tmp/pg_hba.conf.$$
|
20
|
+
- sudo mv /tmp/pg_hba.conf.$$ $PGDATA/pg_hba.conf
|
21
|
+
- sudo -i -u postgres $PGPREFIX/bin/pg_ctl -D $PGDATA reload
|
22
|
+
- PGHOST_UNIX=`netstat -l --protocol=unix|grep PGSQL|awk '{print $NF}'`
|
23
|
+
- export PGHOST_UNIX="`dirname $PGHOST_UNIX`"
|
24
|
+
- test -n "$PGHOST_UNIX"
|
25
|
+
- export PG_CTL_STOP_CMD="sudo -i -u postgres $PGPREFIX/bin/pg_ctl -D $PGDATA stop
|
26
|
+
-s -m fast"
|
27
|
+
- export PG_CTL_START_CMD="sudo -i -u postgres $PGPREFIX/bin/pg_ctl -D $PGDATA start
|
28
|
+
-l $PGDATA/postgresql.log -s -w"
|
29
|
+
- export PGUSER=postgres
|
30
|
+
- psql -c 'create database test;' -h "$PGHOST_UNIX"
|
31
|
+
script: COVERAGE=1 bundle exec rake test_with_coveralls
|
data/Gemfile
ADDED
data/HISTORY.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
0.3.1
|
2
|
+
|
3
|
+
- support for asynchronous data streaming in single row mode -
|
4
|
+
asynchronous versions of get_result, get_last_result and
|
5
|
+
their deferrable variants
|
6
|
+
- watcher improvements allowing to reset pending commands
|
7
|
+
- minor DRY improvements in code and specs
|
8
|
+
- single_row_mode? helper
|
9
|
+
- spec: Travis CI and Coverage
|
10
|
+
|
1
11
|
0.3.0
|
2
12
|
|
3
13
|
- dedicated asynchronous connection pool
|
data/README.md
CHANGED
@@ -1,16 +1,20 @@
|
|
1
|
-
em-pg-client
|
2
|
-
============
|
1
|
+
#em-pg-client
|
3
2
|
|
4
|
-
|
3
|
+
The Ruby EventMachine driver interface to the PostgreSQL RDBMS. It is based on
|
4
|
+
[ruby-pg](https://bitbucket.org/ged/ruby-pg).
|
5
|
+
|
6
|
+
[![Gem Version][GV img]][Gem Version]
|
7
|
+
[![Dependency Status][DS img]][Dependency Status]
|
8
|
+
[![Coverage Status][CS img]][Coverage Status]
|
9
|
+
[![Build Status][BS img]][Build Status]
|
10
|
+
|
11
|
+
Author: Rafał Michalski (rafal at yeondir dot com)
|
5
12
|
|
6
13
|
* http://github.com/royaltm/ruby-em-pg-client
|
7
14
|
|
8
15
|
Description
|
9
16
|
-----------
|
10
17
|
|
11
|
-
__em-pg-client__ is the Ruby EventMachine driver interface to the
|
12
|
-
PostgreSQL RDBMS. It is based on [ruby-pg](https://bitbucket.org/ged/ruby-pg).
|
13
|
-
|
14
18
|
__em-pg-client__ provides {PG::EM::Client} class which inherits
|
15
19
|
[PG::Connection](http://deveiate.org/code/pg/PG/Connection.html).
|
16
20
|
You can work with {PG::EM::Client} almost the same way you would work
|
@@ -58,7 +62,7 @@ Features
|
|
58
62
|
* Non-blocking / fully asynchronous processing with EventMachine.
|
59
63
|
* Event reactor auto-detecting, asynchronous fiber-synchronized command methods
|
60
64
|
(the same code can be used regardless of the EventMachine reactor state)
|
61
|
-
* Asynchronous EM-style (
|
65
|
+
* Asynchronous EM-style (Deferrable returning) command methods.
|
62
66
|
* Fully asynchronous automatic re-connects on connection failures
|
63
67
|
(e.g.: RDBMS restarts, network failures).
|
64
68
|
* Minimal changes to [PG::Connection](http://deveiate.org/code/pg/PG/Connection.html) API.
|
@@ -66,7 +70,9 @@ Features
|
|
66
70
|
* Dedicated connection pool with dynamic size, supporting asynchronous
|
67
71
|
processing and transactions.
|
68
72
|
* [Sequel Adapter](https://github.com/fl00r/em-pg-sequel) by Peter Yanovich.
|
69
|
-
* Works on windows (requires ruby 2.0) (issue #7).
|
73
|
+
* Works on windows (requires ruby 2.0) ([issue #7][Issue 7]).
|
74
|
+
* __New__ - supports asynchronous query data processing in single row mode
|
75
|
+
([issue #12][Issue 12]).
|
70
76
|
|
71
77
|
Requirements
|
72
78
|
------------
|
@@ -82,14 +88,14 @@ Requirements
|
|
82
88
|
Install
|
83
89
|
-------
|
84
90
|
|
85
|
-
```
|
91
|
+
```sh
|
86
92
|
$ [sudo] gem install em-pg-client
|
87
93
|
```
|
88
94
|
|
89
95
|
#### Gemfile
|
90
96
|
|
91
97
|
```ruby
|
92
|
-
gem "em-pg-client", "~> 0.3.
|
98
|
+
gem "em-pg-client", "~> 0.3.1"
|
93
99
|
```
|
94
100
|
|
95
101
|
#### Github
|
@@ -390,3 +396,18 @@ The greetz go to:
|
|
390
396
|
[em-pg-sequel](https://github.com/fl00r/em-pg-sequel)
|
391
397
|
* Andrew Rudenko [prepor](https://github.com/prepor) for the implicit idea
|
392
398
|
of the re-usable watcher from his [em-pg](https://github.com/prepor/em-pg).
|
399
|
+
|
400
|
+
[![Bitdeli Badge][BB img]][Bitdeli Badge]
|
401
|
+
|
402
|
+
[Gem Version]: https://rubygems.org/gems/em-pg-client
|
403
|
+
[Dependency Status]: https://gemnasium.com/royaltm/ruby-em-pg-client
|
404
|
+
[Coverage Status]: https://coveralls.io/r/royaltm/ruby-em-pg-client
|
405
|
+
[Build Status]: https://travis-ci.org/royaltm/ruby-em-pg-client
|
406
|
+
[Bitdeli Badge]: https://bitdeli.com/free
|
407
|
+
[Issue 7]: https://github.com/royaltm/ruby-em-pg-client/issues/7
|
408
|
+
[Issue 12]: https://github.com/royaltm/ruby-em-pg-client/issues/12
|
409
|
+
[GV img]: https://badge.fury.io/rb/em-pg-client.png
|
410
|
+
[DS img]: https://gemnasium.com/royaltm/ruby-em-pg-client.png
|
411
|
+
[CS img]: https://coveralls.io/repos/royaltm/ruby-em-pg-client/badge.png
|
412
|
+
[BS img]: https://travis-ci.org/royaltm/ruby-em-pg-client.png
|
413
|
+
[BB img]: https://d2weczhvl823v0.cloudfront.net/royaltm/ruby-em-pg-client/trend.png
|
data/Rakefile
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
require 'coveralls/rake/task'
|
2
|
+
Coveralls::RakeTask.new
|
3
|
+
task :test_with_coveralls => ['test:all', 'coveralls:push']
|
4
|
+
|
1
5
|
$:.unshift "lib"
|
2
6
|
|
3
7
|
task :default => [:test]
|
@@ -13,43 +17,76 @@ task :test => :'test:safe'
|
|
13
17
|
|
14
18
|
namespace :test do
|
15
19
|
env_common = {'PGDATABASE' => 'test'}
|
16
|
-
|
17
|
-
|
20
|
+
env_unix = env_common.merge('PGHOST' => ENV['PGHOST_UNIX'] || '/tmp')
|
21
|
+
env_inet = env_common.merge('PGHOST' => ENV['PGHOST_INET'] || 'localhost')
|
18
22
|
|
19
23
|
task :warn do
|
20
|
-
puts "WARNING: The
|
24
|
+
puts "WARNING: The tests needs to be run with an available local PostgreSQL server"
|
21
25
|
end
|
22
26
|
|
23
27
|
desc "Run specs only"
|
24
|
-
task :spec
|
25
|
-
|
26
|
-
|
27
|
-
sh
|
28
|
-
|
29
|
-
|
28
|
+
task :spec => [:spec_defer, :spec_pool, :spec_client]
|
29
|
+
|
30
|
+
task :spec_client do
|
31
|
+
sh({'COVNAME'=>'spec:client'}, "rspec spec/pg_em_client_*.rb")
|
32
|
+
end
|
33
|
+
|
34
|
+
task :spec_pool do
|
35
|
+
sh({'COVNAME'=>'spec:pool'}, "rspec spec/pg_em_connection_pool.rb")
|
36
|
+
end
|
37
|
+
|
38
|
+
task :spec_defer do
|
39
|
+
sh({'COVNAME'=>'spec:deferrable'}, "rspec spec/pg_em_featured_deferrable.rb")
|
30
40
|
end
|
31
41
|
|
32
42
|
desc "Run safe tests only"
|
33
|
-
task :safe
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
43
|
+
task :safe => [:warn, :spec, :async, :fiber]
|
44
|
+
task :async => [:async_inet, :async_unix]
|
45
|
+
task :fiber => [:fiber_inet, :fiber_unix]
|
46
|
+
|
47
|
+
task :async_inet do
|
48
|
+
sh env_inet.merge('COVNAME'=>'async:inet'), "rspec spec/em_client.rb"
|
49
|
+
end
|
50
|
+
|
51
|
+
task :async_unix do
|
52
|
+
sh env_unix.merge('COVNAME'=>'async:unix'), "rspec spec/em_client.rb" unless windows_os?
|
53
|
+
end
|
54
|
+
|
55
|
+
task :fiber_inet do
|
56
|
+
sh env_inet.merge('COVNAME'=>'fiber:inet'), "rspec spec/em_synchrony_client.rb"
|
57
|
+
end
|
58
|
+
|
59
|
+
task :fiber_unix do
|
60
|
+
sh env_unix.merge('COVNAME'=>'fiber:unix'), "rspec spec/em_synchrony_client.rb" unless windows_os?
|
61
|
+
end
|
62
|
+
|
63
|
+
task :pgdata_check do
|
64
|
+
unless ENV['PGDATA'] || (ENV['PG_CTL_STOP_CMD'] && ENV['PG_CTL_START_CMD'])
|
65
|
+
raise "Set PGDATA environment variable before running the autoreconnect tests."
|
40
66
|
end
|
41
67
|
end
|
42
68
|
|
43
69
|
desc "Run unsafe tests only"
|
44
|
-
task :unsafe => :warn
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
70
|
+
task :unsafe => [:warn, :pgdata_check,
|
71
|
+
:async_autoreconnect_inet,
|
72
|
+
:async_autoreconnect_unix,
|
73
|
+
:fiber_autoreconnect_inet,
|
74
|
+
:fiber_autoreconnect_unix]
|
75
|
+
|
76
|
+
task :async_autoreconnect_inet do
|
77
|
+
sh env_inet.merge('COVNAME'=>'async:autoreconnect:inet'), "rspec spec/em_client_autoreconnect.rb"
|
78
|
+
end
|
79
|
+
|
80
|
+
task :async_autoreconnect_unix do
|
81
|
+
sh env_unix.merge('COVNAME'=>'async:autoreconnect:unix'), "rspec spec/em_client_autoreconnect.rb"
|
82
|
+
end
|
83
|
+
|
84
|
+
task :fiber_autoreconnect_inet do
|
85
|
+
sh env_inet.merge('COVNAME'=>'fiber:autoreconnect:inet'), "rspec spec/em_synchrony_client_autoreconnect.rb"
|
86
|
+
end
|
87
|
+
|
88
|
+
task :fiber_autoreconnect_unix do
|
89
|
+
sh env_unix.merge('COVNAME'=>'fiber:autoreconnect:unix'), "rspec spec/em_synchrony_client_autoreconnect.rb"
|
53
90
|
end
|
54
91
|
|
55
92
|
desc "Run safe and unsafe tests"
|
data/benchmarks/em_pg.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
$:.unshift('./lib')
|
2
2
|
require 'eventmachine'
|
3
3
|
require 'em-synchrony'
|
4
|
-
require 'em
|
4
|
+
require 'pg/em/connection_pool'
|
5
5
|
require "em-synchrony/fiber_iterator"
|
6
6
|
require 'pp'
|
7
7
|
require 'benchmark'
|
8
8
|
|
9
|
-
$dbname = 'alpha'
|
10
|
-
|
11
9
|
def benchmark(repeat=100)
|
12
10
|
Benchmark.bm(20) do |b|
|
13
11
|
b.report('single:') { single(repeat) }
|
@@ -28,31 +26,36 @@ end
|
|
28
26
|
|
29
27
|
def patch_remove_blocking
|
30
28
|
PG::EM::Client::Watcher.module_eval <<-EOE
|
31
|
-
alias_method :
|
32
|
-
|
29
|
+
alias_method :fetch_results, :original_fetch_results
|
30
|
+
alias_method :notify_readable, :fetch_results
|
31
|
+
undef :original_fetch_results
|
33
32
|
EOE
|
34
33
|
end
|
35
34
|
|
36
35
|
def patch_blocking
|
36
|
+
PG::Connection.class_eval <<-EOE
|
37
|
+
alias_method :blocking_get_last_result, :get_last_result
|
38
|
+
EOE
|
37
39
|
PG::EM::Client::Watcher.module_eval <<-EOE
|
38
|
-
alias_method :
|
39
|
-
def
|
40
|
-
|
40
|
+
alias_method :original_fetch_results, :fetch_results
|
41
|
+
def fetch_results
|
42
|
+
self.notify_readable = false
|
41
43
|
begin
|
42
|
-
result = @client.
|
44
|
+
result = @client.blocking_get_last_result
|
43
45
|
rescue Exception => e
|
44
46
|
@deferrable.fail(e)
|
45
47
|
else
|
46
48
|
@deferrable.succeed(result)
|
47
49
|
end
|
48
50
|
end
|
51
|
+
alias_method :notify_readable, :fetch_results
|
49
52
|
EOE
|
50
53
|
end
|
51
54
|
|
52
55
|
# retrieve resources using single select query
|
53
56
|
def single(repeat=1)
|
54
57
|
rowcount = 0
|
55
|
-
p = PGconn.new
|
58
|
+
p = PGconn.new
|
56
59
|
p.query('select count(*) from resources') do |result|
|
57
60
|
rowcount = result.getvalue(0,0).to_i
|
58
61
|
end
|
@@ -69,7 +72,7 @@ def parallel(repeat=1, chunk_size=2000, concurrency=10)
|
|
69
72
|
resources = []
|
70
73
|
rowcount = 0
|
71
74
|
EM.synchrony do
|
72
|
-
p = EM::
|
75
|
+
p = PG::EM::ConnectionPool.new size: concurrency
|
73
76
|
p.query('select count(*) from resources') do |result|
|
74
77
|
rowcount = result.getvalue(0,0).to_i
|
75
78
|
end
|
@@ -78,7 +81,7 @@ def parallel(repeat=1, chunk_size=2000, concurrency=10)
|
|
78
81
|
EM::Synchrony::FiberIterator.new(offsets, concurrency).each do |offset|
|
79
82
|
p.query('select * from resources order by cdate limit $1 offset $2', [chunk_size, offset]) do |result|
|
80
83
|
resources[offset, chunk_size] = result.values
|
81
|
-
end
|
84
|
+
end
|
82
85
|
end
|
83
86
|
end
|
84
87
|
EM.stop
|
data/em-pg-client.gemspec
CHANGED
@@ -22,6 +22,8 @@ Gem::Specification.new do |s|
|
|
22
22
|
s.requirements << "PostgreSQL server"
|
23
23
|
s.add_runtime_dependency "pg", ">= 0.17.0"
|
24
24
|
s.add_runtime_dependency "eventmachine", "~> 1.0.0"
|
25
|
-
s.add_development_dependency "rspec", "~> 2.
|
25
|
+
s.add_development_dependency "rspec", "~> 2.14"
|
26
26
|
s.add_development_dependency "em-synchrony", "~> 1.0.0"
|
27
|
+
s.add_development_dependency "coveralls", ">= 0.7.0"
|
28
|
+
s.add_development_dependency "simplecov", ">= 0.8.2"
|
27
29
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Demonstation of fully asynchronous query data streaming.
|
2
|
+
#
|
3
|
+
# This is a low-level method which has its:
|
4
|
+
#
|
5
|
+
# upside: it's the same way you would work with PG::Connection
|
6
|
+
# downside: it's a little verbose and doesn't support automatic re-connects
|
7
|
+
gem 'em-pg-client', '>= 0.3.1'
|
8
|
+
require 'pg/em/connection_pool'
|
9
|
+
require 'em-synchrony'
|
10
|
+
require 'em-synchrony/fiber_iterator'
|
11
|
+
|
12
|
+
TABLE_NAME = 'resources'
|
13
|
+
|
14
|
+
unless PG::EM::Client.single_row_mode?
|
15
|
+
raise 'compile pg against pqlib >= 9.2 to support single row mode'
|
16
|
+
end
|
17
|
+
|
18
|
+
def tick_sleep
|
19
|
+
f = Fiber.current
|
20
|
+
EM.next_tick { f.resume }
|
21
|
+
Fiber.yield
|
22
|
+
end
|
23
|
+
|
24
|
+
EM.synchrony do
|
25
|
+
EM.add_periodic_timer(0.01) { print ' ' }
|
26
|
+
|
27
|
+
db = PG::EM::ConnectionPool.new size: 3
|
28
|
+
|
29
|
+
10.times do
|
30
|
+
|
31
|
+
EM::Synchrony::FiberIterator.new(%w[@ * #], 3).each do |mark|
|
32
|
+
|
33
|
+
db.hold do |pg|
|
34
|
+
pg.send_query("select * from #{TABLE_NAME}")
|
35
|
+
pg.set_single_row_mode
|
36
|
+
rows = 0
|
37
|
+
while result = pg.get_result
|
38
|
+
begin
|
39
|
+
result.check
|
40
|
+
result.each do |tuple|
|
41
|
+
rows += 1
|
42
|
+
# process tuple
|
43
|
+
print mark
|
44
|
+
if (rows % 10).zero?
|
45
|
+
# let reactor do some work if data is coming too fast
|
46
|
+
tick_sleep
|
47
|
+
end
|
48
|
+
# break stream cleanly
|
49
|
+
pg.reset if rows > 1000
|
50
|
+
end
|
51
|
+
rescue PG::Error => e
|
52
|
+
# cleanup connection
|
53
|
+
pg.get_last_result
|
54
|
+
raise e
|
55
|
+
ensure
|
56
|
+
result.clear
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
puts
|
64
|
+
puts '='*80
|
65
|
+
end
|
66
|
+
EM.stop
|
67
|
+
end
|
data/lib/pg/em-version.rb
CHANGED
data/lib/pg/em.rb
CHANGED
@@ -175,6 +175,21 @@ module PG
|
|
175
175
|
# Used internally for marking connection as aborted on query timeout.
|
176
176
|
attr_accessor :async_command_aborted
|
177
177
|
|
178
|
+
# Returns +true+ if +pg+ supports single row mode or +false+ otherwise.
|
179
|
+
# Single row mode is available since +libpq+ 9.2.
|
180
|
+
# @return [Boolean]
|
181
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-set_single_row_mode PG::Connection#set_single_row_mode
|
182
|
+
def self.single_row_mode?
|
183
|
+
method_defined? :set_single_row_mode
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns +true+ if +pg+ supports single row mode or +false+ otherwise.
|
187
|
+
# @return [Boolean]
|
188
|
+
# @see single_row_mode?
|
189
|
+
def single_row_mode?
|
190
|
+
self.class.single_row_mode?
|
191
|
+
end
|
192
|
+
|
178
193
|
# environment variable name for connect_timeout fallback value
|
179
194
|
@@connect_timeout_envvar = conndefaults.find{|d| d[:keyword] == "connect_timeout" }[:envvar]
|
180
195
|
|
@@ -267,9 +282,10 @@ module PG
|
|
267
282
|
# after successfull reset.
|
268
283
|
# If the block is provided it's bound to +callback+ and +errback+ hooks
|
269
284
|
# of the returned deferrable.
|
285
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-reset PG::Connection#reset
|
270
286
|
def reset_defer(&blk)
|
271
287
|
@async_command_aborted = false
|
272
|
-
df =
|
288
|
+
df = FeaturedDeferrable.new(&blk)
|
273
289
|
# there can be only one watch handler over the socket
|
274
290
|
# apparently eventmachine has hard time dealing with more than one
|
275
291
|
# for blocking reset this is not needed
|
@@ -302,10 +318,9 @@ module PG
|
|
302
318
|
# Otherwise performs a thread-blocking call to the parent method.
|
303
319
|
#
|
304
320
|
# @raise [PG::Error]
|
305
|
-
#
|
321
|
+
# @see #reset_defer
|
306
322
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-reset PG::Connection#reset
|
307
323
|
def reset
|
308
|
-
@async_command_aborted = false
|
309
324
|
if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
|
310
325
|
reset_defer {|r| f.resume(r) }
|
311
326
|
|
@@ -313,6 +328,11 @@ module PG
|
|
313
328
|
raise conn if conn.is_a?(::Exception)
|
314
329
|
conn
|
315
330
|
else
|
331
|
+
@async_command_aborted = false
|
332
|
+
if @watcher
|
333
|
+
@watcher.detach if @watcher.watching?
|
334
|
+
@watcher = nil
|
335
|
+
end
|
316
336
|
super
|
317
337
|
end
|
318
338
|
end
|
@@ -394,7 +414,7 @@ module PG
|
|
394
414
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-status PG::Connection#status
|
395
415
|
def status
|
396
416
|
if @async_command_aborted
|
397
|
-
|
417
|
+
CONNECTION_BAD
|
398
418
|
else
|
399
419
|
super
|
400
420
|
end
|
@@ -404,10 +424,10 @@ module PG
|
|
404
424
|
# Perform auto re-connect. Used internally.
|
405
425
|
def async_autoreconnect!(deferrable, error, &send_proc)
|
406
426
|
# reconnect only if connection is bad and flag is set
|
407
|
-
if self.status
|
427
|
+
if self.status == CONNECTION_BAD && async_autoreconnect
|
408
428
|
# check if transaction was active
|
409
429
|
was_in_transaction = case @last_transaction_status
|
410
|
-
when
|
430
|
+
when PQTRANS_IDLE, PQTRANS_UNKNOWN
|
411
431
|
false
|
412
432
|
else
|
413
433
|
true
|
@@ -431,7 +451,7 @@ module PG
|
|
431
451
|
if returned_df.respond_to?(:callback) && returned_df.respond_to?(:errback)
|
432
452
|
# the handler returned a deferrable
|
433
453
|
returned_df.callback do
|
434
|
-
if was_in_transaction
|
454
|
+
if was_in_transaction || !send_proc
|
435
455
|
# there was a transaction in progress, fail anyway
|
436
456
|
deferrable.fail error
|
437
457
|
else
|
@@ -444,7 +464,7 @@ module PG
|
|
444
464
|
elsif returned_df.is_a?(Exception)
|
445
465
|
# tha handler returned an exception object, so fail with it
|
446
466
|
deferrable.fail returned_df
|
447
|
-
elsif returned_df == false || (was_in_transaction && returned_df != true)
|
467
|
+
elsif returned_df == false || !send_proc || (was_in_transaction && returned_df != true)
|
448
468
|
# tha handler returned false or raised an exception
|
449
469
|
# or there was an active transaction and handler didn't return true
|
450
470
|
deferrable.fail error
|
@@ -453,7 +473,7 @@ module PG
|
|
453
473
|
deferrable.protect(&send_proc)
|
454
474
|
end
|
455
475
|
end.resume
|
456
|
-
elsif was_in_transaction
|
476
|
+
elsif was_in_transaction || !send_proc
|
457
477
|
# there was a transaction in progress, fail anyway
|
458
478
|
deferrable.fail error
|
459
479
|
else
|
@@ -469,11 +489,14 @@ module PG
|
|
469
489
|
end
|
470
490
|
|
471
491
|
# @!macro deferrable_api
|
472
|
-
# @yieldparam result [PG::Result|Error] command result on success or a PG::Error instance on error.
|
473
492
|
# @return [FeaturedDeferrable]
|
474
|
-
# Use the returned
|
475
|
-
# If the block is provided it's bound to both the
|
476
|
-
# of the returned deferrable.
|
493
|
+
# Use the returned Deferrable's +callback+ and +errback+ methods to
|
494
|
+
# get the result. If the block is provided it's bound to both the
|
495
|
+
# +callback+ and +errback+ hooks of the returned deferrable.
|
496
|
+
|
497
|
+
# @!macro deferrable_query_api
|
498
|
+
# @yieldparam result [PG::Result|Error] command result on success or a PG::Error instance on error.
|
499
|
+
# @macro deferrable_api
|
477
500
|
|
478
501
|
# @!group Deferrable command methods
|
479
502
|
|
@@ -481,41 +504,41 @@ module PG
|
|
481
504
|
# Sends SQL query request specified by +sql+ to PostgreSQL for asynchronous processing,
|
482
505
|
# and immediately returns with +deferrable+.
|
483
506
|
#
|
484
|
-
# @macro
|
507
|
+
# @macro deferrable_query_api
|
485
508
|
#
|
486
509
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec PG::Connection#exec
|
487
510
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_params PG::Connection#exec_params
|
488
511
|
#
|
489
512
|
# @!method prepare_defer(stmt_name, sql, param_types=nil, &blk)
|
490
513
|
# Prepares statement +sql+ with name +stmt_name+ to be executed later asynchronously,
|
491
|
-
# and immediately returns with
|
514
|
+
# and immediately returns with a Deferrable.
|
492
515
|
#
|
493
|
-
# @macro
|
516
|
+
# @macro deferrable_query_api
|
494
517
|
#
|
495
518
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-prepare PG::Connection#prepare
|
496
519
|
#
|
497
520
|
# @!method exec_prepared_defer(statement_name, params=nil, result_format=nil, &blk)
|
498
521
|
# Execute prepared named statement specified by +statement_name+ asynchronously,
|
499
|
-
# and immediately returns with
|
522
|
+
# and immediately returns with a Deferrable.
|
500
523
|
#
|
501
|
-
# @macro
|
524
|
+
# @macro deferrable_query_api
|
502
525
|
#
|
503
526
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query_prepared PG::Connection#send_query_prepared
|
504
527
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared PG::Connection#send_exec_prepared
|
505
528
|
#
|
506
529
|
# @!method describe_prepared_defer(statement_name, &blk)
|
507
530
|
# Asynchronously sends command to retrieve information about the prepared statement +statement_name+,
|
508
|
-
# and immediately returns with
|
531
|
+
# and immediately returns with a Deferrable.
|
509
532
|
#
|
510
|
-
# @macro
|
533
|
+
# @macro deferrable_query_api
|
511
534
|
#
|
512
535
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_prepared PG::Connection#describe_prepared
|
513
536
|
#
|
514
537
|
# @!method describe_portal_defer(portal_name, &blk)
|
515
538
|
# Asynchronously sends command to retrieve information about the portal +portal_name+,
|
516
|
-
# and immediately returns with
|
539
|
+
# and immediately returns with a Deferrable.
|
517
540
|
#
|
518
|
-
# @macro
|
541
|
+
# @macro deferrable_query_api
|
519
542
|
#
|
520
543
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_portal PG::Connection#describe_portal
|
521
544
|
#
|
@@ -529,25 +552,16 @@ module PG
|
|
529
552
|
|
530
553
|
class_eval <<-EOD, __FILE__, __LINE__
|
531
554
|
def #{defer_name}(*args, &blk)
|
532
|
-
df =
|
555
|
+
df = FeaturedDeferrable.new(&blk)
|
533
556
|
send_proc = proc do
|
534
557
|
#{send_name}(*args)
|
535
|
-
|
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
|
558
|
+
setup_emio_watcher.watch_results(df, send_proc)
|
541
559
|
end
|
542
560
|
begin
|
543
|
-
|
544
|
-
error = ConnectionBad.new("previous query expired, need connection reset")
|
545
|
-
error.instance_variable_set(:@connection, self)
|
546
|
-
raise error
|
547
|
-
end
|
561
|
+
check_async_command_aborted!
|
548
562
|
@last_transaction_status = transaction_status
|
549
563
|
send_proc.call
|
550
|
-
rescue
|
564
|
+
rescue Error => e
|
551
565
|
::EM.next_tick { async_autoreconnect!(df, e, &send_proc) }
|
552
566
|
rescue Exception => e
|
553
567
|
::EM.next_tick { df.fail(e) }
|
@@ -565,18 +579,106 @@ module PG
|
|
565
579
|
|
566
580
|
# @!endgroup
|
567
581
|
|
568
|
-
#
|
569
|
-
#
|
570
|
-
#
|
571
|
-
#
|
582
|
+
# Asynchronously retrieves the next result from a call to
|
583
|
+
# #send_query (or another asynchronous command) and immediately
|
584
|
+
# returns with a Deferrable.
|
585
|
+
# It then receives the result object on :succeed, or +nil+
|
586
|
+
# if no results are available.
|
587
|
+
#
|
588
|
+
# @macro deferrable_api
|
589
|
+
# @yieldparam result [PG::Result|Error|nil] command result on success or a PG::Error instance on error
|
590
|
+
# or +nil+ if no results are available.
|
591
|
+
#
|
592
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query PG::Connection#send_query
|
593
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_result PG::Connection#get_result
|
594
|
+
#
|
595
|
+
def get_result_defer(&blk)
|
596
|
+
begin
|
597
|
+
df = FeaturedDeferrable.new(&blk)
|
598
|
+
check_async_command_aborted!
|
599
|
+
setup_emio_watcher.watch_results(df, nil, true)
|
600
|
+
rescue Error => e
|
601
|
+
::EM.next_tick { async_autoreconnect!(df, e) }
|
602
|
+
rescue Exception => e
|
603
|
+
::EM.next_tick { df.fail(e) }
|
604
|
+
end
|
605
|
+
df
|
606
|
+
end
|
607
|
+
|
608
|
+
alias_method :blocking_get_result, :get_result
|
609
|
+
|
610
|
+
# Asynchronously retrieves all available results on the current
|
611
|
+
# connection (from previously issued asynchronous commands like
|
612
|
+
# +send_query()+) and immediately returns with a Deferrable.
|
613
|
+
# It then receives the last non-NULL result on :succeed, or +nil+
|
614
|
+
# if no results are available.
|
615
|
+
#
|
616
|
+
# @macro deferrable_api
|
617
|
+
# @yieldparam result [PG::Result|Error|nil] command result on success or a PG::Error instance on error
|
618
|
+
# or +nil+ if no results are available.
|
619
|
+
#
|
620
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query PG::Connection#send_query
|
621
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_last_result PG::Connection#get_last_result
|
622
|
+
#
|
623
|
+
def get_last_result_defer(&blk)
|
624
|
+
begin
|
625
|
+
df = FeaturedDeferrable.new(&blk)
|
626
|
+
check_async_command_aborted!
|
627
|
+
setup_emio_watcher.watch_results(df)
|
628
|
+
rescue Error => e
|
629
|
+
::EM.next_tick { async_autoreconnect!(df, e) }
|
630
|
+
rescue Exception => e
|
631
|
+
::EM.next_tick { df.fail(e) }
|
632
|
+
end
|
633
|
+
df
|
634
|
+
end
|
635
|
+
|
636
|
+
def raise_error(klass=Error, message=error_message)
|
637
|
+
error = klass.new(message)
|
638
|
+
error.instance_variable_set(:@connection, self)
|
639
|
+
raise error
|
640
|
+
end
|
641
|
+
|
642
|
+
private
|
643
|
+
|
644
|
+
def check_async_command_aborted!
|
645
|
+
if @async_command_aborted
|
646
|
+
raise_error ConnectionBad, "previous query expired, need connection reset"
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
def setup_emio_watcher
|
651
|
+
case status
|
652
|
+
when CONNECTION_BAD
|
653
|
+
raise_error ConnectionBad
|
654
|
+
when CONNECTION_OK
|
655
|
+
if @watcher && @watcher.watching?
|
656
|
+
@watcher
|
657
|
+
else
|
658
|
+
@watcher = ::EM.watch(self.socket_io, Watcher, self)
|
659
|
+
end
|
660
|
+
else
|
661
|
+
raise_error ConnectionBad, "connection reset pending"
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
public
|
666
|
+
|
667
|
+
# @!macro auto_synchrony_api_intro
|
668
|
+
# If EventMachine reactor is running and the current fiber isn't the
|
669
|
+
# root fiber this method performs command asynchronously yielding
|
670
|
+
# current fiber. Other fibers can process while waiting for the server
|
572
671
|
# to complete the request.
|
573
672
|
#
|
574
|
-
# Otherwise performs a blocking call to parent method.
|
673
|
+
# Otherwise performs a blocking call to a parent method.
|
575
674
|
#
|
576
675
|
# @yieldparam result [PG::Result] command result on success
|
676
|
+
# @raise [PG::Error]
|
677
|
+
|
678
|
+
# @!macro auto_synchrony_api
|
679
|
+
# @macro auto_synchrony_api_intro
|
577
680
|
# @return [PG::Result] if block wasn't given
|
578
681
|
# @return [Object] result of the given block
|
579
|
-
# @raise [PG::Error]
|
580
682
|
|
581
683
|
# @!group Auto-sensing thread or fiber blocking command methods
|
582
684
|
|
@@ -605,7 +707,7 @@ module PG
|
|
605
707
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-prepare PG::Connection#prepare
|
606
708
|
#
|
607
709
|
# @!method exec_prepared(statement_name, params=nil, result_format=nil, &blk)
|
608
|
-
#
|
710
|
+
# Executes prepared named statement specified by +statement_name+.
|
609
711
|
#
|
610
712
|
# @macro auto_synchrony_api
|
611
713
|
#
|
@@ -613,7 +715,7 @@ module PG
|
|
613
715
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared PG::Connection#exec_prepared
|
614
716
|
#
|
615
717
|
# @!method describe_prepared(statement_name, &blk)
|
616
|
-
#
|
718
|
+
# Retrieves information about the prepared statement +statement_name+,
|
617
719
|
#
|
618
720
|
# @macro auto_synchrony_api
|
619
721
|
#
|
@@ -621,13 +723,35 @@ module PG
|
|
621
723
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_prepared PG::Connection#describe_prepared
|
622
724
|
#
|
623
725
|
# @!method describe_portal(portal_name, &blk)
|
624
|
-
#
|
726
|
+
# Retrieves information about the portal +portal_name+,
|
625
727
|
#
|
626
728
|
# @macro auto_synchrony_api
|
627
729
|
#
|
628
730
|
# @see PG::EM::Client#describe_portal_defer
|
629
731
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_portal PG::Connection#describe_portal
|
630
732
|
#
|
733
|
+
# @!method get_result(&blk)
|
734
|
+
# Retrieves the next result from a call to #send_query (or another
|
735
|
+
# asynchronous command). If no more results are available returns
|
736
|
+
# +nil+ and the block (if given) is never called.
|
737
|
+
#
|
738
|
+
# @macro auto_synchrony_api
|
739
|
+
# @return [nil] if no more results
|
740
|
+
#
|
741
|
+
# @see #get_result_defer
|
742
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_result PG::Connection#get_result
|
743
|
+
#
|
744
|
+
# @!method get_last_result
|
745
|
+
# Retrieves all available results on the current connection
|
746
|
+
# (from previously issued asynchronous commands like +send_query()+)
|
747
|
+
# and returns the last non-NULL result, or +nil+ if no results are
|
748
|
+
# available.
|
749
|
+
#
|
750
|
+
# @macro auto_synchrony_api
|
751
|
+
# @return [nil] if no more results
|
752
|
+
#
|
753
|
+
# @see #get_last_result_defer
|
754
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_last_result PG::Connection#get_last_result
|
631
755
|
%w(
|
632
756
|
exec exec_defer
|
633
757
|
exec_params exec_defer
|
@@ -635,18 +759,25 @@ module PG
|
|
635
759
|
prepare prepare_defer
|
636
760
|
describe_prepared describe_prepared_defer
|
637
761
|
describe_portal describe_portal_defer
|
762
|
+
get_result get_result_defer
|
763
|
+
get_last_result get_last_result_defer
|
638
764
|
).each_slice(2) do |name, defer_name|
|
639
765
|
|
640
766
|
class_eval <<-EOD, __FILE__, __LINE__
|
641
767
|
def #{name}(*args, &blk)
|
642
768
|
if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
|
769
|
+
result = fiber = nil
|
643
770
|
#{defer_name}(*args) do |res|
|
644
|
-
f
|
771
|
+
f = nil
|
772
|
+
if fiber
|
773
|
+
fiber.resume(res)
|
774
|
+
else
|
775
|
+
result = res
|
776
|
+
end
|
645
777
|
end
|
646
|
-
|
647
|
-
result = Fiber.yield
|
778
|
+
result = Fiber.yield if (fiber = f)
|
648
779
|
raise result if result.is_a?(::Exception)
|
649
|
-
if block_given?
|
780
|
+
if block_given? && result
|
650
781
|
begin
|
651
782
|
yield result
|
652
783
|
ensure
|
@@ -668,9 +799,11 @@ module PG
|
|
668
799
|
|
669
800
|
# @!endgroup
|
670
801
|
|
802
|
+
|
671
803
|
TRAN_BEGIN_QUERY = 'BEGIN'
|
672
804
|
TRAN_ROLLBACK_QUERY = 'ROLLBACK'
|
673
805
|
TRAN_COMMIT_QUERY = 'COMMIT'
|
806
|
+
|
674
807
|
# Executes a BEGIN at the start of the block and a COMMIT at the end
|
675
808
|
# of the block or ROLLBACK if any exception occurs.
|
676
809
|
#
|
@@ -727,12 +860,12 @@ module PG
|
|
727
860
|
tcount = @client_tran_count.to_i
|
728
861
|
|
729
862
|
case transaction_status
|
730
|
-
when
|
863
|
+
when PQTRANS_IDLE
|
731
864
|
# there is no transaction yet, so let's begin
|
732
865
|
exec(TRAN_BEGIN_QUERY)
|
733
866
|
# reset transaction count in case user code rolled it back before
|
734
867
|
tcount = 0 if tcount != 0
|
735
|
-
when
|
868
|
+
when PQTRANS_INTRANS
|
736
869
|
# transaction in progress, leave it be
|
737
870
|
else
|
738
871
|
# transaction failed, is in unknown state or command is active
|
@@ -748,7 +881,7 @@ module PG
|
|
748
881
|
rescue
|
749
882
|
# error was raised
|
750
883
|
case transaction_status
|
751
|
-
when
|
884
|
+
when PQTRANS_INTRANS, PQTRANS_INERROR
|
752
885
|
# do not rollback if transaction was rolled back before
|
753
886
|
# or is in unknown state, which means connection reset is needed
|
754
887
|
# and rollback only from the outermost transaction block
|
@@ -759,15 +892,15 @@ module PG
|
|
759
892
|
else
|
760
893
|
# we are good (but not out of woods yet)
|
761
894
|
case transaction_status
|
762
|
-
when
|
895
|
+
when PQTRANS_INTRANS
|
763
896
|
# commit only from the outermost transaction block
|
764
897
|
exec(TRAN_COMMIT_QUERY) if tcount.zero?
|
765
|
-
when
|
898
|
+
when PQTRANS_INERROR
|
766
899
|
# no ruby error was raised (or an error was rescued in code block)
|
767
900
|
# but there was an sql error anyway
|
768
901
|
# so rollback after the outermost block
|
769
902
|
exec(TRAN_ROLLBACK_QUERY) if tcount.zero?
|
770
|
-
when
|
903
|
+
when PQTRANS_IDLE
|
771
904
|
# the code block has terminated the transaction on its own
|
772
905
|
# so just reset the counter
|
773
906
|
tcount = 0
|