rpush 2.4.0 → 2.5.0

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.
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