rpush 2.2.0-java → 2.3.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +17 -9
  4. data/lib/generators/rpush_migration_generator.rb +1 -1
  5. data/lib/rpush.rb +4 -1
  6. data/lib/rpush/apns_feedback.rb +1 -1
  7. data/lib/rpush/cli.rb +24 -21
  8. data/lib/rpush/client/active_model/apns/app.rb +1 -1
  9. data/lib/rpush/client/active_model/apns/notification.rb +2 -2
  10. data/lib/rpush/client/active_model/wpns/notification.rb +7 -1
  11. data/lib/rpush/configuration.rb +32 -22
  12. data/lib/rpush/daemon.rb +18 -9
  13. data/lib/rpush/daemon/adm/delivery.rb +4 -1
  14. data/lib/rpush/daemon/apns/delivery.rb +3 -0
  15. data/lib/rpush/daemon/apns/feedback_receiver.rb +6 -2
  16. data/lib/rpush/daemon/app_runner.rb +11 -16
  17. data/lib/rpush/daemon/batch.rb +9 -0
  18. data/lib/rpush/daemon/delivery.rb +10 -2
  19. data/lib/rpush/daemon/delivery_error.rb +3 -3
  20. data/lib/rpush/daemon/dispatcher/apns_tcp.rb +11 -11
  21. data/lib/rpush/daemon/dispatcher/tcp.rb +2 -10
  22. data/lib/rpush/daemon/gcm/delivery.rb +13 -7
  23. data/lib/rpush/daemon/signal_handler.rb +5 -3
  24. data/lib/rpush/daemon/store/active_record.rb +14 -0
  25. data/lib/rpush/daemon/store/interface.rb +2 -1
  26. data/lib/rpush/daemon/store/redis.rb +3 -0
  27. data/lib/rpush/daemon/tcp_connection.rb +47 -17
  28. data/lib/rpush/daemon/wpns/delivery.rb +19 -10
  29. data/lib/rpush/logger.rb +30 -12
  30. data/lib/rpush/plugin.rb +44 -0
  31. data/lib/rpush/push.rb +1 -1
  32. data/lib/rpush/reflectable.rb +13 -0
  33. data/lib/rpush/{reflection.rb → reflection_collection.rb} +1 -9
  34. data/lib/rpush/reflection_public_methods.rb +9 -0
  35. data/lib/rpush/version.rb +1 -1
  36. data/lib/tasks/test.rake +6 -4
  37. data/spec/functional/adm_spec.rb +12 -0
  38. data/spec/functional/apns_spec.rb +61 -42
  39. data/spec/functional/gcm_spec.rb +9 -0
  40. data/spec/functional/retry_spec.rb +1 -1
  41. data/spec/functional/wpns_spec.rb +44 -11
  42. data/spec/spec_helper.rb +2 -0
  43. data/spec/unit/apns_feedback_spec.rb +2 -2
  44. data/spec/unit/client/active_record/apns/app_spec.rb +2 -2
  45. data/spec/unit/client/active_record/apns/notification_spec.rb +1 -2
  46. data/spec/unit/client/active_record/wpns/notification_spec.rb +9 -3
  47. data/spec/unit/configuration_spec.rb +1 -0
  48. data/spec/unit/daemon/adm/delivery_spec.rb +0 -1
  49. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +0 -1
  50. data/spec/unit/daemon/app_runner_spec.rb +14 -9
  51. data/spec/unit/daemon/delivery_spec.rb +0 -1
  52. data/spec/unit/daemon/dispatcher/tcp_spec.rb +0 -7
  53. data/spec/unit/daemon/gcm/delivery_spec.rb +1 -1
  54. data/spec/unit/daemon/signal_handler_spec.rb +5 -1
  55. data/spec/unit/daemon/store/active_record_spec.rb +1 -1
  56. data/spec/unit/daemon/tcp_connection_spec.rb +18 -18
  57. data/spec/unit/daemon/wpns/delivery_spec.rb +1 -1
  58. data/spec/unit/daemon_spec.rb +10 -2
  59. data/spec/unit/logger_spec.rb +0 -1
  60. data/spec/unit/plugin_spec.rb +36 -0
  61. data/spec/unit/push_spec.rb +2 -2
  62. data/spec/unit/{daemon/reflectable_spec.rb → reflectable_spec.rb} +6 -6
  63. data/spec/unit/{reflection_spec.rb → reflection_collection_spec.rb} +4 -8
  64. metadata +34 -36
  65. data/lib/rpush/daemon/reflectable.rb +0 -11
  66. data/spec/integration/rpush_spec.rb +0 -13
  67. data/spec/integration/support/gcm_success_response.json +0 -1
  68. 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("%Y-%m-%d %H:%M:%S")} (retry #{@notification.retries})."
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
- log_info("APNs Feedback Receiver started.")
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("%Y-%m-%d %H:%M:%S UTC")
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
- @runners[app.id].start
34
- puts green('✔') if Rpush.config.foreground
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 start
100
+ def start_dispatchers
101
101
  app.connections.times { @dispatcher_loops.push(new_dispatcher_loop) }
102
- start_loops
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 = []
@@ -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("%Y-%m-%d %H:%M:%S")}.")
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
- other.code == code && \
23
- other.notification_id == notification_id && \
24
- other.to_s == to_s
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
- return unless connection.select(SELECT_TIMEOUT)
64
- rescue Errno::EBADF
65
- # Connection closed, daemon is shutting down.
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('Error received, reconnecting...')
82
- connection.reconnect
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('The APNs disconnected without returning an error. Marking all notifications delivered via this connection as failed.')
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 = ENV["RPUSH_GCM_HOST"] || "https://android.googleapis.com"
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("%Y-%m-%d %H:%M:%S")} (retry #{@notification.retries})."
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.total_fail = failures.count == @registration_ids.count
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 :total_fail, :description
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 @total_fail
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 hndling signal '#{signal}'")
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.info("Received HUP signal.")
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("Received USR2 signal.")
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)
@@ -103,6 +103,9 @@ module Rpush
103
103
  def release_connection
104
104
  end
105
105
 
106
+ def reopen_log
107
+ end
108
+
106
109
  private
107
110
 
108
111
  def create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) # rubocop:disable ParameterLists
@@ -6,7 +6,14 @@ module Rpush
6
6
  include Reflectable
7
7
  include Loggable
8
8
 
9
- attr_accessor :last_write
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
- written
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 Errno::EPIPE, Errno::ETIMEDOUT, OpenSSL::SSL::SSLError, IOError => e
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
- reconnect
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 - last_write > self.class.idle_period
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
- written
103
+ touch
88
104
  end
89
105
 
90
- def written
91
- self.last_write = Time.now
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, 1)
106
- tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
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 StandardError => error
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("%Y-%m-%d %H:%M:%S UTC")
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