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

Sign up to get free protection for your applications and to get access to all the features.
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