rpush 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +11 -7
  4. data/lib/generators/templates/rpush.rb +8 -2
  5. data/lib/generators/templates/rpush_2_0_0_updates.rb +1 -1
  6. data/lib/rpush/cli.rb +61 -27
  7. data/lib/rpush/client/active_model.rb +3 -0
  8. data/lib/rpush/client/active_model/apns/notification.rb +1 -1
  9. data/lib/rpush/client/active_model/wns/app.rb +23 -0
  10. data/lib/rpush/client/active_model/wns/notification.rb +28 -0
  11. data/lib/rpush/client/active_model/wpns/notification.rb +11 -6
  12. data/lib/rpush/client/active_record.rb +3 -0
  13. data/lib/rpush/client/active_record/wns/app.rb +11 -0
  14. data/lib/rpush/client/active_record/wns/notification.rb +11 -0
  15. data/lib/rpush/client/mongoid.rb +3 -0
  16. data/lib/rpush/client/mongoid/apns/feedback.rb +3 -0
  17. data/lib/rpush/client/mongoid/notification.rb +6 -0
  18. data/lib/rpush/client/mongoid/wns/app.rb +14 -0
  19. data/lib/rpush/client/mongoid/wns/notification.rb +11 -0
  20. data/lib/rpush/client/redis.rb +3 -0
  21. data/lib/rpush/client/redis/wns/app.rb +14 -0
  22. data/lib/rpush/client/redis/wns/notification.rb +11 -0
  23. data/lib/rpush/configuration.rb +3 -7
  24. data/lib/rpush/daemon.rb +9 -0
  25. data/lib/rpush/daemon/apns/feedback_receiver.rb +5 -0
  26. data/lib/rpush/daemon/app_runner.rb +4 -5
  27. data/lib/rpush/daemon/dispatcher/apns_tcp.rb +47 -12
  28. data/lib/rpush/daemon/dispatcher_loop.rb +5 -0
  29. data/lib/rpush/daemon/feeder.rb +11 -0
  30. data/lib/rpush/daemon/interruptible_sleep.rb +8 -3
  31. data/lib/rpush/daemon/loggable.rb +4 -0
  32. data/lib/rpush/daemon/rpc.rb +9 -0
  33. data/lib/rpush/daemon/rpc/client.rb +27 -0
  34. data/lib/rpush/daemon/rpc/server.rb +82 -0
  35. data/lib/rpush/daemon/signal_handler.rb +7 -0
  36. data/lib/rpush/daemon/store/active_record.rb +17 -3
  37. data/lib/rpush/daemon/store/mongoid.rb +2 -2
  38. data/lib/rpush/daemon/store/redis.rb +2 -2
  39. data/lib/rpush/daemon/tcp_connection.rb +2 -2
  40. data/lib/rpush/daemon/wns.rb +9 -0
  41. data/lib/rpush/daemon/wns/delivery.rb +206 -0
  42. data/lib/rpush/embed.rb +15 -13
  43. data/lib/rpush/logger.rb +4 -0
  44. data/lib/rpush/plugin.rb +1 -1
  45. data/lib/rpush/push.rb +2 -11
  46. data/lib/rpush/reflection_collection.rb +15 -17
  47. data/lib/rpush/reflection_public_methods.rb +6 -4
  48. data/lib/rpush/version.rb +1 -1
  49. data/spec/functional/apns_spec.rb +1 -11
  50. data/spec/functional/cli_spec.rb +35 -0
  51. data/spec/functional_spec_helper.rb +11 -1
  52. data/spec/spec_helper.rb +4 -3
  53. data/spec/support/active_record_setup.rb +1 -1
  54. data/spec/unit/client/active_record/apns/notification_spec.rb +1 -1
  55. data/spec/unit/configuration_spec.rb +0 -7
  56. data/spec/unit/daemon/adm/delivery_spec.rb +2 -2
  57. data/spec/unit/daemon/app_runner_spec.rb +2 -3
  58. data/spec/unit/daemon/gcm/delivery_spec.rb +1 -1
  59. data/spec/unit/daemon/tcp_connection_spec.rb +1 -1
  60. data/spec/unit/daemon/wns/delivery_spec.rb +171 -0
  61. data/spec/unit/daemon/wpns/delivery_spec.rb +1 -1
  62. data/spec/unit/daemon_spec.rb +2 -0
  63. data/spec/unit/embed_spec.rb +4 -11
  64. data/spec/unit/logger_spec.rb +2 -2
  65. data/spec/unit/push_spec.rb +0 -7
  66. data/spec/unit_spec_helper.rb +1 -1
  67. metadata +20 -3
@@ -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
@@ -0,0 +1,11 @@
1
+ module Rpush
2
+ module Client
3
+ module Redis
4
+ module Wns
5
+ class Notification < Rpush::Client::Redis::Notification
6
+ include Rpush::Client::ActiveModel::Wns::Notification
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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 = [:feedback_poll]
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
@@ -43,6 +43,11 @@ module Rpush
43
43
  @stop = true
44
44
  @interruptible_sleep.stop
45
45
  @thread.join if @thread
46
+ rescue StandardError => e
47
+ log_error(e)
48
+ reflect(:error, e)
49
+ ensure
50
+ @thread = nil
46
51
  end
47
52
 
48
53
  def check_for_feedback
@@ -84,8 +84,8 @@ module Rpush
84
84
  @runners[app.id].increment_dispatchers(num)
85
85
  end
86
86
 
87
- def self.debug
88
- @runners.values.map(&:debug)
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 debug
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
- runner_details = { dispatchers: dispatcher_details, queued: queue_size }
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
- rescue SystemCallError, IOError
67
- # Connection closed.
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
- log_error("Lost connection to #{@connection.host}:#{@connection.port}, reconnecting...")
86
- @connection.reconnect_with_rescue
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 = APNS_ERRORS[code.to_i] || "Unknown error code #{code.inspect}. Possible Rpush bug?"
99
- log_error(description + " (#{code})")
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
- if retry_ids.size > 0
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
@@ -44,6 +44,11 @@ module Rpush
44
44
  @queue.push([STOP, object_id]) if @thread
45
45
  @thread.join if @thread
46
46
  @dispatcher.cleanup
47
+ rescue StandardError => e
48
+ log_error(e)
49
+ reflect(:error, e)
50
+ ensure
51
+ @thread = nil
47
52
  end
48
53
 
49
54
  private
@@ -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
- @thread.join
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
@@ -1,6 +1,10 @@
1
1
  module Rpush
2
2
  module Daemon
3
3
  module Loggable
4
+ def log_debug(msg)
5
+ Rpush.logger.debug(app_prefix(msg))
6
+ end
7
+
4
8
  def log_info(msg)
5
9
  Rpush.logger.info(app_prefix(msg))
6
10
  end
@@ -0,0 +1,9 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Rpc
4
+ def self.socket_path(pid = Process.pid)
5
+ "/tmp/rpush.#{pid}.sock"
6
+ end
7
+ end
8
+ end
9
+ 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