rpush 2.0.0.rc1 → 2.0.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.
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