rapns 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -44,7 +44,7 @@ If you want to use rapns in environments other than development or production, y
44
44
  ### Options:
45
45
 
46
46
  * `host` the APNs host to connect to, either `gateway.sandbox.push.apple.com` or `gateway.sandbox.push.apple.com`.
47
- * `port` the APNs port. Currently 2195 for both hosts.
47
+ * `port` the APNs port. Currently `2195` for both hosts.
48
48
  * `certificate` The path to your .pem certificate, `config/rapns` is automatically checked if a relative path is given.
49
49
  * `certificate_password` (default: blank) the password you used when exporting your certificate, if any.
50
50
  * `airbrake_notify` (default: true) Enables/disables error notifications via Airbrake.
@@ -59,7 +59,11 @@ If you want to use rapns in environments other than development or production, y
59
59
 
60
60
  ### Options
61
61
 
62
- * `--foreground` will prevent rapns from forking into a daemon. Activity information will be printed to the screen.
62
+ * `--foreground` will prevent rapns from forking into a daemon.
63
+
64
+ ## Logging
65
+
66
+ rapns logs activity to `rapns.log` in your Rails log directory. This is also printed to STDOUT when running in the foreground. When running as a daemon rapns does not print to STDOUT or STDERR.
63
67
 
64
68
  ## Sending a Notification
65
69
 
@@ -74,7 +78,7 @@ If you want to use rapns in environments other than development or production, y
74
78
  n.save!
75
79
 
76
80
  * `sound` defaults to `1.aiff`. You can either set it to a custom .aiff file, or `nil` for no sound.
77
- * `expiry` is the time in seconds the APNs will spend trying to deliver the notification to the device. The notification is discarded if it has not been delivered in this time. Default is 1 day.
81
+ * `expiry` is the time in seconds the APNs (not rapns) will spend trying to deliver the notification to the device. The notification is discarded if it has not been delivered in this time. Default is 1 day.
78
82
  * `attributes_for_device` is the `NSDictionary` argument passed to your iOS app in either `didFinishLaunchingWithOptions` or `didReceiveRemoteNotification`.
79
83
  * `deliver_after` is not required, but may be set if you'd like to delay delivery of the notification to a specific time in the future.
80
84
 
@@ -109,4 +113,4 @@ Fork as usual and go crazy!
109
113
 
110
114
  When running specs, please note that the ActiveRecord adapter can be changed by setting the `ADAPTER` environment variable. For example: `ADAPTER=postgresql rake`.
111
115
 
112
- Available adapters for testing are 'mysql', 'mysql2' and 'postgresql'.
116
+ Available adapters for testing are `mysql`, `mysql2` and `postgresql`.
@@ -39,7 +39,7 @@ module Rapns
39
39
  @ssl_socket.flush
40
40
 
41
41
  check_for_error
42
- rescue Errno::EPIPE => e
42
+ rescue Errno::EPIPE, OpenSSL::SSL::SSLError => e
43
43
  Rapns::Daemon.logger.error("[#{@name}] Lost connection to #{Rapns::Daemon.configuration.host}:#{Rapns::Daemon.configuration.port}, reconnecting...")
44
44
  @tcp_socket, @ssl_socket = connect_socket
45
45
 
@@ -4,7 +4,7 @@ module Rapns
4
4
  STOP = 0x666
5
5
 
6
6
  def start
7
- @thread = Thread.new do
7
+ Thread.new do
8
8
  loop do
9
9
  break if @stop
10
10
  handle_next_notification
@@ -18,32 +18,39 @@ module Rapns
18
18
 
19
19
  protected
20
20
 
21
+ def deliver(notification)
22
+ Rapns::Daemon.connection_pool.claim_connection do |connection|
23
+ begin
24
+ connection.write(notification.to_binary)
25
+
26
+ notification.delivered = true
27
+ notification.delivered_at = Time.now
28
+ notification.save!(:validate => false)
29
+
30
+ Rapns::Daemon.logger.info("Notification #{notification.id} delivered to #{notification.device_token}")
31
+ rescue Rapns::DeliveryError => error
32
+ handle_delivery_error(notification, error)
33
+ end
34
+ end
35
+ end
36
+
37
+ def handle_delivery_error(notification, error)
38
+ Rapns::Daemon.logger.error(error)
39
+
40
+ notification.delivered = false
41
+ notification.delivered_at = nil
42
+ notification.failed = true
43
+ notification.failed_at = Time.now
44
+ notification.error_code = error.code
45
+ notification.error_description = error.description
46
+ notification.save!(:validate => false)
47
+ end
48
+
21
49
  def handle_next_notification
22
50
  notification = Rapns::Daemon.delivery_queue.pop
23
51
  begin
24
52
  return if notification == STOP
25
-
26
- Rapns::Daemon.connection_pool.claim_connection do |connection|
27
- begin
28
- connection.write(notification.to_binary)
29
-
30
- notification.delivered = true
31
- notification.delivered_at = Time.now
32
- notification.save!(:validate => false)
33
-
34
- Rapns::Daemon.logger.info("Notification #{notification.id} delivered to #{notification.device_token}")
35
- rescue Rapns::DeliveryError => error
36
- Rapns::Daemon.logger.error(error)
37
-
38
- notification.delivered = false
39
- notification.delivered_at = nil
40
- notification.failed = true
41
- notification.failed_at = Time.now
42
- notification.error_code = error.code
43
- notification.error_description = error.description
44
- notification.save!(:validate => false)
45
- end
46
- end
53
+ deliver(notification)
47
54
  rescue StandardError => e
48
55
  Rapns::Daemon.logger.error(e)
49
56
  ensure
@@ -4,8 +4,8 @@ module Rapns
4
4
  def initialize(num_handlers)
5
5
  @num_handlers = num_handlers
6
6
  @queue = Queue.new
7
- @feeder_threads = []
8
- @mutex = Mutex.new
7
+ @feeder = nil
8
+ @handler_mutex = Mutex.new
9
9
  end
10
10
 
11
11
  def push(obj)
@@ -17,7 +17,7 @@ module Rapns
17
17
  end
18
18
 
19
19
  def handler_available
20
- @mutex.synchronize do
20
+ @handler_mutex.synchronize do
21
21
  signal_feeder if handler_available?
22
22
  end
23
23
  end
@@ -28,19 +28,16 @@ module Rapns
28
28
 
29
29
  def signal_feeder
30
30
  begin
31
- t = @feeder_threads.shift
32
- t.wakeup if t
31
+ @feeder.wakeup if @feeder
33
32
  rescue ThreadError
34
33
  retry
35
34
  end
36
35
  end
37
36
 
38
37
  def wait_for_available_handler
39
- Thread.exclusive do
40
- if @queue.size >= @num_handlers
41
- @feeder_threads << Thread.current
42
- Thread.stop
43
- end
38
+ if !handler_available?
39
+ @feeder = Thread.current
40
+ @feeder.stop
44
41
  end
45
42
  end
46
43
  end
@@ -7,13 +7,21 @@ ADAPTER_ERRORS = [PGError, Mysql::Error, Mysql2::Error]
7
7
  module Rapns
8
8
  module Daemon
9
9
  class Feeder
10
- def self.start
10
+ def self.start(foreground)
11
+ connect unless foreground
12
+
11
13
  loop do
12
14
  break if @stop
13
15
  enqueue_notifications
14
16
  end
15
17
  end
16
18
 
19
+ def self.stop
20
+ @stop = true
21
+ end
22
+
23
+ protected
24
+
17
25
  def self.enqueue_notifications
18
26
  begin
19
27
  Rapns::Notification.ready_for_delivery.each do |notification|
@@ -31,27 +39,36 @@ module Rapns
31
39
  sleep Rapns::Daemon.configuration.poll
32
40
  end
33
41
 
34
- def self.stop
35
- @stop = true
36
- end
37
-
38
42
  def self.reconnect
39
43
  Rapns::Daemon.logger.warn('Lost connection to database, reconnecting...')
40
44
  attempts = 0
41
45
  loop do
42
46
  begin
43
47
  Rapns::Daemon.logger.warn("Attempt #{attempts += 1}")
44
- ActiveRecord::Base.clear_all_connections!
45
- ActiveRecord::Base.establish_connection
46
- Rapns::Notification.count
48
+ connect
49
+ check_is_connected
47
50
  break
48
51
  rescue *ADAPTER_ERRORS => e
49
52
  Rapns::Daemon.logger.error(e, :airbrake_notify => false)
50
- sleep 2 # Avoid thrashing.
53
+ sleep_to_avoid_thrashing
51
54
  end
52
55
  end
53
56
  Rapns::Daemon.logger.warn('Database reconnected')
54
57
  end
58
+
59
+ def self.connect
60
+ ActiveRecord::Base.clear_all_connections!
61
+ ActiveRecord::Base.establish_connection
62
+ end
63
+
64
+ def self.check_is_connected
65
+ # Simple asking the adapter for the connection state is not sufficient.
66
+ Rapns::Notification.count
67
+ end
68
+
69
+ def self.sleep_to_avoid_thrashing
70
+ sleep 2
71
+ end
55
72
  end
56
73
  end
57
74
  end
@@ -13,7 +13,7 @@ module Rapns
13
13
  end
14
14
 
15
15
  def error(msg, options = {})
16
- airbrake_notify(msg) if msg.is_a?(Exception) && options[:airbrake_notify] != false
16
+ airbrake_notify(msg) if notify_via_airbrake?(msg, options)
17
17
  log(:error, msg, 'ERROR')
18
18
  end
19
19
 
@@ -44,6 +44,10 @@ module Rapns
44
44
  HoptoadNotifier.notify(e)
45
45
  end
46
46
  end
47
+
48
+ def notify_via_airbrake?(msg, options)
49
+ msg.is_a?(Exception) && options[:airbrake_notify] != false
50
+ end
47
51
  end
48
52
  end
49
53
  end
data/lib/rapns/daemon.rb CHANGED
@@ -36,10 +36,7 @@ module Rapns
36
36
 
37
37
  self.delivery_queue = DeliveryQueue.new(configuration.connections)
38
38
 
39
- unless foreground?
40
- daemonize
41
- ActiveRecord::Base.establish_connection
42
- end
39
+ daemonize unless foreground?
43
40
 
44
41
  write_pid_file
45
42
 
@@ -49,8 +46,8 @@ module Rapns
49
46
  self.connection_pool = ConnectionPool.new(configuration.connections)
50
47
  connection_pool.populate
51
48
 
52
- logger.info("Ready")
53
- Feeder.start
49
+ logger.info('Ready')
50
+ Feeder.start(foreground?)
54
51
  end
55
52
 
56
53
  protected
data/lib/rapns/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rapns
2
- VERSION = '0.2.2'
2
+ VERSION = '0.2.3'
3
3
  end
@@ -160,6 +160,51 @@ describe Rapns::Daemon::Connection, "when the connection is lost" do
160
160
  end
161
161
  end
162
162
 
163
+ describe Rapns::Daemon::Connection, "when an SSL error occurs during write" do
164
+ before do
165
+ @connection = Rapns::Daemon::Connection.new("Connection 1")
166
+ @ssl_socket = mock("SSLSocket")
167
+ @connection.instance_variable_set("@ssl_socket", @ssl_socket)
168
+ @connection.stub(:connect_socket).and_return([mock("TCPSocket"), @ssl_socket])
169
+ @ssl_socket.stub(:write).and_raise(OpenSSL::SSL::SSLError)
170
+ @logger = mock("Logger", :error => nil)
171
+ Rapns::Daemon.stub(:logger).and_return(@logger)
172
+ @connection.stub(:sleep)
173
+ configuration = mock("Configuration", :host => "localhost", :port => 123)
174
+ Rapns::Daemon.stub(:configuration).and_return(configuration)
175
+ end
176
+
177
+ it "should log a error" do
178
+ Rapns::Daemon.logger.should_receive(:error).with("[Connection 1] Lost connection to localhost:123, reconnecting...")
179
+ begin
180
+ @connection.write(nil)
181
+ rescue Rapns::Daemon::ConnectionError
182
+ end
183
+ end
184
+
185
+ it "should retry to make a connection 3 times" do
186
+ @connection.should_receive(:connect_socket).exactly(3).times
187
+ begin
188
+ @connection.write(nil)
189
+ rescue Rapns::Daemon::ConnectionError
190
+ end
191
+ end
192
+
193
+ it "should raise a ConnectionError after 3 attempts at reconnecting" do
194
+ expect do
195
+ @connection.write(nil)
196
+ end.to raise_error(Rapns::Daemon::ConnectionError, "Connection 1 tried 3 times to reconnect but failed: #<OpenSSL::SSL::SSLError: OpenSSL::SSL::SSLError>")
197
+ end
198
+
199
+ it "should sleep 1 second before retrying the connection" do
200
+ @connection.should_receive(:sleep).with(1)
201
+ begin
202
+ @connection.write(nil)
203
+ rescue Rapns::Daemon::ConnectionError
204
+ end
205
+ end
206
+ end
207
+
163
208
  describe Rapns::Daemon::Connection, "when sending a notification" do
164
209
  before do
165
210
  @connection = Rapns::Daemon::Connection.new("Connection 1")
@@ -11,6 +11,18 @@ describe Rapns::Daemon::Feeder do
11
11
  Rapns::Daemon.stub(:configuration => mock("Configuration", :poll => 2))
12
12
  end
13
13
 
14
+ it "should reconnect to the database when daemonized" do
15
+ Rapns::Daemon::Feeder.stub(:loop)
16
+ ActiveRecord::Base.should_receive(:establish_connection)
17
+ Rapns::Daemon::Feeder.start(false)
18
+ end
19
+
20
+ it "should not reconnect to the database when running in the foreground" do
21
+ Rapns::Daemon::Feeder.stub(:loop)
22
+ ActiveRecord::Base.should_not_receive(:establish_connection)
23
+ Rapns::Daemon::Feeder.start(true)
24
+ end
25
+
14
26
  it "should enqueue an undelivered notification" do
15
27
  @notification.update_attributes!(:delivered => false)
16
28
  Rapns::Daemon.delivery_queue.should_receive(:push)
@@ -87,12 +87,6 @@ describe Rapns::Daemon, "when starting" do
87
87
  Rapns::Daemon.start("development", false)
88
88
  end
89
89
 
90
- it "should re-establish the connection to the database after being forked" do
91
- ActiveRecord::Base.should_receive(:establish_connection)
92
- Rapns::Daemon.stub(:daemonize)
93
- Rapns::Daemon.start("development", false)
94
- end
95
-
96
90
  it "should not fork a child process if the foreground option is true" do
97
91
  Rapns::Daemon.should_not_receive(:daemonize)
98
92
  Rapns::Daemon.start("development", true)
data/spec/spec_helper.rb CHANGED
@@ -21,6 +21,7 @@ require 'generators/templates/create_rapns_notifications'
21
21
  CreateRapnsNotifications.down rescue ActiveRecord::StatementInvalid
22
22
  CreateRapnsNotifications.up
23
23
 
24
+ require 'bundler'
24
25
  Bundler.require(:default)
25
26
 
26
27
  require 'shoulda'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rapns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-19 00:00:00.000000000Z
12
+ date: 2011-11-04 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Easy to use library for Apple's Push Notification Service with Rails
15
15
  3