rpush 2.0.0.rc1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0b35cfade151c01d8fc40a3c2c600294eacfbfe0
4
- data.tar.gz: e9da0a8194b7916eab23ad91b8158e48f506247f
3
+ metadata.gz: ddc24f899bd6b0f1eed7b50b40f16658290cec0e
4
+ data.tar.gz: bffc9e9752d7d53fa65dd6f6f1498e6a0f8d3ad4
5
5
  SHA512:
6
- metadata.gz: a5b4463d42d74de9fa8a68cdcc424b0990d7b6e34fae5a920875b042dba246a6901a4666ff2452d6ef37f1325232764537a41ed06b2f8dc318a84ef9ae3b368b
7
- data.tar.gz: 0fc2176665da6cabbc9459bd35d1cb7b782771195fd882122808ccad7e35f9303675da12bf0c2c7617c17b4a8bdc271742a8ead20b790711b2d25f6fb5fe0657
6
+ metadata.gz: 458e8e80010377460ad4968ac03ac448e435d221fb6385a7045a023e07114f69635d5f3f130c2c3bcf68b4ac35396d50a58de888154959275a326d5e680ab059
7
+ data.tar.gz: 85197cad6ac231852aeba0c1d405a9f2eaab8a4b60523cc7e9b223d6598436a53075dd6eadf7a7611f38e32a03173cbf04d1804445215e474f738de1af76ce61
@@ -1,4 +1,4 @@
1
- ## 2.0.0 (unreleased)
1
+ ## 2.0.0 (Sept 6, 2014)
2
2
  * Use APNs enhanced binary format version 2.
3
3
  * Support running multiple Rpush processes when using ActiveRecord and Redis.
4
4
  * APNs error detection is now performed asynchronously, 'check_for_errors' is therefore deprecated.
@@ -11,6 +11,7 @@
11
11
  * The 'batch_storage_updates' config option has been deprecated, storage backends will now always batch updates where appropriate.
12
12
  * The rpush process title updates with number of queued notifications and number of dispatchers.
13
13
  * Rpush::Apns::Feedback#app has been renamed to app_id and is now an Integer.
14
+ * An app is restarted when the HUP signal is received if its certificate or environment attribute changed.
14
15
 
15
16
  ## 1.0.0 (Feb 9, 2014)
16
17
  * Renamed to Rpush (from Rapns). Version number reset to 1.0.0.
data/README.md CHANGED
@@ -7,15 +7,23 @@
7
7
 
8
8
  ### Rpush. The push notification service for Ruby.
9
9
 
10
- * Supports:
10
+ * Supported services:
11
11
  * **Apple Push Notification Service**
12
12
  * **Google Cloud Messaging**
13
13
  * **Amazon Device Messaging**
14
- * **Windows Phone Push Notification Service**.
14
+ * **Windows Phone Push Notification Service**
15
+
16
+
17
+ * Supported storage backends:
18
+ * ActiveRecord
19
+ * Redis
20
+ * More coming soon!
21
+
22
+
15
23
  * Seamless Rails (3, 4) integration.
16
- * Scalable - choose the number of persistent connections for each app.
17
- * Designed for uptime - signal -HUP to add, update apps.
18
- * Run as a daemon or inside an [existing processs](https://github.com/rpush/rpush/wiki/Embedding-API).
24
+ * Scales vertically (threading) and horizontally (multiple processes).
25
+ * Designed for uptime - new apps are loaded automatically, signal `HUP` to update running apps.
26
+ * Run as a daemon or inside an [existing process](https://github.com/rpush/rpush/wiki/Embedding-API).
19
27
  * Use in a scheduler for low-workload deployments ([Push API](https://github.com/rpush/rpush/wiki/Push-API)).
20
28
  * Hooks for fine-grained instrumentation and error handling ([Reflection API](https://github.com/rpush/rpush/wiki/Reflection-API)).
21
29
  * Works with MRI, JRuby and Rubinius.
@@ -122,31 +130,40 @@ n.alert = "..."
122
130
  n.save!
123
131
  ```
124
132
 
125
- ### Starting Rpush
133
+ ### Running Rpush
126
134
 
127
- As a daemon (recommended):
135
+ It is recommended to run Rpush as a separate process in most cases, though embedding and manual modes are provided for low-workload environments.
136
+
137
+ #### As a daemon (recommended):
128
138
 
129
139
  cd /path/to/rails/app
130
140
  rpush <Rails environment> [options]
131
141
 
132
- Inside an existing process (see [Embedding API](https://github.com/rpush/rpush/wiki/Embedding-API)):
142
+ #### Embedded inside an existing process
133
143
 
134
144
  ```ruby
145
+ # Call this during startup of your application, for example, by adding it to the end of config/initializers/rpush.rb
135
146
  Rpush.embed
136
147
  ```
137
148
 
138
- In a scheduler (see [Push API](https://github.com/rpush/rpush/wiki/Push-API)):
149
+ See [Embedding API](https://github.com/rpush/rpush/wiki/Embedding-API) for more details.
150
+
151
+ #### Manually (in a scheduler)
139
152
 
140
153
  ```ruby
141
154
  Rpush.push
142
155
  Rpush.apns_feedback
143
156
  ```
144
157
 
158
+ See [Push API](https://github.com/rpush/rpush/wiki/Push-API) for more details.
159
+
160
+ ### Configuration
161
+
145
162
  See [Configuration](https://github.com/rpush/rpush/wiki/Configuration) for a list of options, or run `rpush --help`.
146
163
 
147
164
  ### Updating Rpush
148
165
 
149
- After updating you should run `rails g rpush` to check for any new migrations.
166
+ If you're using ActiveRecord, you should run `rails g rpush` after upgrading Rpush to check for any new migrations.
150
167
 
151
168
  ### Wiki
152
169
 
data/bin/rpush CHANGED
@@ -12,10 +12,10 @@ options = ARGV.options do |opts|
12
12
  opts.on('-f', '--foreground', 'Run in the foreground.') { config.foreground = true }
13
13
  opts.on('-P N', '--db-poll N', Integer, "Frequency in seconds to check for new notifications.") { |n| config.push_poll = n }
14
14
  opts.on('-F N', '--feedback-poll N', Integer, "Frequency in seconds to check for feedback.") { |n| config.feedback_poll = n }
15
- opts.on('-e', '--no-error-checks', 'Disable APNs error checking after notification delivery.') { config.check_for_errors = false }
15
+ opts.on('-e', '--no-error-checks', 'Disable APNs error checking after notification delivery.') { config.check_for_errors = false } # deprecated
16
16
  opts.on('-p PATH', '--pid-file PATH', String, 'Path to write PID file. Relative to Rails root unless absolute.') { |path| config.pid_file = path }
17
17
  opts.on('-b N', '--batch-size N', Integer, 'Storage backend notification batch size.') { |n| config.batch_size = n }
18
- opts.on('-B', '--[no-]batch-storage-updates', 'Perform storage updates in batches.') { |v| config.batch_storage_updates = v }
18
+ opts.on('-B', '--[no-]batch-storage-updates', 'Perform storage updates in batches.') { |v| config.batch_storage_updates = v } # deprecated
19
19
  opts.on('-v', '--version', 'Print the version.') do
20
20
  puts "rpush #{Rpush::VERSION}"
21
21
  exit
@@ -119,7 +119,6 @@ Rpush.reflect do |on|
119
119
  # on.adm_failed_to_recipient do |notification, registration_id, reason|
120
120
  # end
121
121
 
122
-
123
122
  # Called when an exception is raised.
124
123
  # on.error do |error|
125
124
  # end
@@ -7,7 +7,7 @@ class Rpush200Updates < ActiveRecord::Migration
7
7
  remove_index :rpush_notifications, name: :index_rpush_notifications_multi
8
8
  end
9
9
 
10
- add_index :rpush_notifications, [:processing, :delivered, :failed, :deliver_after], name: 'index_rpush_notifications_multi'
10
+ add_index :rpush_notifications, [:delivered, :failed], name: 'index_rpush_notifications_multi', where: 'NOT delivered AND NOT failed'
11
11
 
12
12
  rename_column :rpush_feedback, :app, :app_id
13
13
 
@@ -14,6 +14,8 @@ module Rpush
14
14
  attribute :client_id, :string
15
15
  attribute :client_secret, :string
16
16
 
17
+ index :name
18
+
17
19
  validates :name, presence: true
18
20
  validates_numericality_of :connections, greater_than: 0, only_integer: true
19
21
  end
@@ -51,8 +51,8 @@ module Rpush
51
51
  end
52
52
 
53
53
  def self.start
54
- SignalHandler.start
55
54
  Process.daemon if daemonize?
55
+ SignalHandler.start
56
56
  initialize_store
57
57
  write_pid_file
58
58
  Synchronizer.sync
@@ -4,7 +4,9 @@ module Rpush
4
4
  extend Reflectable
5
5
  include Reflectable
6
6
  include Loggable
7
+ extend Loggable
7
8
  include StringHelpers
9
+ extend StringHelpers
8
10
 
9
11
  @runners = {}
10
12
 
@@ -24,6 +26,7 @@ module Rpush
24
26
  def self.start_app(app)
25
27
  @runners[app.id] = new(app)
26
28
  @runners[app.id].start
29
+ log_info("[#{app.name}] Started, #{pluralize(app.connections, 'dispatcher')}.")
27
30
  rescue StandardError => e
28
31
  @runners.delete(app.id)
29
32
  Rpush.logger.error("[#{app.name}] Exception raised during startup. Notifications will not be delivered for this app.")
@@ -32,7 +35,15 @@ module Rpush
32
35
  end
33
36
 
34
37
  def self.stop_app(app_id)
35
- @runners.delete(app_id).stop
38
+ runner = @runners.delete(app_id)
39
+ if runner
40
+ runner.stop
41
+ log_info("[#{runner.app.name}] Stopped.")
42
+ end
43
+ end
44
+
45
+ def self.app_with_id(app_id)
46
+ @runners[app_id].app
36
47
  end
37
48
 
38
49
  def self.app_running?(app)
@@ -7,42 +7,47 @@ module Rpush
7
7
 
8
8
  def self.start
9
9
  return unless trap_signals?
10
- @shutting_down = false
10
+
11
11
  read_io, @write_io = IO.pipe
12
12
  start_handler(read_io)
13
13
  %w(INT TERM HUP USR2).each do |signal|
14
- Signal.trap(signal) { @write_io.write("#{Signal.list[signal]}\n") }
14
+ Signal.trap(signal) { @write_io.puts(signal) }
15
15
  end
16
16
  end
17
17
 
18
18
  def self.stop
19
- @write_io.write("shutdown\n") if @write_io
19
+ @write_io.puts('break') if @write_io
20
20
  @thread.join if @thread
21
21
  end
22
22
 
23
23
  def self.start_handler(read_io)
24
24
  @thread = Thread.new do
25
- loop do
26
- case read_io.readline.strip.to_i
27
- when Signal.list['HUP']
28
- Synchronizer.sync
29
- Feeder.wakeup
30
- when Signal.list['USR2']
31
- AppRunner.debug
32
- when Signal.list['INT'], Signal.list['TERM']
33
- Thread.new { handle_shutdown_signal }
34
- else
35
- break
25
+ while readable_io = IO.select([read_io]) # rubocop:disable AssignmentInCondition
26
+ signal = readable_io.first[0].gets.strip
27
+
28
+ begin
29
+ case signal
30
+ when 'HUP'
31
+ Synchronizer.sync
32
+ Feeder.wakeup
33
+ when 'USR2'
34
+ AppRunner.debug
35
+ when 'INT', 'TERM'
36
+ Thread.new { Rpush::Daemon.shutdown }
37
+ break
38
+ when 'break'
39
+ break
40
+ else
41
+ Rpush.logger.error("Unhandled signal: #{signal}")
42
+ end
43
+ rescue StandardError => e
44
+ Rpush.logger.error("Error raised when hndling signal '#{signal}'")
45
+ Rpush.logger.error(e)
36
46
  end
37
47
  end
38
48
  end
39
49
  end
40
50
 
41
- def self.handle_shutdown_signal
42
- @shutting_down = true
43
- Rpush::Daemon.shutdown
44
- end
45
-
46
51
  def self.trap_signals?
47
52
  !Rpush.config.embedded
48
53
  end
@@ -14,13 +14,19 @@ module Rpush
14
14
  end
15
15
 
16
16
  def self.sync_app(app)
17
- unless AppRunner.app_running?(app)
17
+ if !AppRunner.app_running?(app)
18
18
  AppRunner.start_app(app)
19
- log_info("[#{app.name}] Started, #{pluralize(app.connections, 'dispatcher')}.")
20
- return
19
+ elsif certificate_changed?(app)
20
+ log_info("[#{app.name}] Certificate changed, restarting...")
21
+ AppRunner.stop_app(app.id)
22
+ AppRunner.start_app(app)
23
+ elsif environment_changed?(app)
24
+ log_info("[#{app.name}] Environment changed, restarting...")
25
+ AppRunner.stop_app(app.id)
26
+ AppRunner.start_app(app)
27
+ else
28
+ sync_dispatcher_count(app)
21
29
  end
22
-
23
- sync_dispatcher_count(app)
24
30
  end
25
31
 
26
32
  def self.sync_dispatcher_count(app)
@@ -39,6 +45,16 @@ module Rpush
39
45
  num_dispatchers = AppRunner.num_dispatchers_for_app(app)
40
46
  log_info("[#{app.name}] #{start_stop_str} #{pluralize(diff.abs, 'dispatcher')}. #{num_dispatchers} running.")
41
47
  end
48
+
49
+ def self.certificate_changed?(app)
50
+ old_app = AppRunner.app_with_id(app.id)
51
+ app.certificate != old_app.certificate
52
+ end
53
+
54
+ def self.environment_changed?(app)
55
+ old_app = AppRunner.app_with_id(app.id)
56
+ app.environment != old_app.environment
57
+ end
42
58
  end
43
59
  end
44
60
  end
@@ -1,3 +1,3 @@
1
1
  module Rpush
2
- VERSION = '2.0.0.rc1'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -127,17 +127,16 @@ describe 'APNs' do
127
127
  notification1.delivered.should be_true
128
128
  end
129
129
 
130
- it 'marks notifications following the failed one as retryable' # do
131
- # # Such hacks. Set the poll frequency high enough that we'll only ever feed once.
132
- # Rpush.config.push_poll = 1_000_000
133
- #
134
- # notifications.each { |n| wait_for_notification_to_deliver(n) }
135
- # fail_notification(notification2)
136
- #
137
- # [notification3, notification4].each do |n|
138
- # wait_for_notification_to_retry(n)
139
- # end
140
- # end
130
+ it 'marks notifications following the failed one as retryable' do
131
+ Rpush.config.push_poll = 1_000_000
132
+
133
+ notifications.each { |n| wait_for_notification_to_deliver(n) }
134
+ fail_notification(notification2)
135
+
136
+ [notification3, notification4].each do |n|
137
+ wait_for_notification_to_retry(n)
138
+ end
139
+ end
141
140
 
142
141
  describe 'without an error response' do
143
142
  it 'marks all notifications as failed' do
@@ -16,6 +16,9 @@ describe 'Synchronization' do
16
16
  app.name = 'test'
17
17
  app.auth_key = 'abc123'
18
18
  app.connections = 2
19
+ app.certificate = TEST_CERT_WITH_PASSWORD
20
+ app.password = 'fubar'
21
+ app.environment = 'sandbox'
19
22
  app.save!
20
23
 
21
24
  Rpush.embed
@@ -43,4 +46,23 @@ describe 'Synchronization' do
43
46
  Rpush.sync
44
47
  Rpush::Daemon::AppRunner.app_running?(app).should be_false
45
48
  end
49
+
50
+ it 'restarts an app when the certificate is changed' do
51
+ app.certificate = TEST_CERT
52
+ app.password = nil
53
+ app.save!
54
+ Rpush.sync
55
+
56
+ running_app = Rpush::Daemon::AppRunner.app_with_id(app.id)
57
+ expect(running_app.certificate).to eql(TEST_CERT)
58
+ end
59
+
60
+ it 'restarts an app when the environment is changed' do
61
+ app.environment = 'production'
62
+ app.save!
63
+ Rpush.sync
64
+
65
+ running_app = Rpush::Daemon::AppRunner.app_with_id(app.id)
66
+ expect(running_app.environment).to eql('production')
67
+ end
46
68
  end
@@ -32,7 +32,7 @@ module Rpush
32
32
  end
33
33
 
34
34
  describe Rpush::Daemon::AppRunner, 'enqueue' do
35
- let(:app) { double(id: 1) }
35
+ let(:app) { double(id: 1, name: 'Test', connections: 1) }
36
36
  let(:notification) { double(app_id: 1) }
37
37
  let(:runner) { double(Rpush::Daemon::AppRunner, enqueue: nil, start: nil, stop: nil) }
38
38
  let(:logger) { double(Rpush::Logger, error: nil, info: nil) }
@@ -53,7 +53,7 @@ describe Rpush::Daemon::AppRunner, 'enqueue' do
53
53
 
54
54
  it 'starts the app if a runner does not exist' do
55
55
  notification = double(app_id: 3)
56
- new_app = double(Rpush::App, id: 3)
56
+ new_app = double(Rpush::App, id: 3, name: 'NewApp', connections: 1)
57
57
  Rpush::Daemon.store = double(app: new_app)
58
58
  Rpush::Daemon::AppRunner.enqueue([notification])
59
59
  Rpush::Daemon::AppRunner.app_running?(new_app).should be_true
@@ -69,4 +69,27 @@ describe Rpush::Daemon::SignalHandler do
69
69
  end
70
70
  end
71
71
  end
72
+
73
+ describe 'error handing' do
74
+ let(:error) { StandardError.new('test') }
75
+
76
+ before { Rpush.stub(logger: double(error: nil)) }
77
+
78
+ it 'logs errors received when handling a signal' do
79
+ Rpush::Daemon::Synchronizer.stub(:sync).and_raise(error)
80
+ expect(Rpush.logger).to receive(:error).with(error)
81
+ with_handler_start_stop do
82
+ signal_handler('HUP')
83
+ end
84
+ end
85
+
86
+ it 'does not interrupt processing of further errors' do
87
+ Rpush::Daemon::Synchronizer.stub(:sync).and_raise(error)
88
+ expect(Rpush::Daemon::AppRunner).to receive(:debug)
89
+ with_handler_start_stop do
90
+ signal_handler('HUP')
91
+ signal_handler('USR2')
92
+ end
93
+ end
94
+ end
72
95
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rpush
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.rc1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Leitch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-02 00:00:00.000000000 Z
11
+ date: 2014-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -249,9 +249,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
249
249
  version: '0'
250
250
  required_rubygems_version: !ruby/object:Gem::Requirement
251
251
  requirements:
252
- - - ">"
252
+ - - ">="
253
253
  - !ruby/object:Gem::Version
254
- version: 1.3.1
254
+ version: '0'
255
255
  requirements: []
256
256
  rubyforge_project:
257
257
  rubygems_version: 2.2.2