rpush 2.4.0 → 2.5.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 +12 -0
- data/README.md +11 -7
- data/lib/generators/templates/rpush.rb +8 -2
- data/lib/generators/templates/rpush_2_0_0_updates.rb +1 -1
- data/lib/rpush/cli.rb +61 -27
- data/lib/rpush/client/active_model.rb +3 -0
- data/lib/rpush/client/active_model/apns/notification.rb +1 -1
- data/lib/rpush/client/active_model/wns/app.rb +23 -0
- data/lib/rpush/client/active_model/wns/notification.rb +28 -0
- data/lib/rpush/client/active_model/wpns/notification.rb +11 -6
- data/lib/rpush/client/active_record.rb +3 -0
- data/lib/rpush/client/active_record/wns/app.rb +11 -0
- data/lib/rpush/client/active_record/wns/notification.rb +11 -0
- data/lib/rpush/client/mongoid.rb +3 -0
- data/lib/rpush/client/mongoid/apns/feedback.rb +3 -0
- data/lib/rpush/client/mongoid/notification.rb +6 -0
- data/lib/rpush/client/mongoid/wns/app.rb +14 -0
- data/lib/rpush/client/mongoid/wns/notification.rb +11 -0
- data/lib/rpush/client/redis.rb +3 -0
- data/lib/rpush/client/redis/wns/app.rb +14 -0
- data/lib/rpush/client/redis/wns/notification.rb +11 -0
- data/lib/rpush/configuration.rb +3 -7
- data/lib/rpush/daemon.rb +9 -0
- data/lib/rpush/daemon/apns/feedback_receiver.rb +5 -0
- data/lib/rpush/daemon/app_runner.rb +4 -5
- data/lib/rpush/daemon/dispatcher/apns_tcp.rb +47 -12
- data/lib/rpush/daemon/dispatcher_loop.rb +5 -0
- data/lib/rpush/daemon/feeder.rb +11 -0
- data/lib/rpush/daemon/interruptible_sleep.rb +8 -3
- data/lib/rpush/daemon/loggable.rb +4 -0
- data/lib/rpush/daemon/rpc.rb +9 -0
- data/lib/rpush/daemon/rpc/client.rb +27 -0
- data/lib/rpush/daemon/rpc/server.rb +82 -0
- data/lib/rpush/daemon/signal_handler.rb +7 -0
- data/lib/rpush/daemon/store/active_record.rb +17 -3
- data/lib/rpush/daemon/store/mongoid.rb +2 -2
- data/lib/rpush/daemon/store/redis.rb +2 -2
- data/lib/rpush/daemon/tcp_connection.rb +2 -2
- data/lib/rpush/daemon/wns.rb +9 -0
- data/lib/rpush/daemon/wns/delivery.rb +206 -0
- data/lib/rpush/embed.rb +15 -13
- data/lib/rpush/logger.rb +4 -0
- data/lib/rpush/plugin.rb +1 -1
- data/lib/rpush/push.rb +2 -11
- data/lib/rpush/reflection_collection.rb +15 -17
- data/lib/rpush/reflection_public_methods.rb +6 -4
- data/lib/rpush/version.rb +1 -1
- data/spec/functional/apns_spec.rb +1 -11
- data/spec/functional/cli_spec.rb +35 -0
- data/spec/functional_spec_helper.rb +11 -1
- data/spec/spec_helper.rb +4 -3
- data/spec/support/active_record_setup.rb +1 -1
- data/spec/unit/client/active_record/apns/notification_spec.rb +1 -1
- data/spec/unit/configuration_spec.rb +0 -7
- data/spec/unit/daemon/adm/delivery_spec.rb +2 -2
- data/spec/unit/daemon/app_runner_spec.rb +2 -3
- data/spec/unit/daemon/gcm/delivery_spec.rb +1 -1
- data/spec/unit/daemon/tcp_connection_spec.rb +1 -1
- data/spec/unit/daemon/wns/delivery_spec.rb +171 -0
- data/spec/unit/daemon/wpns/delivery_spec.rb +1 -1
- data/spec/unit/daemon_spec.rb +2 -0
- data/spec/unit/embed_spec.rb +4 -11
- data/spec/unit/logger_spec.rb +2 -2
- data/spec/unit/push_spec.rb +0 -7
- data/spec/unit_spec_helper.rb +1 -1
- metadata +20 -3
data/lib/rpush/client/redis.rb
CHANGED
@@ -30,6 +30,9 @@ require 'rpush/client/redis/adm/notification'
|
|
30
30
|
require 'rpush/client/redis/wpns/app'
|
31
31
|
require 'rpush/client/redis/wpns/notification'
|
32
32
|
|
33
|
+
require 'rpush/client/redis/wns/app'
|
34
|
+
require 'rpush/client/redis/wns/notification'
|
35
|
+
|
33
36
|
Modis.configure do |config|
|
34
37
|
config.namespace = :rpush
|
35
38
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Client
|
3
|
+
module Redis
|
4
|
+
module Wns
|
5
|
+
class App < Rpush::Client::Redis::App
|
6
|
+
include Rpush::Client::ActiveModel::Wns::App
|
7
|
+
|
8
|
+
attribute :access_token, :string
|
9
|
+
attribute :access_token_expiration, :timestamp
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/rpush/configuration.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'pathname'
|
2
|
+
require 'ostruct'
|
2
3
|
|
3
4
|
module Rpush
|
4
5
|
class << self
|
@@ -16,7 +17,7 @@ module Rpush
|
|
16
17
|
end
|
17
18
|
|
18
19
|
CURRENT_ATTRS = [:push_poll, :embedded, :pid_file, :batch_size, :push, :client, :logger, :log_file, :foreground, :log_level, :plugin, :apns]
|
19
|
-
DEPRECATED_ATTRS = [
|
20
|
+
DEPRECATED_ATTRS = []
|
20
21
|
CONFIG_ATTRS = CURRENT_ATTRS + DEPRECATED_ATTRS
|
21
22
|
|
22
23
|
class ConfigurationError < StandardError; end
|
@@ -97,11 +98,6 @@ module Rpush
|
|
97
98
|
Modis.redis_options = options if client == :redis
|
98
99
|
end
|
99
100
|
|
100
|
-
def feedback_poll=(frequency)
|
101
|
-
apns.feedback_receiver.frequency = frequency
|
102
|
-
end
|
103
|
-
deprecated(:feedback_poll=, '2.5.0', 'Please use apns.feedback_receiver.frequency= instead.')
|
104
|
-
|
105
101
|
def initialize_client
|
106
102
|
return if @client_initialized
|
107
103
|
raise ConfigurationError, 'Rpush.config.client is not set.' unless client
|
@@ -110,7 +106,7 @@ module Rpush
|
|
110
106
|
client_module = Rpush::Client.const_get(client.to_s.camelize)
|
111
107
|
Rpush.send(:include, client_module) unless Rpush.ancestors.include?(client_module)
|
112
108
|
|
113
|
-
[:Apns, :Gcm, :Wpns, :Adm].each do |service|
|
109
|
+
[:Apns, :Gcm, :Wpns, :Wns, :Adm].each do |service|
|
114
110
|
Rpush.const_set(service, client_module.const_get(service)) unless Rpush.const_defined?(service)
|
115
111
|
end
|
116
112
|
|
data/lib/rpush/daemon.rb
CHANGED
@@ -30,6 +30,10 @@ require 'rpush/daemon/ring_buffer'
|
|
30
30
|
require 'rpush/daemon/signal_handler'
|
31
31
|
require 'rpush/daemon/proc_title'
|
32
32
|
|
33
|
+
require 'rpush/daemon/rpc'
|
34
|
+
require 'rpush/daemon/rpc/server'
|
35
|
+
require 'rpush/daemon/rpc/client'
|
36
|
+
|
33
37
|
require 'rpush/daemon/store/interface'
|
34
38
|
|
35
39
|
require 'rpush/daemon/apns/delivery'
|
@@ -42,6 +46,9 @@ require 'rpush/daemon/gcm'
|
|
42
46
|
require 'rpush/daemon/wpns/delivery'
|
43
47
|
require 'rpush/daemon/wpns'
|
44
48
|
|
49
|
+
require 'rpush/daemon/wns/delivery'
|
50
|
+
require 'rpush/daemon/wns'
|
51
|
+
|
45
52
|
require 'rpush/daemon/adm/delivery'
|
46
53
|
require 'rpush/daemon/adm'
|
47
54
|
|
@@ -57,6 +64,7 @@ module Rpush
|
|
57
64
|
SignalHandler.start
|
58
65
|
common_init
|
59
66
|
Synchronizer.sync
|
67
|
+
Rpc::Server.start
|
60
68
|
|
61
69
|
# No further store connections will be made from this thread.
|
62
70
|
store.release_connection
|
@@ -81,6 +89,7 @@ module Rpush
|
|
81
89
|
Rpush.logger.info('Shutting down... ', true)
|
82
90
|
|
83
91
|
shutdown_lock.synchronize do
|
92
|
+
Rpc::Server.stop
|
84
93
|
Feeder.stop
|
85
94
|
AppRunner.stop
|
86
95
|
delete_pid_file
|
@@ -84,8 +84,8 @@ module Rpush
|
|
84
84
|
@runners[app.id].increment_dispatchers(num)
|
85
85
|
end
|
86
86
|
|
87
|
-
def self.
|
88
|
-
@runners.values.map(&:
|
87
|
+
def self.status
|
88
|
+
{ app_runners: @runners.values.map(&:status) }
|
89
89
|
end
|
90
90
|
|
91
91
|
attr_reader :app
|
@@ -140,7 +140,7 @@ module Rpush
|
|
140
140
|
num.times { @dispatcher_loops.push(new_dispatcher_loop) }
|
141
141
|
end
|
142
142
|
|
143
|
-
def
|
143
|
+
def status
|
144
144
|
dispatcher_details = {}
|
145
145
|
|
146
146
|
@dispatcher_loops.each_with_index do |dispatcher_loop, i|
|
@@ -151,8 +151,7 @@ module Rpush
|
|
151
151
|
}
|
152
152
|
end
|
153
153
|
|
154
|
-
|
155
|
-
log_info(JSON.pretty_generate(runner_details))
|
154
|
+
{ app_name: @app.name, dispatchers: dispatcher_details, queued: queue_size }
|
156
155
|
end
|
157
156
|
|
158
157
|
def num_dispatcher_loops
|
@@ -16,6 +16,7 @@ module Rpush
|
|
16
16
|
6 => 'Missing topic size',
|
17
17
|
7 => 'Missing payload size',
|
18
18
|
8 => 'Invalid token',
|
19
|
+
10 => 'APNs closed connection (possible maintenance)',
|
19
20
|
255 => 'None (unknown error)'
|
20
21
|
}
|
21
22
|
|
@@ -34,9 +35,21 @@ module Rpush
|
|
34
35
|
end
|
35
36
|
|
36
37
|
def cleanup
|
38
|
+
if Rpush.config.push
|
39
|
+
# In push mode only a single batch is sent, followed my immediate shutdown.
|
40
|
+
# Allow the error receiver time to handle any errors.
|
41
|
+
@reconnect_disabled = true
|
42
|
+
sleep 1
|
43
|
+
end
|
44
|
+
|
37
45
|
@stop_error_receiver = true
|
38
46
|
super
|
39
47
|
@error_receiver_thread.join if @error_receiver_thread
|
48
|
+
rescue StandardError => e
|
49
|
+
log_error(e)
|
50
|
+
reflect(:error, e)
|
51
|
+
ensure
|
52
|
+
@error_receiver_thread = nil
|
40
53
|
end
|
41
54
|
|
42
55
|
private
|
@@ -63,12 +76,12 @@ module Rpush
|
|
63
76
|
# On Linux, select returns nil from a dropped connection.
|
64
77
|
# On OS X, Errno::EBADF is raised following a Errno::EADDRNOTAVAIL from the write call.
|
65
78
|
return unless @connection.select(SELECT_TIMEOUT)
|
66
|
-
|
67
|
-
|
79
|
+
tuple = @connection.read(ERROR_TUPLE_BYTES)
|
80
|
+
rescue *TcpConnection::TCP_ERRORS
|
81
|
+
reconnect unless @stop_error_receiver
|
68
82
|
return
|
69
83
|
end
|
70
84
|
|
71
|
-
tuple = @connection.read(ERROR_TUPLE_BYTES)
|
72
85
|
@dispatch_mutex.synchronize { handle_error_response(tuple) }
|
73
86
|
rescue StandardError => e
|
74
87
|
log_error(e)
|
@@ -82,12 +95,23 @@ module Rpush
|
|
82
95
|
handle_disconnect
|
83
96
|
end
|
84
97
|
|
85
|
-
|
86
|
-
|
98
|
+
if Rpush.config.push
|
99
|
+
# Only attempt to handle a single error in Push mode.
|
100
|
+
@stop_error_receiver = true
|
101
|
+
return
|
102
|
+
end
|
103
|
+
|
104
|
+
reconnect
|
87
105
|
ensure
|
88
106
|
delivered_buffer.clear
|
89
107
|
end
|
90
108
|
|
109
|
+
def reconnect
|
110
|
+
return if @reconnect_disabled
|
111
|
+
log_error("Lost connection to #{@connection.host}:#{@connection.port}, reconnecting...")
|
112
|
+
@connection.reconnect_with_rescue
|
113
|
+
end
|
114
|
+
|
91
115
|
def handle_disconnect
|
92
116
|
log_error("The APNs disconnected before any notifications could be delivered. This usually indicates you are using an invalid certificate.") if delivered_buffer.size == 0
|
93
117
|
end
|
@@ -95,22 +119,33 @@ module Rpush
|
|
95
119
|
def handle_error(code, notification_id)
|
96
120
|
notification_id = Rpush::Daemon.store.translate_integer_notification_id(notification_id)
|
97
121
|
failed_pos = delivered_buffer.index(notification_id)
|
98
|
-
description =
|
99
|
-
log_error(
|
122
|
+
description = description_for_code(code)
|
123
|
+
log_error("Notification #{notification_id} failed with error: " + description)
|
100
124
|
Rpush::Daemon.store.mark_ids_failed([notification_id], code, description, Time.now)
|
101
125
|
reflect(:notification_id_failed, @app, notification_id, code, description)
|
102
126
|
|
103
127
|
if failed_pos
|
104
128
|
retry_ids = delivered_buffer[(failed_pos + 1)..-1]
|
105
|
-
|
106
|
-
now = Time.now
|
107
|
-
Rpush::Daemon.store.mark_ids_retryable(retry_ids, now)
|
108
|
-
retry_ids.each { |id| reflect(:notification_id_will_retry, @app, id, now) }
|
109
|
-
end
|
129
|
+
retry_notification_ids(retry_ids, notification_id)
|
110
130
|
elsif delivered_buffer.size > 0
|
111
131
|
log_error("Delivery sequence unknown for notifications following #{notification_id}.")
|
112
132
|
end
|
113
133
|
end
|
134
|
+
|
135
|
+
def description_for_code(code)
|
136
|
+
APNS_ERRORS[code.to_i] ? "#{APNS_ERRORS[code.to_i]} (#{code})" : "Unknown error code #{code.inspect}. Possible Rpush bug?"
|
137
|
+
end
|
138
|
+
|
139
|
+
def retry_notification_ids(ids, notification_id)
|
140
|
+
return if ids.size == 0
|
141
|
+
|
142
|
+
now = Time.now
|
143
|
+
Rpush::Daemon.store.mark_ids_retryable(ids, now)
|
144
|
+
notifications_str = 'Notification'
|
145
|
+
notifications_str += 's' if ids.size > 1
|
146
|
+
log_warn("#{notifications_str} #{ids.join(', ')} will be retried due to the failure of notification #{notification_id}.")
|
147
|
+
ids.each { |id| reflect(:notification_id_will_retry, @app, id, now) }
|
148
|
+
end
|
114
149
|
end
|
115
150
|
end
|
116
151
|
end
|
data/lib/rpush/daemon/feeder.rb
CHANGED
@@ -2,6 +2,7 @@ module Rpush
|
|
2
2
|
module Daemon
|
3
3
|
class Feeder
|
4
4
|
extend Reflectable
|
5
|
+
extend Loggable
|
5
6
|
|
6
7
|
def self.start(push_mode = false)
|
7
8
|
self.should_stop = false
|
@@ -12,12 +13,22 @@ module Rpush
|
|
12
13
|
end
|
13
14
|
|
14
15
|
@thread.join
|
16
|
+
rescue StandardError => e
|
17
|
+
log_error(e)
|
18
|
+
reflect(:error, e)
|
19
|
+
ensure
|
20
|
+
@thread = nil
|
15
21
|
end
|
16
22
|
|
17
23
|
def self.stop
|
18
24
|
self.should_stop = true
|
19
25
|
interruptible_sleeper.stop
|
20
26
|
@thread.join if @thread
|
27
|
+
rescue StandardError => e
|
28
|
+
log_error(e)
|
29
|
+
reflect(:error, e)
|
30
|
+
ensure
|
31
|
+
@thread = nil
|
21
32
|
end
|
22
33
|
|
23
34
|
def self.wakeup
|
@@ -1,17 +1,22 @@
|
|
1
|
-
require 'monitor'
|
2
|
-
|
3
1
|
module Rpush
|
4
2
|
module Daemon
|
5
3
|
class InterruptibleSleep
|
6
4
|
def sleep(duration)
|
7
5
|
@thread = Thread.new { Kernel.sleep duration }
|
8
6
|
Thread.pass
|
9
|
-
|
7
|
+
|
8
|
+
begin
|
9
|
+
@thread.join
|
10
|
+
rescue StandardError
|
11
|
+
@thread = nil
|
12
|
+
end
|
10
13
|
end
|
11
14
|
|
12
15
|
def stop
|
13
16
|
@thread.kill if @thread
|
14
17
|
rescue StandardError # rubocop:disable Lint/HandleExceptions
|
18
|
+
ensure
|
19
|
+
@thread = nil
|
15
20
|
end
|
16
21
|
end
|
17
22
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
module Rpc
|
4
|
+
class Client
|
5
|
+
def initialize(pid)
|
6
|
+
@socket = UNIXSocket.open(Rpc.socket_path(pid))
|
7
|
+
end
|
8
|
+
|
9
|
+
def status
|
10
|
+
call(:status)
|
11
|
+
end
|
12
|
+
|
13
|
+
def close
|
14
|
+
@socket.close
|
15
|
+
rescue StandardError # rubocop:disable Lint/HandleExceptions
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def call(cmd, args = {})
|
21
|
+
@socket.puts(JSON.dump([cmd, args]))
|
22
|
+
JSON.parse(@socket.gets)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
module Rpush
|
5
|
+
module Daemon
|
6
|
+
module Rpc
|
7
|
+
class Server
|
8
|
+
include Singleton
|
9
|
+
include Loggable
|
10
|
+
include Reflectable
|
11
|
+
|
12
|
+
def self.start
|
13
|
+
instance.start
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.stop
|
17
|
+
instance.stop
|
18
|
+
end
|
19
|
+
|
20
|
+
def start
|
21
|
+
@stop = false
|
22
|
+
|
23
|
+
@thread = Thread.new(UNIXServer.open(Rpc.socket_path)) do |server|
|
24
|
+
begin
|
25
|
+
loop do
|
26
|
+
socket = server.accept
|
27
|
+
break if @stop
|
28
|
+
read_loop(socket)
|
29
|
+
end
|
30
|
+
|
31
|
+
server.close
|
32
|
+
rescue StandardError => e
|
33
|
+
log_error(e)
|
34
|
+
ensure
|
35
|
+
File.unlink(Rpc.socket_path) if File.exist?(Rpc.socket_path)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def stop
|
41
|
+
@stop = true
|
42
|
+
UNIXSocket.new(Rpc.socket_path)
|
43
|
+
@thread.join if @thread
|
44
|
+
rescue StandardError => e
|
45
|
+
log_error(e)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def read_loop(socket)
|
51
|
+
loop do
|
52
|
+
line = socket.gets
|
53
|
+
break unless line
|
54
|
+
|
55
|
+
begin
|
56
|
+
cmd, args = JSON.load(line)
|
57
|
+
log_debug("[rpc:server] #{cmd.to_sym.inspect}, args: #{args.inspect}")
|
58
|
+
response = process(cmd, args)
|
59
|
+
socket.puts(JSON.dump(response))
|
60
|
+
rescue StandardError => e
|
61
|
+
log_error(e)
|
62
|
+
reflect(:error, e)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
socket.close
|
67
|
+
end
|
68
|
+
|
69
|
+
def process(cmd, args) # rubocop:disable Lint/UnusedMethodArgument
|
70
|
+
case cmd
|
71
|
+
when 'status'
|
72
|
+
status
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def status
|
77
|
+
Rpush::Daemon::AppRunner.status
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|