em-pg-client 0.3.3 → 0.3.4
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/HISTORY.md +5 -0
- data/README.md +52 -10
- data/lib/pg/em-version.rb +1 -1
- data/lib/pg/em.rb +151 -65
- data/lib/pg/em/client/watcher.rb +96 -20
- data/spec/em_client.rb +1 -1
- data/spec/em_client_autoreconnect.rb +253 -36
- data/spec/em_client_common.rb +230 -13
- data/spec/em_client_on_connect.rb +9 -9
- data/spec/em_connection_pool.rb +3 -1
- data/spec/em_synchrony_client.rb +191 -1
- data/spec/em_synchrony_client_autoreconnect.rb +249 -6
- data/spec/pg_em_client_connect_finish.rb +1 -1
- data/spec/pg_em_client_connect_timeout.rb +1 -1
- metadata +27 -13
- checksums.yaml +0 -7
data/HISTORY.md
CHANGED
data/README.md
CHANGED
@@ -72,15 +72,16 @@ Features
|
|
72
72
|
processing and transactions.
|
73
73
|
* [Sequel Adapter](https://github.com/fl00r/em-pg-sequel) by Peter Yanovich.
|
74
74
|
* Works on windows (requires ruby 2.0) ([issue #7][Issue 7]).
|
75
|
-
*
|
75
|
+
* Supports asynchronous query data processing in single row mode
|
76
76
|
([issue #12][Issue 12]). See {file:BENCHMARKS.md BENCHMARKING}.
|
77
|
+
* __New__ - asynchronous implementation of wait_for_notify
|
77
78
|
|
78
79
|
Requirements
|
79
80
|
------------
|
80
81
|
|
81
82
|
* ruby >= 1.9.2 (tested: 2.1.0, 2.0.0-p353, 1.9.3-p374, 1.9.2-p320)
|
82
83
|
* https://bitbucket.org/ged/ruby-pg >= 0.17.0
|
83
|
-
* [PostgreSQL](http://www.postgresql.org/ftp/source/) RDBMS >= 8.
|
84
|
+
* [PostgreSQL](http://www.postgresql.org/ftp/source/) RDBMS >= 8.4
|
84
85
|
* http://rubyeventmachine.com >= 1.0.0
|
85
86
|
* [EM-Synchrony](https://github.com/igrigorik/em-synchrony)
|
86
87
|
(optional - not needed for any of the client functionality,
|
@@ -96,7 +97,7 @@ Install
|
|
96
97
|
#### Gemfile
|
97
98
|
|
98
99
|
```ruby
|
99
|
-
gem "em-pg-client", "~> 0.3.
|
100
|
+
gem "em-pg-client", "~> 0.3.4"
|
100
101
|
```
|
101
102
|
|
102
103
|
#### Github
|
@@ -261,6 +262,52 @@ Additionally the `on_autoreconnect` callback may be set on the connection.
|
|
261
262
|
It's being invoked after successfull connection restart, just before the
|
262
263
|
pending command is sent again to the server.
|
263
264
|
|
265
|
+
### Server-sent notifications - async style
|
266
|
+
|
267
|
+
Not surprisingly, there are two possible ways to wait for notifications,
|
268
|
+
one with a deferrable:
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
pg = PG::EM::Client.new
|
272
|
+
EM.run do
|
273
|
+
pg.wait_for_notify_defer(7).callback do |notify|
|
274
|
+
if notify
|
275
|
+
puts "Someone spoke to us on channel: #{notify[:relname]} from #{notify[:be_pid]}"
|
276
|
+
else
|
277
|
+
puts "Too late, 7 seconds passed"
|
278
|
+
end
|
279
|
+
end.errback do |ex|
|
280
|
+
puts "Connection to deep space lost..."
|
281
|
+
end
|
282
|
+
pg.query_defer("LISTEN deep_space") do
|
283
|
+
pg.query_defer("NOTIFY deep_space") do
|
284
|
+
puts "Reaching out... to the other worlds"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
and the other, using fibers:
|
291
|
+
|
292
|
+
```ruby
|
293
|
+
EM.synchrony do
|
294
|
+
pg = PG::EM::Client.new
|
295
|
+
EM::Synchrony.next_tick do
|
296
|
+
pg.query('LISTEN "some channel"')
|
297
|
+
pg.query('SELECT pg_notify($1::text,$2::text)', ['some channel', 'with some message'])
|
298
|
+
end
|
299
|
+
pg.wait_for_notify(10) do |channel, pid, payload|
|
300
|
+
puts "I've got notification on #{channel} #{payload}."
|
301
|
+
end.tap do |name|
|
302
|
+
puts "Whatever, I've been waiting too long already" if name.nil?
|
303
|
+
end
|
304
|
+
end
|
305
|
+
```
|
306
|
+
|
307
|
+
As you might have noticed, one does not simply wait for notifications,
|
308
|
+
but one can also run some queries on the same connection at the same time,
|
309
|
+
if one wishes so.
|
310
|
+
|
264
311
|
### Connection Pool
|
265
312
|
|
266
313
|
Forever alone? Not anymore! There is a dedicated {PG::EM::ConnectionPool}
|
@@ -360,16 +407,14 @@ The other reason was to get rid of the ugly em / em-synchrony duality.
|
|
360
407
|
Bugs/Limitations
|
361
408
|
----------------
|
362
409
|
|
363
|
-
* no async support for
|
364
|
-
`wait_for_notify`
|
410
|
+
* no async support for COPY commands (`get_copy_data`, `put_copy_data`)
|
365
411
|
* actually no ActiveRecord support (you are welcome to contribute).
|
366
412
|
|
367
413
|
TODO:
|
368
414
|
-----
|
369
415
|
|
370
416
|
* more convenient streaming API
|
371
|
-
* implement EM adapted version of `get_copy_data`, `put_copy_data
|
372
|
-
`wait_for_notify` and `transaction`
|
417
|
+
* implement EM adapted version of `get_copy_data`, `put_copy_data`
|
373
418
|
* ORM (ActiveRecord and maybe Datamapper) support as separate projects
|
374
419
|
|
375
420
|
More Info
|
@@ -398,13 +443,10 @@ The greetz go to:
|
|
398
443
|
* Andrew Rudenko [prepor](https://github.com/prepor) for the implicit idea
|
399
444
|
of the re-usable watcher from his [em-pg](https://github.com/prepor/em-pg).
|
400
445
|
|
401
|
-
[![Bitdeli Badge][BB img]][Bitdeli Badge]
|
402
|
-
|
403
446
|
[Gem Version]: https://rubygems.org/gems/em-pg-client
|
404
447
|
[Dependency Status]: https://gemnasium.com/royaltm/ruby-em-pg-client
|
405
448
|
[Coverage Status]: https://coveralls.io/r/royaltm/ruby-em-pg-client
|
406
449
|
[Build Status]: https://travis-ci.org/royaltm/ruby-em-pg-client
|
407
|
-
[Bitdeli Badge]: https://bitdeli.com/free
|
408
450
|
[Issue 7]: https://github.com/royaltm/ruby-em-pg-client/issues/7
|
409
451
|
[Issue 12]: https://github.com/royaltm/ruby-em-pg-client/issues/12
|
410
452
|
[GV img]: https://badge.fury.io/rb/em-pg-client.png
|
data/lib/pg/em-version.rb
CHANGED
data/lib/pg/em.rb
CHANGED
@@ -352,7 +352,7 @@ module PG
|
|
352
352
|
#
|
353
353
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-c-new PG::Connection.new
|
354
354
|
def self.connect_defer(*args, &blk)
|
355
|
-
df =
|
355
|
+
df = FeaturedDeferrable.new(&blk)
|
356
356
|
async_args = parse_async_options(args)
|
357
357
|
conn = df.protect { connect_start(*args) }
|
358
358
|
if conn
|
@@ -523,70 +523,77 @@ module PG
|
|
523
523
|
|
524
524
|
# @!visibility private
|
525
525
|
# Perform auto re-connect. Used internally.
|
526
|
-
def async_autoreconnect!(deferrable, error, &
|
526
|
+
def async_autoreconnect!(deferrable, error, send_proc = nil, &on_connection_bad)
|
527
527
|
# reconnect only if connection is bad and flag is set
|
528
|
-
if self.status == CONNECTION_BAD
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
#
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
528
|
+
if self.status == CONNECTION_BAD
|
529
|
+
|
530
|
+
yield if block_given?
|
531
|
+
|
532
|
+
if async_autoreconnect
|
533
|
+
# check if transaction was active
|
534
|
+
was_in_transaction = case @last_transaction_status
|
535
|
+
when PQTRANS_IDLE, PQTRANS_UNKNOWN
|
536
|
+
false
|
537
|
+
else
|
538
|
+
true
|
539
|
+
end
|
540
|
+
# reset asynchronously
|
541
|
+
reset_df = reset_defer
|
542
|
+
# just fail on reset failure
|
543
|
+
reset_df.errback { |ex| deferrable.fail ex }
|
544
|
+
# reset succeeds
|
545
|
+
reset_df.callback do
|
546
|
+
# handle on_autoreconnect
|
547
|
+
if on_autoreconnect
|
548
|
+
# wrap in a fiber, so on_autoreconnect code may yield from it
|
549
|
+
Fiber.new do
|
550
|
+
# call on_autoreconnect handler and fail if it raises an error
|
551
|
+
returned_df = begin
|
552
|
+
on_autoreconnect.call(self, error)
|
553
|
+
rescue => ex
|
554
|
+
ex
|
555
|
+
end
|
556
|
+
if returned_df.respond_to?(:callback) && returned_df.respond_to?(:errback)
|
557
|
+
# the handler returned a deferrable
|
558
|
+
returned_df.callback do
|
559
|
+
if was_in_transaction || !send_proc
|
560
|
+
# fail anyway, there was a transaction in progress or in single result mode
|
561
|
+
deferrable.fail error
|
562
|
+
else
|
563
|
+
# try to call failed query command again
|
564
|
+
deferrable.protect(&send_proc)
|
565
|
+
end
|
561
566
|
end
|
567
|
+
# fail when handler's deferrable fails
|
568
|
+
returned_df.errback { |ex| deferrable.fail ex }
|
569
|
+
elsif returned_df.is_a?(Exception)
|
570
|
+
# tha handler returned an exception object, so fail with it
|
571
|
+
deferrable.fail returned_df
|
572
|
+
elsif returned_df == false || !send_proc || (was_in_transaction && returned_df != true)
|
573
|
+
# tha handler returned false or in single result mode
|
574
|
+
# or there was an active transaction and handler didn't return true
|
575
|
+
deferrable.fail error
|
576
|
+
else
|
577
|
+
# try to call failed query command again
|
578
|
+
deferrable.protect(&send_proc)
|
562
579
|
end
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
# try to call failed query command again
|
574
|
-
deferrable.protect(&send_proc)
|
575
|
-
end
|
576
|
-
end.resume
|
577
|
-
elsif was_in_transaction || !send_proc
|
578
|
-
# there was a transaction in progress, fail anyway
|
579
|
-
deferrable.fail error
|
580
|
-
else
|
581
|
-
# no on_autoreconnect handler, no transaction, then
|
582
|
-
# try to call failed query command again
|
583
|
-
deferrable.protect(&send_proc)
|
580
|
+
end.resume
|
581
|
+
elsif was_in_transaction || !send_proc
|
582
|
+
# there was a transaction in progress or in single result mode;
|
583
|
+
# fail anyway
|
584
|
+
deferrable.fail error
|
585
|
+
else
|
586
|
+
# no on_autoreconnect handler, no transaction
|
587
|
+
# try to call failed query command again
|
588
|
+
deferrable.protect(&send_proc)
|
589
|
+
end
|
584
590
|
end
|
591
|
+
# connection is bad, reset in progress, all done
|
592
|
+
return
|
585
593
|
end
|
586
|
-
else
|
587
|
-
# connection is good, or the async_autoreconnect is not set
|
588
|
-
deferrable.fail error
|
589
594
|
end
|
595
|
+
# connection is either good or bad, the async_autoreconnect is not set
|
596
|
+
deferrable.fail error
|
590
597
|
end
|
591
598
|
|
592
599
|
# @!macro deferrable_api
|
@@ -653,7 +660,7 @@ module PG
|
|
653
660
|
|
654
661
|
class_eval <<-EOD, __FILE__, __LINE__
|
655
662
|
def #{defer_name}(*args, &blk)
|
656
|
-
df = FeaturedDeferrable.new
|
663
|
+
df = FeaturedDeferrable.new
|
657
664
|
send_proc = proc do
|
658
665
|
#{send_name}(*args)
|
659
666
|
setup_emio_watcher.watch_results(df, send_proc)
|
@@ -663,10 +670,11 @@ module PG
|
|
663
670
|
@last_transaction_status = transaction_status
|
664
671
|
send_proc.call
|
665
672
|
rescue Error => e
|
666
|
-
::EM.next_tick { async_autoreconnect!(df, e,
|
673
|
+
::EM.next_tick { async_autoreconnect!(df, e, send_proc) }
|
667
674
|
rescue Exception => e
|
668
675
|
::EM.next_tick { df.fail(e) }
|
669
676
|
end
|
677
|
+
df.completion(&blk) if block_given?
|
670
678
|
df
|
671
679
|
end
|
672
680
|
EOD
|
@@ -678,6 +686,45 @@ module PG
|
|
678
686
|
alias_method :async_exec_defer, :exec_defer
|
679
687
|
alias_method :exec_params_defer, :exec_defer
|
680
688
|
|
689
|
+
# Asynchronously waits for notification or until the optional
|
690
|
+
# +timeout+ is reached, whichever comes first. +timeout+ is
|
691
|
+
# measured in seconds and can be fractional.
|
692
|
+
# Returns immediately with a Deferrable.
|
693
|
+
#
|
694
|
+
# Pass the block to the returned deferrable's +callback+ to obtain notification
|
695
|
+
# hash. In case of connection error +errback+ hook is called with an error object.
|
696
|
+
# If the +timeout+ is reached +nil+ is passed to deferrable's +callback+.
|
697
|
+
# If the block is provided it's bound to both the +callback+ and +errback+ hooks
|
698
|
+
# of the returned deferrable.
|
699
|
+
# If another call is made to this method before the notification is received
|
700
|
+
# (or before reaching timeout) the previous deferrable's +errback+ will be called
|
701
|
+
# with +nil+ argument.
|
702
|
+
#
|
703
|
+
# @return [FeaturedDeferrable]
|
704
|
+
# @yieldparam notification [Hash|nil|Error] notification hash or a PG::Error instance on error
|
705
|
+
# or nil when timeout is reached or canceled.
|
706
|
+
#
|
707
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-notifies PG::Connection#notifies
|
708
|
+
def wait_for_notify_defer(timeout = nil, &blk)
|
709
|
+
df = FeaturedDeferrable.new
|
710
|
+
begin
|
711
|
+
check_async_command_aborted!
|
712
|
+
if status == CONNECTION_OK
|
713
|
+
setup_emio_watcher.watch_notify(df, timeout)
|
714
|
+
else
|
715
|
+
raise_error ConnectionBad
|
716
|
+
end
|
717
|
+
rescue Error => e
|
718
|
+
::EM.next_tick { async_autoreconnect!(df, e) }
|
719
|
+
rescue Exception => e
|
720
|
+
::EM.next_tick { df.fail(e) }
|
721
|
+
end
|
722
|
+
df.completion(&blk) if block_given?
|
723
|
+
df
|
724
|
+
end
|
725
|
+
|
726
|
+
alias_method :notifies_wait_defer, :wait_for_notify_defer
|
727
|
+
|
681
728
|
# Asynchronously retrieves the next result from a call to
|
682
729
|
# #send_query (or another asynchronous command) and immediately
|
683
730
|
# returns with a Deferrable.
|
@@ -692,11 +739,10 @@ module PG
|
|
692
739
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_result PG::Connection#get_result
|
693
740
|
#
|
694
741
|
def get_result_defer(&blk)
|
742
|
+
df = FeaturedDeferrable.new
|
695
743
|
begin
|
696
|
-
df = FeaturedDeferrable.new(&blk)
|
697
744
|
if status == CONNECTION_OK
|
698
745
|
if is_busy
|
699
|
-
check_async_command_aborted!
|
700
746
|
setup_emio_watcher.watch_results(df, nil, true)
|
701
747
|
else
|
702
748
|
df.succeed blocking_get_result
|
@@ -709,6 +755,7 @@ module PG
|
|
709
755
|
rescue Exception => e
|
710
756
|
::EM.next_tick { df.fail(e) }
|
711
757
|
end
|
758
|
+
df.completion(&blk) if block_given?
|
712
759
|
df
|
713
760
|
end
|
714
761
|
|
@@ -726,10 +773,9 @@ module PG
|
|
726
773
|
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_last_result PG::Connection#get_last_result
|
727
774
|
#
|
728
775
|
def get_last_result_defer(&blk)
|
776
|
+
df = FeaturedDeferrable.new
|
729
777
|
begin
|
730
|
-
df = FeaturedDeferrable.new(&blk)
|
731
778
|
if status == CONNECTION_OK
|
732
|
-
check_async_command_aborted!
|
733
779
|
setup_emio_watcher.watch_results(df)
|
734
780
|
else
|
735
781
|
df.succeed
|
@@ -739,11 +785,13 @@ module PG
|
|
739
785
|
rescue Exception => e
|
740
786
|
::EM.next_tick { df.fail(e) }
|
741
787
|
end
|
788
|
+
df.completion(&blk) if block_given?
|
742
789
|
df
|
743
790
|
end
|
744
791
|
|
745
792
|
# @!endgroup
|
746
793
|
|
794
|
+
alias_method :blocking_wait_for_notify, :wait_for_notify
|
747
795
|
alias_method :blocking_get_result, :get_result
|
748
796
|
|
749
797
|
def raise_error(klass=Error, message=error_message)
|
@@ -892,6 +940,44 @@ module PG
|
|
892
940
|
alias_method :async_query, :exec
|
893
941
|
alias_method :async_exec, :exec
|
894
942
|
|
943
|
+
# Blocks while waiting for notification(s), or until the optional
|
944
|
+
# +timeout+ is reached, whichever comes first.
|
945
|
+
# Returns +nil+ if +timeout+ is reached, the name of the +NOTIFY+
|
946
|
+
# event otherwise.
|
947
|
+
#
|
948
|
+
# If EventMachine reactor is running and the current fiber isn't the
|
949
|
+
# root fiber this method performs command asynchronously yielding
|
950
|
+
# current fiber. Other fibers can process while the current one is
|
951
|
+
# waiting for notifications.
|
952
|
+
#
|
953
|
+
# Otherwise performs a blocking call to a parent method.
|
954
|
+
# @return [String|nil]
|
955
|
+
# @yieldparam name [String] the name of the +NOTIFY+ event
|
956
|
+
# @yieldparam pid [Number] the generating pid
|
957
|
+
# @yieldparam payload [String] the optional payload
|
958
|
+
# @raise [PG::Error]
|
959
|
+
#
|
960
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-i-wait_for_notify PG::Connection#wait_for_notify
|
961
|
+
def wait_for_notify(timeout = nil)
|
962
|
+
if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
|
963
|
+
unless notify_hash = notifies
|
964
|
+
if (notify_hash = fiber_sync wait_for_notify_defer(timeout), f).is_a?(::Exception)
|
965
|
+
raise notify_hash
|
966
|
+
end
|
967
|
+
end
|
968
|
+
if notify_hash
|
969
|
+
if block_given?
|
970
|
+
yield notify_hash.values_at(:relname, :be_pid, :extra)
|
971
|
+
end
|
972
|
+
notify_hash[:relname]
|
973
|
+
end
|
974
|
+
else
|
975
|
+
super
|
976
|
+
end
|
977
|
+
end
|
978
|
+
|
979
|
+
alias_method :notifies_wait, :wait_for_notify
|
980
|
+
|
895
981
|
# Retrieves the next result from a call to #send_query (or another
|
896
982
|
# asynchronous command). If no more results are available returns
|
897
983
|
# +nil+ and the block (if given) is never called.
|
data/lib/pg/em/client/watcher.rb
CHANGED
@@ -11,6 +11,11 @@ module PG
|
|
11
11
|
def initialize(client)
|
12
12
|
@client = client
|
13
13
|
@is_connected = true
|
14
|
+
@one_result_mode = false
|
15
|
+
@deferrable = nil
|
16
|
+
@notify_deferrable = nil
|
17
|
+
@timer = nil
|
18
|
+
@notify_timer = nil
|
14
19
|
end
|
15
20
|
|
16
21
|
def watching?
|
@@ -21,28 +26,49 @@ module PG
|
|
21
26
|
@one_result_mode
|
22
27
|
end
|
23
28
|
|
24
|
-
def watch_results(deferrable, send_proc=nil, one_result_mode=false)
|
29
|
+
def watch_results(deferrable, send_proc = nil, one_result_mode = false)
|
25
30
|
@one_result_mode = one_result_mode
|
26
31
|
@last_result = nil
|
27
32
|
@deferrable = deferrable
|
28
33
|
@send_proc = send_proc
|
29
34
|
cancel_timer
|
30
|
-
self.notify_readable = true
|
35
|
+
self.notify_readable = true unless notify_readable?
|
31
36
|
if (timeout = @client.query_timeout) > 0
|
32
|
-
@
|
37
|
+
@readable_timestamp = Time.now
|
33
38
|
setup_timer timeout
|
34
39
|
end
|
35
40
|
fetch_results
|
36
41
|
end
|
37
42
|
|
43
|
+
def watch_notify(deferrable, timeout = nil)
|
44
|
+
notify_df = @notify_deferrable
|
45
|
+
@notify_deferrable = deferrable
|
46
|
+
cancel_notify_timer
|
47
|
+
self.notify_readable = true unless notify_readable?
|
48
|
+
if timeout
|
49
|
+
@notify_timer = ::EM::Timer.new(timeout) do
|
50
|
+
@notify_timer = nil
|
51
|
+
succeed_notify
|
52
|
+
end
|
53
|
+
end
|
54
|
+
notify_df.fail nil if notify_df
|
55
|
+
check_notify
|
56
|
+
end
|
57
|
+
|
38
58
|
def setup_timer(timeout, adjustment = 0)
|
39
59
|
@timer = ::EM::Timer.new(timeout - adjustment) do
|
40
|
-
if (last_interval = Time.now - @
|
60
|
+
if (last_interval = Time.now - @readable_timestamp) >= timeout
|
41
61
|
@timer = nil
|
62
|
+
cancel_notify_timer
|
42
63
|
self.notify_readable = false
|
43
64
|
@client.async_command_aborted = true
|
44
|
-
@
|
65
|
+
@send_proc = nil
|
66
|
+
begin
|
45
67
|
@client.raise_error ConnectionBad, "query timeout expired (async)"
|
68
|
+
rescue Exception => e
|
69
|
+
fail_result e
|
70
|
+
# notify should also fail: query timeout is like connection error
|
71
|
+
fail_notify e
|
46
72
|
end
|
47
73
|
else
|
48
74
|
setup_timer timeout, last_interval
|
@@ -50,6 +76,13 @@ module PG
|
|
50
76
|
end
|
51
77
|
end
|
52
78
|
|
79
|
+
def cancel_notify_timer
|
80
|
+
if @notify_timer
|
81
|
+
@notify_timer.cancel
|
82
|
+
@notify_timer = nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
53
86
|
def cancel_timer
|
54
87
|
if @timer
|
55
88
|
@timer.cancel
|
@@ -60,9 +93,17 @@ module PG
|
|
60
93
|
def notify_readable
|
61
94
|
@client.consume_input
|
62
95
|
rescue Exception => e
|
63
|
-
handle_error
|
96
|
+
handle_error e
|
64
97
|
else
|
65
|
-
fetch_results
|
98
|
+
fetch_results if @deferrable
|
99
|
+
check_notify if @notify_deferrable
|
100
|
+
end
|
101
|
+
|
102
|
+
def check_notify
|
103
|
+
if notify_hash = @client.notifies
|
104
|
+
cancel_notify_timer
|
105
|
+
succeed_notify notify_hash
|
106
|
+
end
|
66
107
|
end
|
67
108
|
|
68
109
|
# Carefully extract results without
|
@@ -84,40 +125,75 @@ module PG
|
|
84
125
|
@last_result = single_result
|
85
126
|
end
|
86
127
|
rescue Exception => e
|
87
|
-
handle_error
|
128
|
+
handle_error e
|
88
129
|
else
|
89
130
|
if result == false
|
90
|
-
@
|
131
|
+
@readable_timestamp = Time.now if @timer
|
91
132
|
else
|
92
|
-
self.notify_readable = false
|
93
133
|
cancel_timer
|
94
|
-
|
95
|
-
@deferrable
|
134
|
+
self.notify_readable = false unless @notify_deferrable
|
135
|
+
df = @deferrable
|
136
|
+
@deferrable = @send_proc = nil
|
137
|
+
df.succeed result
|
96
138
|
end
|
97
139
|
end
|
98
140
|
|
99
141
|
def unbind
|
100
142
|
@is_connected = false
|
101
|
-
|
102
|
-
|
143
|
+
cancel_timer
|
144
|
+
cancel_notify_timer
|
145
|
+
if @deferrable || @notify_deferrable
|
103
146
|
@client.raise_error ConnectionBad, "connection reset"
|
104
|
-
end
|
147
|
+
end
|
148
|
+
rescue Exception => e
|
149
|
+
fail_result e
|
150
|
+
fail_notify e
|
105
151
|
end
|
106
152
|
|
107
153
|
private
|
108
154
|
|
155
|
+
def fail_result(e)
|
156
|
+
df = @deferrable
|
157
|
+
@deferrable = nil
|
158
|
+
df.fail e if df
|
159
|
+
end
|
160
|
+
|
161
|
+
def succeed_notify(notify_hash = nil)
|
162
|
+
self.notify_readable = false unless @deferrable
|
163
|
+
df = @notify_deferrable
|
164
|
+
@notify_deferrable = nil
|
165
|
+
df.succeed notify_hash
|
166
|
+
end
|
167
|
+
|
168
|
+
def fail_notify(e)
|
169
|
+
df = @notify_deferrable
|
170
|
+
@notify_deferrable = nil
|
171
|
+
df.fail e if df
|
172
|
+
end
|
173
|
+
|
109
174
|
def handle_error(e)
|
110
|
-
self.notify_readable = false
|
111
175
|
cancel_timer
|
112
176
|
send_proc = @send_proc
|
113
177
|
@send_proc = nil
|
114
|
-
df = @deferrable
|
178
|
+
df = @deferrable || FeaturedDeferrable.new
|
115
179
|
# prevent unbind error on auto re-connect
|
116
|
-
@deferrable =
|
180
|
+
@deferrable = nil
|
181
|
+
notify_df = @notify_deferrable
|
182
|
+
self.notify_readable = false unless notify_df
|
117
183
|
if e.is_a?(PG::Error)
|
118
|
-
@client.async_autoreconnect!(df, e,
|
184
|
+
@client.async_autoreconnect!(df, e, send_proc) do
|
185
|
+
# there was a connection error so stop any remaining activity
|
186
|
+
if notify_df
|
187
|
+
@notify_deferrable = nil
|
188
|
+
cancel_notify_timer
|
189
|
+
self.notify_readable = false
|
190
|
+
# fail notify_df after deferrable completes
|
191
|
+
# handler might setup listen again then immediately
|
192
|
+
df.completion { notify_df.fail e }
|
193
|
+
end
|
194
|
+
end
|
119
195
|
else
|
120
|
-
df.fail
|
196
|
+
df.fail e
|
121
197
|
end
|
122
198
|
end
|
123
199
|
|