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 +4 -4
- data/CHANGELOG.md +2 -1
- data/README.md +27 -10
- data/bin/rpush +2 -2
- data/lib/generators/templates/rpush.rb +0 -1
- data/lib/generators/templates/rpush_2_0_0_updates.rb +1 -1
- data/lib/rpush/client/redis/app.rb +2 -0
- data/lib/rpush/daemon.rb +1 -1
- data/lib/rpush/daemon/app_runner.rb +12 -1
- data/lib/rpush/daemon/signal_handler.rb +24 -19
- data/lib/rpush/daemon/synchronizer.rb +21 -5
- data/lib/rpush/version.rb +1 -1
- data/spec/functional/apns_spec.rb +10 -11
- data/spec/functional/synchronization_spec.rb +22 -0
- data/spec/unit/daemon/app_runner_spec.rb +2 -2
- data/spec/unit/daemon/signal_handler_spec.rb +23 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddc24f899bd6b0f1eed7b50b40f16658290cec0e
|
4
|
+
data.tar.gz: bffc9e9752d7d53fa65dd6f6f1498e6a0f8d3ad4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 458e8e80010377460ad4968ac03ac448e435d221fb6385a7045a023e07114f69635d5f3f130c2c3bcf68b4ac35396d50a58de888154959275a326d5e680ab059
|
7
|
+
data.tar.gz: 85197cad6ac231852aeba0c1d405a9f2eaab8a4b60523cc7e9b223d6598436a53075dd6eadf7a7611f38e32a03173cbf04d1804445215e474f738de1af76ce61
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
## 2.0.0 (
|
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
|
-
*
|
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
|
-
*
|
17
|
-
* Designed for uptime - signal
|
18
|
-
* Run as a daemon or inside an [existing
|
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
|
-
###
|
133
|
+
### Running Rpush
|
126
134
|
|
127
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
@@ -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, [:
|
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
|
|
data/lib/rpush/daemon.rb
CHANGED
@@ -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)
|
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
|
-
|
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.
|
14
|
+
Signal.trap(signal) { @write_io.puts(signal) }
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.stop
|
19
|
-
@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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
17
|
+
if !AppRunner.app_running?(app)
|
18
18
|
AppRunner.start_app(app)
|
19
|
-
|
20
|
-
|
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
|
data/lib/rpush/version.rb
CHANGED
@@ -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'
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
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-
|
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:
|
254
|
+
version: '0'
|
255
255
|
requirements: []
|
256
256
|
rubyforge_project:
|
257
257
|
rubygems_version: 2.2.2
|