em-pg-client 0.2.0.pre.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/BENCHMARKS.rdoc CHANGED
@@ -6,7 +6,7 @@ The goal of the test is simply to retrieve (~80000) rows from table with a lot o
6
6
  The parallel method uses synchrony for simplicity.
7
7
 
8
8
  * +single+ is (eventmachine-less) job for retrieving a whole data table in
9
- one query simple query "select * from resources"
9
+ one simple query "select * from resources"
10
10
  * +parallel+ chunk_row_count / concurrency] uses em-pg-client for retrieving
11
11
  result in chunks by +chunk_row_count+ rows and using +concurrency+ parallel
12
12
  connections
data/HISTORY.rdoc CHANGED
@@ -1,3 +1,13 @@
1
+ 0.2.0
2
+ - disabled async_autoreconnect by default unless on_autoreconnect is set
3
+ - async_connect sets #internal_encoding to Encoding.default_internal
4
+ - fix: finish connection on async connect_timeout
5
+ - nice errors generated on missing dependencies
6
+ - blocking #reset() should clear async_command_aborted flag
7
+ - less calls to #is_busy in Watcher#notify_readable
8
+ - #async_describe_portal + specs
9
+ - #async_describe_prepared + specs
10
+
1
11
  0.2.0.pre.3
2
12
  - #status() returns CONNECTION_BAD for connections with expired query
3
13
  - spec: query timeout expiration
data/README.rdoc CHANGED
@@ -6,102 +6,111 @@ Author:: Rafał Michalski (mailto:rafal@yeondir.com)
6
6
 
7
7
  == DESCRIPTION
8
8
 
9
- *em-pg-client* is a PostgreSQL EventMachine client wrapper for Ruby
10
- based on ruby-pg[https://bitbucket.org/ged/ruby-pg]
9
+ *em-pg-client* is the Ruby and EventMachine driver interface to the PostgreSQL
10
+ RDBMS. It is based on ruby-pg[https://bitbucket.org/ged/ruby-pg].
11
11
 
12
12
  == FEATURES
13
13
 
14
- * minimal changes to PG::Connection API
15
- * fully async auto reconnects on socket connection loss (like server restarts)
16
- * true non-blocking asynchronous processing
17
- * EM-Synchrony[https://github.com/igrigorik/em-synchrony] support
14
+ * Non-blocking / asynchronous processing with EventMachine,
15
+ * fully async auto re-connects on connection losses (e.g.: RDBMS restarts),
16
+ * minimal changes to PG::Connection[http://deveiate.org/code/pg/PG/Connection.html] API,
17
+ * configurable timeouts (connect or execute) of asynchronous processing,
18
+ * additional Fiber-aware version supporting EM-Synchrony[https://github.com/igrigorik/em-synchrony].
18
19
 
19
20
  == BUGS/LIMITATIONS
20
21
 
22
+ * no async support for: COPY commands (+get_copy_data+, +put_copy_data+),
23
+ +wait_for_notify+ and +transaction+
21
24
  * actually no ActiveRecord nor Sequel support (you are welcome to contribute).
25
+ * doesn't work on Windows (issue #7)
22
26
 
23
- == API Changes
27
+ == API Changes between versions
24
28
 
29
+ === 0.1.x -> 0.2.x
25
30
  * +on_reconnect+ renamed to more accurate +on_autoreconnect+
26
- (well, it's not used by +#reset+ call)
31
+ (well, it's not used by PG::EM::Client#reset call).
32
+ * +async_autoreconnect+ is +false+ by default if +on_autoreconnect+
33
+ is *not* specified as initialization option.
27
34
 
28
35
  == TODO:
29
36
 
37
+ * implement EM adapted version of +get_copy_data+, +put_copy_data+,
38
+ +wait_for_notify+ and +transaction+
39
+ * add some fd/socket hackery to get it working on Windows (issue #7)
30
40
  * em-synchrony ORM (ActiveRecord, Sequel and maybe Datamapper) support
31
41
  as separate projects
42
+ * present more benchmarks
32
43
 
33
44
  == REQUIREMENTS
34
45
 
35
- * ruby >= 1.9
36
- * https://bitbucket.org/ged/ruby-pg (>= 0.13)
37
- * http://rubyeventmachine.com
46
+ * ruby >= 1.9 (tested: 1.9.3-p194, 1.9.2-p320, 1.9.2-p280, 1.9.1-p378)
47
+ * https://bitbucket.org/ged/ruby-pg >= 0.13
48
+ * PostgreSQL[http://www.postgresql.org/ftp/source/] RDBMS >= 8.3
49
+ * http://rubyeventmachine.com >= 0.12.10
38
50
  * (optional) EM-Synchrony[https://github.com/igrigorik/em-synchrony]
39
51
 
40
52
  == INSTALL
41
53
 
42
- === Legacy
43
-
44
54
  $ [sudo] gem install em-pg-client
45
-
46
- ==== Gemfile
47
-
48
- # eventmachine
49
- gem "em-pg-client", "~> 0.1.1", :require => 'pg/em'
50
- # em-synchrony
51
- gem "em-pg-client", "~> 0.1.1", :require => ['pg/em', 'em-synchrony/pg']
52
-
53
- === Latest branch (fully-async)
54
-
55
- $ [sudo] gem install em-pg-client --pre
56
55
 
57
56
  ==== Gemfile
58
57
 
59
58
  # eventmachine
60
- gem "em-pg-client", "~> 0.2.0.pre", :require => 'pg/em'
59
+ gem "em-pg-client", "~> 0.2.0", :require => 'pg/em'
61
60
  # em-synchrony
62
- gem "em-pg-client", "~> 0.2.0.pre", :require => ['pg/em', 'em-synchrony/pg']
61
+ gem "em-pg-client", "~> 0.2.0", :require => ['pg/em', 'em-synchrony/pg']
63
62
 
64
63
  ==== Github
65
64
 
66
65
  git clone git://github.com/royaltm/ruby-em-pg-client.git
67
- git checkout fully-async
68
66
 
69
67
  == WHY?
70
68
 
71
- Because until now nobody did it to fit my needs.
69
+ Because I didn't find any ruby-pg's EM implementation to fit my needs.
72
70
  I've found at least 3 other implementations of EM postgres client:
73
71
 
74
72
  * https://github.com/jzimmek/em-postgresql-sequel
75
73
  * https://github.com/leftbee/em-postgresql-adapter
76
74
  * https://github.com/jtoy/em-postgres
77
75
 
78
- and (except the bundled one which uses no longer maintained postgres-pr library)
76
+ and (except the EM-bundled one which uses no longer maintained postgres-pr library)
79
77
  all of them have similiar flaws:
80
78
 
81
- * 2 of them are designed to support some ORM (ActiveRecord or Sequel)
79
+ * 2 of them are designed to support some ORM (ActiveRecord or Sequel),
82
80
  so they are EM-Synchrony only,
83
81
  * non-standard API method names,
84
82
  * no (nonexistent or non-working) autoreconnect implementation,
83
+ * poor error handling,
85
84
  * not fully supporting asynchronous PG::Connection API.
86
85
 
87
86
  The last one is worth some comment:
88
87
 
89
88
  They all use blocking methods to retrieve whole result from server
90
- (PGConn#block() or PGConn#get_result() which also
89
+ (PGConn#block[http://deveiate.org/code/pg/PG/Connection.html#method-i-block] or
90
+ PGConn#get_result[http://deveiate.org/code/pg/PG/Connection.html#method-i-get_result] which also
91
91
  blocks when there is not enough buffered data on socket).
92
92
 
93
- This implementation makes use of non-blocking: PGConn#is_busy and PGConn#consume_input methods.
94
- Depending on size of result sets and concurrency level the gain in overall speed and responsiveness of your
95
- application might be actually quite huge. I've done some tests[link:BENCHMARKS.rdoc] already.
93
+ This implementation makes use of non-blocking:
94
+ PGConn#is_busy[http://deveiate.org/code/pg/PG/Connection.html#method-i-is_busy] and
95
+ PGConn#consume_input[http://deveiate.org/code/pg/PG/Connection.html#method-i-consume_input] methods.
96
+ Depending on the size of queries results and the level of concurrency, the gain in overall speed and
97
+ responsiveness of your application might be actually quite huge. I've done some
98
+ tests[link:BENCHMARKS.rdoc] already.
96
99
 
97
100
  == Thanks
98
101
 
99
102
  The greetz go to:
100
103
  - Authors[https://bitbucket.org/ged/ruby-pg/wiki/Home#!copying] of +pg+ driver (especially for its async-api)
101
104
  - Francis Cianfrocca for great reactor framework (EventMachine[https://github.com/eventmachine/eventmachine])
102
- - Ilya Grigorik (igrigorik[https://github.com/igrigorik]) for (untangling)[http://www.igvita.com/2010/03/22/untangling-evented-code-with-ruby-fibers/] EM with Fibers
105
+ - Ilya Grigorik (igrigorik[https://github.com/igrigorik]) for (untangling[http://www.igvita.com/2010/03/22/untangling-evented-code-with-ruby-fibers/]) EM with Fibers
103
106
 
104
107
  == USAGE
108
+ +em-pg-client+ provides PG::EM::Client class which inherits
109
+ PG::Connection[http://deveiate.org/code/pg/PG/Connection.html].
110
+ You can work with PG::EM::Client almost the same way you would with
111
+ PG::Connection.
112
+
113
+ The real difference begins when you turn EventMachine reactor on.
105
114
 
106
115
  === BASIC
107
116
 
@@ -109,13 +118,26 @@ The greetz go to:
109
118
 
110
119
  # no async
111
120
  pg = PG::EM::Client.new dbname: 'test'
112
- pq.query('select * from foo') do |result|
121
+ pg.query('select * from foo') do |result|
113
122
  puts Array(result).inspect
114
123
  end
115
-
124
+
116
125
  # asynchronous
117
126
  EM.run do
118
- pq.query('select * from foo') do |result|
127
+ df = pg.query('select * from foo')
128
+ df.callback { |result|
129
+ puts Array(result).inspect
130
+ EM.stop
131
+ }
132
+ df.errback {|ex|
133
+ raise ex
134
+ }
135
+ puts "sent"
136
+ end
137
+
138
+ # alternatively
139
+ EM.run do
140
+ pg.query('select * from foo') do |result|
119
141
  raise result if result.is_a? ::Exception
120
142
  puts Array(result).inspect
121
143
  EM.stop
@@ -123,12 +145,79 @@ The greetz go to:
123
145
  puts "sent"
124
146
  end
125
147
 
148
+ === PG::Connection methods adapted to EventMachine
149
+ The list of PG::EM::Client async methods for processing with EventMachine.
150
+
151
+ ==== 1. Async methods (always returning +Deferrable+ object):
152
+
153
+ * +Client.async_connect+ (singleton)
154
+ * +async_reset+
155
+ * +async_exec+ (alias: +async_query+)
156
+ * +async_prepare+
157
+ * +async_exec_prepared+
158
+ * +async_describe_prepared+
159
+ * +async_describe_portal+
160
+
161
+ For arguments of theese methods consult their original blocking (without +async_+ prefix)
162
+ counterparts in PG::Connection[http://deveiate.org/code/pg/PG/Connection.html] manual.
163
+
164
+ Use +callback+ on the returned +Deferrable+ to receive result.
165
+ The result you receive is PG::EM::Client for PG::EM::Client.async_connect
166
+ and +async_reset+, and PG::Result[http://deveiate.org/code/pg/PG/Result.html] for the rest
167
+ of the methods. The received PG::EM::Client is in a connected state and ready for queries.
168
+ You need to +clear+ obtained PG::Result object yourself or leave it to +gc+.
169
+
170
+ To detect a failure in an executed method use +errback+ on returned +Deferrable+.
171
+ You should expect an instance of +Exception+ (usually PG::Error) as +errback+
172
+ argument. You may check its +backtrace+ to find origin of the error.
173
+
174
+ ==== 2. Async / blocking methods (returning +Deferrable+ only when EM is running):
175
+
176
+ * +exec+ (alias: +query+)
177
+ * +prepare+
178
+ * +exec_prepared+
179
+ * +describe_prepared+
180
+ * +describe_portal+
181
+
182
+ Outside EventMachine's event loop these methods are regular, blocking PG::Connection
183
+ methods.
184
+
185
+ All the methods (1 & 2) accept block argument which they attach to +callback+ and +errback+
186
+ hooks of returned +Deferrable+.
187
+
188
+ You may also mix async and blocking methods without closing the connection.
189
+ You only need to start/stop EventMachine in between async calls.
190
+
191
+ ==== Special options
192
+ There are 3 additional connection options and 1 standard +pg+ option used by
193
+ async methods. You may add them as one of the *hash* options to
194
+ PG::EM::Client.new or PG::EM::Client.async_connect or simply use accessor
195
+ methods to change them on the fly. The additional options are not passed to
196
+ +libpq+.
197
+
198
+ The options are:
199
+
200
+ - +async_autoreconnect+ (+true+ / +false+ with default +false+ unless
201
+ +on_autoreconnect+ is specified)
202
+ allows automatic re-connection when there was a problem with connection
203
+ to the server,
204
+ - +on_autoreconnect+ (+nil+ / +Proc+ with default +nil+)
205
+ a hook which is called after auto-reconnecting,
206
+ - +query_timeout+ (+Float+ / +Fixnum+ with default +0+)
207
+ allows to set timeout for query execution,
208
+ - +connect_timeout+ (+Float+ / +Fixnum+ with default +0+)
209
+ connection establishing and resetting timeout.
210
+
211
+ Only +connect_timeout+ is a standard +libpq+ option, although changing it by
212
+ accessor method only affects asynchronous functions.
213
+
126
214
  === AUTORECONNECTING IN ASYNC MODE
127
- Autoreconnecting is done in non-blocking manner using #async_reset internally.
215
+ Autoreconnecting is done in non-blocking manner using +async_reset+ internally.
128
216
 
129
217
  EM.run do
130
218
  pg = PG::EM::Client.new dbname: 'test',
131
- connect_timeout: 5, query_timeout: 50
219
+ connect_timeout: 5, query_timeout: 50,
220
+ async_autoreconnect: true
132
221
  try_query = lambda do |&blk|
133
222
  pg.query('select * from foo') do |result|
134
223
  raise result if result.is_a? ::Exception
@@ -143,21 +232,22 @@ Autoreconnecting is done in non-blocking manner using #async_reset internally.
143
232
  }
144
233
  end
145
234
 
146
- to disable this feature call:
235
+ to enable this feature call:
147
236
 
148
- pg.async_autoreconnect = false
237
+ pg.async_autoreconnect = true
149
238
 
150
239
  or
151
240
 
152
241
  pg = PG::EM::Client.new dbname: 'test',
153
- async_autoreconnect: false
242
+ async_autoreconnect: true
154
243
 
155
244
  It's also possible to define +on_autoreconnect+ callback to be invoked
156
245
  while the connection has been reset. It's called just before the send query
157
246
  command is executed:
158
247
 
159
248
  EM.run do
160
- pg = PG::EM::Client.new dbname: 'test'
249
+ pg = PG::EM::Client.new dbname: 'test',
250
+ async_autoreconnect: true
161
251
  pg.prepare('bar', 'select * from foo order by cdate desc') do
162
252
  pg.on_autoreconnect = proc { |c, e|
163
253
  c.prepare('bar', 'select * from foo order by cdate desc')
@@ -177,14 +267,15 @@ command is executed:
177
267
  end
178
268
  end
179
269
 
180
- As you can see it's possible to send async query from inside on_autoreconnect
270
+ As you can see it's possible to send async query from inside +on_autoreconnect+
181
271
  proc. However you have to pass +Deferrable+ from the async callback to the
182
- caller. See +#on_autoreconnect+ docs for details.
272
+ caller. See PG::EM::Client#on_autoreconnect docs for details.
183
273
 
184
274
  === TRUE ASYNC
185
- For non-blocking connect use PG::EM::Client.async_connect and #async_reset for
186
- asynchronous connection reset. Like other async methods they return deferrable object.
187
- Use #callback to obtain already connected Client.
275
+ For non-blocking connect use PG::EM::Client.async_connect and
276
+ PG::EM::Client#async_reset for asynchronous re-connect. Like other async
277
+ methods they return +Deferrable+ object.
278
+ Use Deferrable's #callback to obtain already connected PG::EM::Client.
188
279
 
189
280
  EM.run do
190
281
  pool = (1..10).map {
@@ -205,10 +296,16 @@ Use #callback to obtain already connected Client.
205
296
  end
206
297
  end
207
298
 
208
- === EM-Synchrony
209
- Under +em-synchrony+ PG::EM::Client.new is fully asynchronous and blocks only current fiber.
210
- This also applies to #reset.
299
+ === Fibers / EM-Synchrony
300
+ There is a special version of PG::EM::Client library with fiber aware methods
301
+ for EM-Synchrony or other implementations of Fiber untangled EventMachine.
302
+
303
+ The +require+ string is "em-synchrony/pg" instead of "pg/em".
304
+
305
+ +em-synchrony/pg+ version of PG::EM::Client.new is fully asynchronous and
306
+ blocks only current fiber. This also applies to PG::EM::Client#reset.
211
307
 
308
+ require 'em-synchrony'
212
309
  require 'em-synchrony/pg'
213
310
 
214
311
  EM.synchrony do
@@ -219,6 +316,43 @@ This also applies to #reset.
219
316
  EM.stop
220
317
  end
221
318
 
319
+ Although em-synchrony[https://github.com/igrigorik/em-synchrony/] provides
320
+ very nice set of tools for untangled EventMachine, you don't really require
321
+ it to fully benefit from this version of PG::EM::Client.
322
+
323
+ ==== PG::Connection methods adapted to EM-Synchrony
324
+ The list of PG::EM::Client fiber aware methods for processing with
325
+ EM-Synchrony / EventMachine.
326
+
327
+ All +async_*+ methods are exactly the same as in pure EventMachine version
328
+ of PG::EM::Client.
329
+
330
+ The fiber aware methods are:
331
+
332
+ * +Client.connect+ (singleton, alias: +new+, +open+, +setdb+, +setdblogin+)
333
+ * +reset+
334
+ * +exec+ (alias: +query+)
335
+ * +prepare+
336
+ * +exec_prepared+
337
+ * +describe_prepared+
338
+ * +describe_portal+
339
+
340
+ Under the hood, these methods call async counterparts of themselves and +yield+ from current
341
+ fiber awaiting for the result. The PG::Result (or PG::EM::Client for +connect+
342
+ and +reset+) is then returned to the caller. If code block was given, it is
343
+ executed with result as the argument. In that case the value of the block is
344
+ returned instead and PG::Result is cleared (or in case of +connect+ or +reset+
345
+ PG::EM::Client is being closed) after executing block. From single fiber point
346
+ of view, they behave like regular blocking PG::Connection methods.
347
+
348
+ Each of them is also automatic, detecting if EventMachine is running.
349
+ If called outside EM event loop they are exactly the original methods of
350
+ PG::Connection.
351
+
352
+ Like in pure EventMachine version you can mix async, fiber aware and
353
+ blocking methods without finishing the connection. You only need to
354
+ start/stop EventMachine in between async calls.
355
+
222
356
  ==== Handling errors
223
357
 
224
358
  EM.synchrony do
@@ -289,6 +423,9 @@ This also applies to #reset.
289
423
  EM.stop
290
424
  end
291
425
 
426
+ Specifying +on_autoreconnect+ as PG::EM::Client.new initialization option,
427
+ implicitly enables +async_autoreconnect+.
428
+
292
429
  == LICENCE
293
430
 
294
431
  The MIT License - Copyright (c) 2012 Rafał Michalski
data/em-pg-client.gemspec CHANGED
@@ -2,14 +2,14 @@ $:.unshift "lib"
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "em-pg-client"
5
- s.version = "0.2.0.pre.3"
5
+ s.version = "0.2.0"
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"
9
9
  s.email = "rafal@yeondir.com"
10
10
  s.homepage = "http://github.com/royaltm/ruby-em-pg-client"
11
11
  s.require_path = "lib"
12
- s.description = "PostgreSQL asynchronous EventMachine client (ruby-pg) wrapper"
12
+ s.description = "PostgreSQL asynchronous EventMachine client, based on pg interface (PG::Connection)"
13
13
  s.authors = ["Rafal Michalski"]
14
14
  s.files = `git ls-files`.split("\n") - ['.gitignore']
15
15
  s.test_files = Dir.glob("spec/**/*")
@@ -5,18 +5,41 @@ module PG
5
5
  # Author:: Rafal Michalski (mailto:royaltm75@gmail.com)
6
6
  # Licence:: MIT License
7
7
  #
8
- # =PostgreSQL Client for EM-Synchrony
8
+ # =PostgreSQL Client for EM-Synchrony/Fibered EventMachine
9
9
  #
10
10
 
11
11
  # conform to *standard*
12
12
  alias_method :aquery, :async_query
13
13
 
14
- # fiber untangled version of theese methods:
14
+ # fiber aware methods:
15
15
  # - exec (aliased as query)
16
16
  # - exec_prepared
17
17
  # - prepare
18
- %w(exec exec_prepared prepare reset self.connect).each do |name|
18
+ # - describe_prepared
19
+ # - describe_portal
20
+ # - reset
21
+ # - Client.connect
22
+ %w(exec
23
+ exec_prepared
24
+ prepare
25
+ describe_prepared
26
+ describe_portal
27
+ reset
28
+ self.connect).each do |name|
19
29
  async_name = "async_#{name.split('.').last}"
30
+ blocking_call = case name
31
+ when 'reset'
32
+ '@async_command_aborted = false
33
+ super(*args, &blk)'
34
+ else
35
+ 'super(*args, &blk)'
36
+ end
37
+ clear_method = case name
38
+ when 'reset', 'self.connect'
39
+ 'finish'
40
+ else
41
+ 'clear'
42
+ end
20
43
  class_eval <<-EOD
21
44
  def #{name}(*args, &blk)
22
45
  if ::EM.reactor_running?
@@ -28,12 +51,16 @@ module PG
28
51
  result = Fiber.yield
29
52
  raise result if result.is_a?(::Exception)
30
53
  if block_given?
31
- yield result
54
+ begin
55
+ yield result
56
+ ensure
57
+ result.#{clear_method}
58
+ end
32
59
  else
33
60
  result
34
61
  end
35
62
  else
36
- super(*args, &blk)
63
+ #{blocking_call}
37
64
  end
38
65
  end
39
66
  EOD