rpush 2.2.0-java → 2.3.0-java
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +17 -9
- data/lib/generators/rpush_migration_generator.rb +1 -1
- data/lib/rpush.rb +4 -1
- data/lib/rpush/apns_feedback.rb +1 -1
- data/lib/rpush/cli.rb +24 -21
- data/lib/rpush/client/active_model/apns/app.rb +1 -1
- data/lib/rpush/client/active_model/apns/notification.rb +2 -2
- data/lib/rpush/client/active_model/wpns/notification.rb +7 -1
- data/lib/rpush/configuration.rb +32 -22
- data/lib/rpush/daemon.rb +18 -9
- data/lib/rpush/daemon/adm/delivery.rb +4 -1
- data/lib/rpush/daemon/apns/delivery.rb +3 -0
- data/lib/rpush/daemon/apns/feedback_receiver.rb +6 -2
- data/lib/rpush/daemon/app_runner.rb +11 -16
- data/lib/rpush/daemon/batch.rb +9 -0
- data/lib/rpush/daemon/delivery.rb +10 -2
- data/lib/rpush/daemon/delivery_error.rb +3 -3
- data/lib/rpush/daemon/dispatcher/apns_tcp.rb +11 -11
- data/lib/rpush/daemon/dispatcher/tcp.rb +2 -10
- data/lib/rpush/daemon/gcm/delivery.rb +13 -7
- data/lib/rpush/daemon/signal_handler.rb +5 -3
- data/lib/rpush/daemon/store/active_record.rb +14 -0
- data/lib/rpush/daemon/store/interface.rb +2 -1
- data/lib/rpush/daemon/store/redis.rb +3 -0
- data/lib/rpush/daemon/tcp_connection.rb +47 -17
- data/lib/rpush/daemon/wpns/delivery.rb +19 -10
- data/lib/rpush/logger.rb +30 -12
- data/lib/rpush/plugin.rb +44 -0
- data/lib/rpush/push.rb +1 -1
- data/lib/rpush/reflectable.rb +13 -0
- data/lib/rpush/{reflection.rb → reflection_collection.rb} +1 -9
- data/lib/rpush/reflection_public_methods.rb +9 -0
- data/lib/rpush/version.rb +1 -1
- data/lib/tasks/test.rake +6 -4
- data/spec/functional/adm_spec.rb +12 -0
- data/spec/functional/apns_spec.rb +61 -42
- data/spec/functional/gcm_spec.rb +9 -0
- data/spec/functional/retry_spec.rb +1 -1
- data/spec/functional/wpns_spec.rb +44 -11
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/apns_feedback_spec.rb +2 -2
- data/spec/unit/client/active_record/apns/app_spec.rb +2 -2
- data/spec/unit/client/active_record/apns/notification_spec.rb +1 -2
- data/spec/unit/client/active_record/wpns/notification_spec.rb +9 -3
- data/spec/unit/configuration_spec.rb +1 -0
- data/spec/unit/daemon/adm/delivery_spec.rb +0 -1
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +0 -1
- data/spec/unit/daemon/app_runner_spec.rb +14 -9
- data/spec/unit/daemon/delivery_spec.rb +0 -1
- data/spec/unit/daemon/dispatcher/tcp_spec.rb +0 -7
- data/spec/unit/daemon/gcm/delivery_spec.rb +1 -1
- data/spec/unit/daemon/signal_handler_spec.rb +5 -1
- data/spec/unit/daemon/store/active_record_spec.rb +1 -1
- data/spec/unit/daemon/tcp_connection_spec.rb +18 -18
- data/spec/unit/daemon/wpns/delivery_spec.rb +1 -1
- data/spec/unit/daemon_spec.rb +10 -2
- data/spec/unit/logger_spec.rb +0 -1
- data/spec/unit/plugin_spec.rb +36 -0
- data/spec/unit/push_spec.rb +2 -2
- data/spec/unit/{daemon/reflectable_spec.rb → reflectable_spec.rb} +6 -6
- data/spec/unit/{reflection_spec.rb → reflection_collection_spec.rb} +4 -8
- metadata +34 -36
- data/lib/rpush/daemon/reflectable.rb +0 -11
- data/spec/integration/rpush_spec.rb +0 -13
- data/spec/integration/support/gcm_success_response.json +0 -1
- data/spec/support/install.sh +0 -68
@@ -40,6 +40,9 @@ module Rpush
|
|
40
40
|
handle_rate_limited(error)
|
41
41
|
rescue Rpush::RetryableError => error
|
42
42
|
handle_retryable(error)
|
43
|
+
rescue SocketError => error
|
44
|
+
mark_retryable(@notification, Time.now + 10.seconds, error)
|
45
|
+
raise
|
43
46
|
rescue StandardError => error
|
44
47
|
mark_failed(error)
|
45
48
|
raise
|
@@ -176,7 +179,7 @@ module Rpush
|
|
176
179
|
end
|
177
180
|
|
178
181
|
def retry_message
|
179
|
-
"Notification #{@notification.id} will be retired after #{@notification.deliver_after.strftime(
|
182
|
+
"Notification #{@notification.id} will be retired after #{@notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} (retry #{@notification.retries})."
|
180
183
|
end
|
181
184
|
|
182
185
|
def do_post(registration_id)
|
@@ -12,6 +12,9 @@ module Rpush
|
|
12
12
|
@connection.write(batch_to_binary)
|
13
13
|
mark_batch_delivered
|
14
14
|
describe_deliveries
|
15
|
+
rescue Rpush::Daemon::TcpConnectionError => error
|
16
|
+
mark_batch_retryable(Time.now + 10.seconds, error)
|
17
|
+
raise
|
15
18
|
rescue StandardError => error
|
16
19
|
mark_batch_failed(error)
|
17
20
|
raise
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
module Rpush
|
2
4
|
module Daemon
|
3
5
|
module Apns
|
@@ -22,7 +24,7 @@ module Rpush
|
|
22
24
|
|
23
25
|
def start
|
24
26
|
return if Rpush.config.push
|
25
|
-
|
27
|
+
Rpush.logger.info("[#{@app.name}] Starting feedback receiver... ", true)
|
26
28
|
@interruptible_sleep.start
|
27
29
|
|
28
30
|
@thread = Thread.new do
|
@@ -34,6 +36,8 @@ module Rpush
|
|
34
36
|
|
35
37
|
Rpush::Daemon.store.release_connection
|
36
38
|
end
|
39
|
+
|
40
|
+
puts ANSI.green { '✔' } if Rpush.config.foreground
|
37
41
|
end
|
38
42
|
|
39
43
|
def stop
|
@@ -70,7 +74,7 @@ module Rpush
|
|
70
74
|
end
|
71
75
|
|
72
76
|
def create_feedback(failed_at, device_token)
|
73
|
-
formatted_failed_at = failed_at.strftime(
|
77
|
+
formatted_failed_at = failed_at.strftime('%Y-%m-%d %H:%M:%S UTC')
|
74
78
|
log_info("[FeedbackReceiver] Delivery failed at #{formatted_failed_at} for #{device_token}.")
|
75
79
|
|
76
80
|
feedback = Rpush::Daemon.store.create_apns_feedback(failed_at, device_token, @app)
|
@@ -3,8 +3,6 @@
|
|
3
3
|
module Rpush
|
4
4
|
module Daemon
|
5
5
|
class AppRunner
|
6
|
-
extend Term::ANSIColor
|
7
|
-
|
8
6
|
extend Reflectable
|
9
7
|
include Reflectable
|
10
8
|
include Loggable
|
@@ -29,9 +27,10 @@ module Rpush
|
|
29
27
|
|
30
28
|
def self.start_app(app)
|
31
29
|
Rpush.logger.info("[#{app.name}] Starting #{pluralize(app.connections, 'dispatcher')}... ", true)
|
32
|
-
@runners[app.id] = new(app)
|
33
|
-
|
34
|
-
puts green
|
30
|
+
runner = @runners[app.id] = new(app)
|
31
|
+
runner.start_dispatchers
|
32
|
+
puts ANSI.green { '✔' } if Rpush.config.foreground
|
33
|
+
runner.start_loops
|
35
34
|
rescue StandardError => e
|
36
35
|
@runners.delete(app.id)
|
37
36
|
Rpush.logger.error("[#{app.name}] Exception raised during startup. Notifications will not be delivered for this app.")
|
@@ -90,6 +89,7 @@ module Rpush
|
|
90
89
|
end
|
91
90
|
|
92
91
|
attr_reader :app
|
92
|
+
delegate :size, to: :queue, prefix: true
|
93
93
|
|
94
94
|
def initialize(app)
|
95
95
|
@app = app
|
@@ -97,9 +97,13 @@ module Rpush
|
|
97
97
|
@dispatcher_loops = []
|
98
98
|
end
|
99
99
|
|
100
|
-
def
|
100
|
+
def start_dispatchers
|
101
101
|
app.connections.times { @dispatcher_loops.push(new_dispatcher_loop) }
|
102
|
-
|
102
|
+
end
|
103
|
+
|
104
|
+
def start_loops
|
105
|
+
@loops = service.loop_instances(@app)
|
106
|
+
@loops.map(&:start)
|
103
107
|
end
|
104
108
|
|
105
109
|
def stop
|
@@ -151,21 +155,12 @@ module Rpush
|
|
151
155
|
log_info(JSON.pretty_generate(runner_details))
|
152
156
|
end
|
153
157
|
|
154
|
-
def queue_size
|
155
|
-
queue.size
|
156
|
-
end
|
157
|
-
|
158
158
|
def num_dispatcher_loops
|
159
159
|
@dispatcher_loops.size
|
160
160
|
end
|
161
161
|
|
162
162
|
private
|
163
163
|
|
164
|
-
def start_loops
|
165
|
-
@loops = service.loop_instances(@app)
|
166
|
-
@loops.map(&:start)
|
167
|
-
end
|
168
|
-
|
169
164
|
def stop_loops
|
170
165
|
@loops.map(&:stop)
|
171
166
|
@loops = []
|
data/lib/rpush/daemon/batch.rb
CHANGED
@@ -34,6 +34,15 @@ module Rpush
|
|
34
34
|
Rpush::Daemon.store.mark_retryable(notification, deliver_after, persist: false)
|
35
35
|
end
|
36
36
|
|
37
|
+
def mark_all_retryable(deliver_after)
|
38
|
+
@mutex.synchronize do
|
39
|
+
@retryable[deliver_after] = @notifications
|
40
|
+
end
|
41
|
+
each_notification do |notification|
|
42
|
+
Rpush::Daemon.store.mark_retryable(notification, deliver_after, persist: false)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
37
46
|
def mark_delivered(notification)
|
38
47
|
@mutex.synchronize do
|
39
48
|
@delivered << notification
|
@@ -4,10 +4,13 @@ module Rpush
|
|
4
4
|
include Reflectable
|
5
5
|
include Loggable
|
6
6
|
|
7
|
-
def mark_retryable(notification, deliver_after)
|
7
|
+
def mark_retryable(notification, deliver_after, error = nil)
|
8
8
|
if notification.fail_after && notification.fail_after < Time.now
|
9
|
-
@batch.mark_failed(notification, nil, "Notification failed to be delivered before #{notification.fail_after.strftime(
|
9
|
+
@batch.mark_failed(notification, nil, "Notification failed to be delivered before #{notification.fail_after.strftime('%Y-%m-%d %H:%M:%S')}.")
|
10
10
|
else
|
11
|
+
if error
|
12
|
+
log_warn("Will retry notification #{notification.id} after #{deliver_after.strftime('%Y-%m-%d %H:%M:%S')} due to error (#{error.class.name}, #{error.message})")
|
13
|
+
end
|
11
14
|
@batch.mark_retryable(notification, deliver_after)
|
12
15
|
end
|
13
16
|
end
|
@@ -16,6 +19,11 @@ module Rpush
|
|
16
19
|
mark_retryable(notification, Time.now + 2**(notification.retries + 1))
|
17
20
|
end
|
18
21
|
|
22
|
+
def mark_batch_retryable(deliver_after, error)
|
23
|
+
log_warn("Will retry #{@batch.notifications.size} notifications after #{deliver_after.strftime('%Y-%m-%d %H:%M:%S')} due to error (#{error.class.name}, #{error.message})")
|
24
|
+
@batch.mark_all_retryable(deliver_after)
|
25
|
+
end
|
26
|
+
|
19
27
|
def mark_delivered
|
20
28
|
@batch.mark_delivered(@notification)
|
21
29
|
end
|
@@ -19,9 +19,9 @@ module Rpush
|
|
19
19
|
|
20
20
|
def ==(other)
|
21
21
|
other.is_a?(DeliveryError) && \
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
other.code == code && \
|
23
|
+
other.notification_id == notification_id && \
|
24
|
+
other.to_s == to_s
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -28,7 +28,7 @@ module Rpush
|
|
28
28
|
|
29
29
|
def dispatch(payload)
|
30
30
|
@dispatch_mutex.synchronize do
|
31
|
-
@delivery_class.new(@app, connection, payload.batch).perform
|
31
|
+
@delivery_class.new(@app, @connection, payload.batch).perform
|
32
32
|
record_batch(payload.batch)
|
33
33
|
end
|
34
34
|
end
|
@@ -60,13 +60,15 @@ module Rpush
|
|
60
60
|
|
61
61
|
def check_for_error
|
62
62
|
begin
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
# On Linux, select returns nil from a dropped connection.
|
64
|
+
# On OS X, Errno::EBADF is raised following a Errno::EADDRNOTAVAIL from the write call.
|
65
|
+
return unless @connection.select(SELECT_TIMEOUT)
|
66
|
+
rescue SystemCallError
|
67
|
+
# Connection closed.
|
66
68
|
return
|
67
69
|
end
|
68
70
|
|
69
|
-
tuple = connection.read(ERROR_TUPLE_BYTES)
|
71
|
+
tuple = @connection.read(ERROR_TUPLE_BYTES)
|
70
72
|
@dispatch_mutex.synchronize { handle_error_response(tuple) }
|
71
73
|
end
|
72
74
|
|
@@ -78,22 +80,20 @@ module Rpush
|
|
78
80
|
handle_disconnect
|
79
81
|
end
|
80
82
|
|
81
|
-
log_error(
|
82
|
-
connection.
|
83
|
+
log_error("Lost connection to #{@connection.host}:#{@connection.port}, reconnecting...")
|
84
|
+
@connection.reconnect_with_rescue
|
83
85
|
ensure
|
84
86
|
delivered_buffer.clear
|
85
87
|
end
|
86
88
|
|
87
89
|
def handle_disconnect
|
88
|
-
log_error(
|
89
|
-
reason = 'The APNs disconnected without returning an error. This may indicate you are using an invalid certificate.'
|
90
|
-
Rpush::Daemon.store.mark_ids_failed(delivered_buffer, nil, reason, Time.now)
|
91
|
-
delivered_buffer.each { |id| reflect(:notification_id_failed, @app, id, nil, reason) }
|
90
|
+
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
|
92
91
|
end
|
93
92
|
|
94
93
|
def handle_error(code, notification_id)
|
95
94
|
failed_pos = delivered_buffer.index(notification_id)
|
96
95
|
description = APNS_ERRORS[code.to_i] || "Unknown error code #{code.inspect}. Possible Rpush bug?"
|
96
|
+
log_error(description + " (#{code})")
|
97
97
|
Rpush::Daemon.store.mark_ids_failed([notification_id], code, description, Time.now)
|
98
98
|
reflect(:notification_id_failed, @app, notification_id, code, description)
|
99
99
|
|
@@ -6,24 +6,16 @@ module Rpush
|
|
6
6
|
@app = app
|
7
7
|
@delivery_class = delivery_class
|
8
8
|
@host, @port = options[:host].call(@app)
|
9
|
+
@connection = Rpush::Daemon::TcpConnection.new(@app, @host, @port)
|
9
10
|
end
|
10
11
|
|
11
12
|
def dispatch(payload)
|
12
|
-
@delivery_class.new(@app, connection, payload.notification, payload.batch).perform
|
13
|
+
@delivery_class.new(@app, @connection, payload.notification, payload.batch).perform
|
13
14
|
end
|
14
15
|
|
15
16
|
def cleanup
|
16
17
|
@connection.close if @connection
|
17
18
|
end
|
18
|
-
|
19
|
-
protected
|
20
|
-
|
21
|
-
def connection
|
22
|
-
return @connection if defined? @connection
|
23
|
-
connection = Rpush::Daemon::TcpConnection.new(@app, @host, @port)
|
24
|
-
connection.connect
|
25
|
-
@connection = connection
|
26
|
-
end
|
27
19
|
end
|
28
20
|
end
|
29
21
|
end
|
@@ -5,7 +5,7 @@ module Rpush
|
|
5
5
|
class Delivery < Rpush::Daemon::Delivery
|
6
6
|
include MultiJsonHelper
|
7
7
|
|
8
|
-
host =
|
8
|
+
host = 'https://android.googleapis.com'
|
9
9
|
GCM_URI = URI.parse("#{host}/gcm/send")
|
10
10
|
UNAVAILABLE_STATES = %w(Unavailable InternalServerError)
|
11
11
|
INVALID_REGISTRATION_ID_STATES = %w(InvalidRegistration MismatchSenderId NotRegistered InvalidPackageName)
|
@@ -19,6 +19,9 @@ module Rpush
|
|
19
19
|
|
20
20
|
def perform
|
21
21
|
handle_response(do_post)
|
22
|
+
rescue SocketError => error
|
23
|
+
mark_retryable(@notification, Time.now + 10.seconds, error)
|
24
|
+
raise
|
22
25
|
rescue StandardError => error
|
23
26
|
mark_failed(error)
|
24
27
|
raise
|
@@ -47,7 +50,6 @@ module Rpush
|
|
47
50
|
|
48
51
|
def ok(response)
|
49
52
|
results = process_response(response)
|
50
|
-
|
51
53
|
handle_successes(results.successes)
|
52
54
|
|
53
55
|
if results.failures.any?
|
@@ -136,7 +138,7 @@ module Rpush
|
|
136
138
|
end
|
137
139
|
|
138
140
|
def retry_message
|
139
|
-
"Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime(
|
141
|
+
"Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} (retry #{@notification.retries})."
|
140
142
|
end
|
141
143
|
|
142
144
|
def do_post
|
@@ -155,7 +157,7 @@ module Rpush
|
|
155
157
|
@registration_ids = registration_ids
|
156
158
|
end
|
157
159
|
|
158
|
-
def process(failure_partitions = {})
|
160
|
+
def process(failure_partitions = {}) # rubocop:disable Metrics/AbcSize
|
159
161
|
@successes = []
|
160
162
|
@failures = Failures.new
|
161
163
|
failure_partitions.each_key do |category|
|
@@ -178,13 +180,13 @@ module Rpush
|
|
178
180
|
end
|
179
181
|
end
|
180
182
|
end
|
181
|
-
failures.
|
183
|
+
failures.all_failed = failures.count == @registration_ids.count
|
182
184
|
end
|
183
185
|
end
|
184
186
|
|
185
187
|
class Failures < Hash
|
186
188
|
include Enumerable
|
187
|
-
attr_writer :
|
189
|
+
attr_writer :all_failed, :description
|
188
190
|
|
189
191
|
def initialize
|
190
192
|
super[:all] = []
|
@@ -202,10 +204,14 @@ module Rpush
|
|
202
204
|
@description ||= describe
|
203
205
|
end
|
204
206
|
|
207
|
+
def any?
|
208
|
+
self[:all].any?
|
209
|
+
end
|
210
|
+
|
205
211
|
private
|
206
212
|
|
207
213
|
def describe
|
208
|
-
if @
|
214
|
+
if @all_failed
|
209
215
|
error_description = "Failed to deliver to all recipients."
|
210
216
|
else
|
211
217
|
index_list = map { |item| item[:index] }
|
@@ -40,7 +40,7 @@ module Rpush
|
|
40
40
|
Rpush.logger.error("Unhandled signal: #{signal}")
|
41
41
|
end
|
42
42
|
rescue StandardError => e
|
43
|
-
Rpush.logger.error("Error raised when
|
43
|
+
Rpush.logger.error("Error raised when handling signal '#{signal}'")
|
44
44
|
Rpush.logger.error(e)
|
45
45
|
end
|
46
46
|
end
|
@@ -48,13 +48,15 @@ module Rpush
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def self.handle_hup
|
51
|
-
Rpush.logger.
|
51
|
+
Rpush.logger.reopen
|
52
|
+
Rpush.logger.info('Received HUP signal.')
|
53
|
+
Rpush::Daemon.store.reopen_log
|
52
54
|
Synchronizer.sync
|
53
55
|
Feeder.wakeup
|
54
56
|
end
|
55
57
|
|
56
58
|
def self.handle_usr2
|
57
|
-
Rpush.logger.info(
|
59
|
+
Rpush.logger.info('Received USR2 signal.')
|
58
60
|
AppRunner.debug
|
59
61
|
end
|
60
62
|
|
@@ -10,6 +10,14 @@ module Rpush
|
|
10
10
|
|
11
11
|
DEFAULT_MARK_OPTIONS = { persist: true }
|
12
12
|
|
13
|
+
def initialize
|
14
|
+
reopen_log
|
15
|
+
end
|
16
|
+
|
17
|
+
def reopen_log
|
18
|
+
::ActiveRecord::Base.logger = Rpush.logger.internal_logger
|
19
|
+
end
|
20
|
+
|
13
21
|
def app(id)
|
14
22
|
Rpush::Client::ActiveRecord::App.find(id)
|
15
23
|
end
|
@@ -53,6 +61,8 @@ module Rpush
|
|
53
61
|
end
|
54
62
|
|
55
63
|
def mark_ids_retryable(ids, deliver_after)
|
64
|
+
return if ids.empty?
|
65
|
+
|
56
66
|
with_database_reconnect_and_retry do
|
57
67
|
Rpush::Client::ActiveRecord::Notification.where(id: ids).update_all(['processing = ?, delivered = ?, delivered_at = ?, failed = ?, failed_at = ?, retries = retries + 1, deliver_after = ?', false, false, nil, false, nil, deliver_after])
|
58
68
|
end
|
@@ -72,6 +82,8 @@ module Rpush
|
|
72
82
|
end
|
73
83
|
|
74
84
|
def mark_batch_delivered(notifications)
|
85
|
+
return if notifications.empty?
|
86
|
+
|
75
87
|
now = Time.now
|
76
88
|
ids = []
|
77
89
|
notifications.each do |n|
|
@@ -111,6 +123,8 @@ module Rpush
|
|
111
123
|
end
|
112
124
|
|
113
125
|
def mark_ids_failed(ids, code, description, time)
|
126
|
+
return if ids.empty?
|
127
|
+
|
114
128
|
with_database_reconnect_and_retry do
|
115
129
|
Rpush::Client::ActiveRecord::Notification.where(id: ids).update_all(['processing = ?, delivered = ?, delivered_at = NULL, failed = ?, failed_at = ?, error_code = ?, error_description = ?', false, false, true, time, code, description])
|
116
130
|
end
|
@@ -7,7 +7,8 @@ module Rpush
|
|
7
7
|
:mark_failed, :mark_batch_failed, :create_apns_feedback,
|
8
8
|
:create_gcm_notification, :create_adm_notification, :update_app,
|
9
9
|
:update_notification, :release_connection,
|
10
|
-
:all_apps, :app, :mark_ids_failed, :mark_ids_retryable
|
10
|
+
:all_apps, :app, :mark_ids_failed, :mark_ids_retryable,
|
11
|
+
:reopen_log]
|
11
12
|
|
12
13
|
def self.check(klass)
|
13
14
|
missing = PUBLIC_METHODS - klass.instance_methods.map(&:to_sym)
|
@@ -6,7 +6,14 @@ module Rpush
|
|
6
6
|
include Reflectable
|
7
7
|
include Loggable
|
8
8
|
|
9
|
-
|
9
|
+
OSX_TCP_KEEPALIVE = 0x10 # Defined in <netinet/tcp.h>
|
10
|
+
KEEPALIVE_INTERVAL = 5
|
11
|
+
KEEPALIVE_IDLE = 5
|
12
|
+
KEEPALIVE_MAX_FAIL_PROBES = 1
|
13
|
+
TCP_ERRORS = [SystemCallError, OpenSSL::OpenSSLError, IOError]
|
14
|
+
|
15
|
+
attr_accessor :last_touch
|
16
|
+
attr_reader :host, :port
|
10
17
|
|
11
18
|
def self.idle_period
|
12
19
|
30.minutes
|
@@ -18,12 +25,14 @@ module Rpush
|
|
18
25
|
@port = port
|
19
26
|
@certificate = app.certificate
|
20
27
|
@password = app.password
|
21
|
-
|
28
|
+
@connected = false
|
29
|
+
touch
|
22
30
|
end
|
23
31
|
|
24
32
|
def connect
|
25
33
|
@ssl_context = setup_ssl_context
|
26
34
|
@tcp_socket, @ssl_socket = connect_socket
|
35
|
+
@connected = true
|
27
36
|
end
|
28
37
|
|
29
38
|
def close
|
@@ -33,38 +42,45 @@ module Rpush
|
|
33
42
|
end
|
34
43
|
|
35
44
|
def read(num_bytes)
|
36
|
-
@ssl_socket.read(num_bytes)
|
45
|
+
@ssl_socket.read(num_bytes) if @ssl_socket
|
37
46
|
end
|
38
47
|
|
39
48
|
def select(timeout)
|
40
|
-
IO.select([@ssl_socket], nil, nil, timeout)
|
49
|
+
IO.select([@ssl_socket], nil, nil, timeout) if @ssl_socket
|
41
50
|
end
|
42
51
|
|
43
52
|
def write(data)
|
53
|
+
connect unless @connected
|
44
54
|
reconnect_idle if idle_period_exceeded?
|
45
55
|
|
46
56
|
retry_count = 0
|
47
57
|
|
48
58
|
begin
|
49
59
|
write_data(data)
|
50
|
-
rescue
|
60
|
+
rescue *TCP_ERRORS => e
|
51
61
|
retry_count += 1
|
52
62
|
|
53
63
|
if retry_count == 1
|
54
|
-
log_error("Lost connection to #{@host}:#{@port} (#{e.class.name}), reconnecting...")
|
64
|
+
log_error("Lost connection to #{@host}:#{@port} (#{e.class.name}, #{e.message}), reconnecting...")
|
55
65
|
reflect(:tcp_connection_lost, @app, e)
|
56
66
|
end
|
57
67
|
|
58
68
|
if retry_count <= 3
|
59
|
-
|
69
|
+
reconnect_with_rescue
|
60
70
|
sleep 1
|
61
71
|
retry
|
62
72
|
else
|
63
|
-
raise TcpConnectionError, "#{@app.name} tried #{retry_count - 1} times to reconnect but failed (#{e.class.name})."
|
73
|
+
raise TcpConnectionError, "#{@app.name} tried #{retry_count - 1} times to reconnect but failed (#{e.class.name}, #{e.message})."
|
64
74
|
end
|
65
75
|
end
|
66
76
|
end
|
67
77
|
|
78
|
+
def reconnect_with_rescue
|
79
|
+
reconnect
|
80
|
+
rescue StandardError => e
|
81
|
+
log_error(e)
|
82
|
+
end
|
83
|
+
|
68
84
|
def reconnect
|
69
85
|
close
|
70
86
|
@tcp_socket, @ssl_socket = connect_socket
|
@@ -78,17 +94,17 @@ module Rpush
|
|
78
94
|
end
|
79
95
|
|
80
96
|
def idle_period_exceeded?
|
81
|
-
Time.now -
|
97
|
+
Time.now - last_touch > self.class.idle_period
|
82
98
|
end
|
83
99
|
|
84
100
|
def write_data(data)
|
85
101
|
@ssl_socket.write(data)
|
86
102
|
@ssl_socket.flush
|
87
|
-
|
103
|
+
touch
|
88
104
|
end
|
89
105
|
|
90
|
-
def
|
91
|
-
self.
|
106
|
+
def touch
|
107
|
+
self.last_touch = Time.now
|
92
108
|
end
|
93
109
|
|
94
110
|
def setup_ssl_context
|
@@ -99,21 +115,35 @@ module Rpush
|
|
99
115
|
end
|
100
116
|
|
101
117
|
def connect_socket
|
118
|
+
touch
|
102
119
|
check_certificate_expiration
|
103
120
|
|
104
121
|
tcp_socket = TCPSocket.new(@host, @port)
|
105
|
-
tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE,
|
106
|
-
tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY,
|
122
|
+
tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
123
|
+
tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
|
124
|
+
|
125
|
+
# Linux
|
126
|
+
if [:SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all? { |c| Socket.const_defined?(c) }
|
127
|
+
tcp_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, KEEPALIVE_IDLE)
|
128
|
+
tcp_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, KEEPALIVE_INTERVAL)
|
129
|
+
tcp_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, KEEPALIVE_MAX_FAIL_PROBES)
|
130
|
+
end
|
131
|
+
|
132
|
+
# OSX
|
133
|
+
if RUBY_PLATFORM =~ /darwin/
|
134
|
+
tcp_socket.setsockopt(Socket::IPPROTO_TCP, OSX_TCP_KEEPALIVE, KEEPALIVE_IDLE)
|
135
|
+
end
|
136
|
+
|
107
137
|
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl_context)
|
108
138
|
ssl_socket.sync = true
|
109
139
|
ssl_socket.connect
|
110
140
|
[tcp_socket, ssl_socket]
|
111
|
-
rescue
|
141
|
+
rescue *TCP_ERRORS => error
|
112
142
|
if error.message =~ /certificate revoked/i
|
113
143
|
log_warn('Certificate has been revoked.')
|
114
144
|
reflect(:ssl_certificate_revoked, @app, error)
|
115
145
|
end
|
116
|
-
raise
|
146
|
+
raise TcpConnectionError, "#{error.class.name}, #{error.message}"
|
117
147
|
end
|
118
148
|
|
119
149
|
def check_certificate_expiration
|
@@ -128,7 +158,7 @@ module Rpush
|
|
128
158
|
end
|
129
159
|
|
130
160
|
def certificate_msg(msg)
|
131
|
-
time = @ssl_context.cert.not_after.utc.strftime(
|
161
|
+
time = @ssl_context.cert.not_after.utc.strftime('%Y-%m-%d %H:%M:%S UTC')
|
132
162
|
"Certificate #{msg} at #{time}."
|
133
163
|
end
|
134
164
|
|