class Rpush::Daemon::Dispatcher::ApnsTcp

Constants

APNS_ERRORS
ERROR_TUPLE_BYTES
SELECT_TIMEOUT

Public Class Methods

new(*args) click to toggle source
Calls superclass method Rpush::Daemon::Dispatcher::Tcp::new
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 23
def initialize(*args)
  super
  @dispatch_mutex = Mutex.new
  @stop_error_receiver = false
  @connection.on_connect { start_error_receiver }
end

Public Instance Methods

cleanup() click to toggle source
Calls superclass method Rpush::Daemon::Dispatcher::Tcp#cleanup
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 37
def cleanup
  if Rpush.config.push
    # In push mode only a single batch is sent, followed by immediate shutdown.
    # Allow the error receiver time to handle any errors.
    @reconnect_disabled = true
    sleep 1
  end

  @stop_error_receiver = true
  super
  @error_receiver_thread.join if @error_receiver_thread
rescue StandardError => e
  log_error(e)
  reflect(:error, e)
ensure
  @error_receiver_thread = nil
end
dispatch(payload) click to toggle source
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 30
def dispatch(payload)
  @dispatch_mutex.synchronize do
    @delivery_class.new(@app, @connection, payload.batch).perform
    record_batch(payload.batch)
  end
end

Private Instance Methods

check_for_error() click to toggle source
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 74
def check_for_error
  begin
    # On Linux, select returns nil from a dropped connection.
    # On OS X, Errno::EBADF is raised following a Errno::EADDRNOTAVAIL from the write call.
    return unless @connection.select(SELECT_TIMEOUT)
    tuple = @connection.read(ERROR_TUPLE_BYTES)
  rescue *TcpConnection::TCP_ERRORS
    reconnect unless @stop_error_receiver
    return
  end

  @dispatch_mutex.synchronize { handle_error_response(tuple) }
rescue StandardError => e
  log_error(e)
end
delivered_buffer() click to toggle source
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 64
def delivered_buffer
  @delivered_buffer ||= RingBuffer.new(Rpush.config.batch_size * 10)
end
description_for_code(code) click to toggle source
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 135
def description_for_code(code)
  APNS_ERRORS[code.to_i] ? "#{APNS_ERRORS[code.to_i]} (#{code})" : "Unknown error code #{code.inspect}. Possible Rpush bug?"
end
handle_disconnect() click to toggle source
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 115
def handle_disconnect
  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
end
handle_error(code, notification_id) click to toggle source
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 119
def handle_error(code, notification_id)
  notification_id = Rpush::Daemon.store.translate_integer_notification_id(notification_id)
  failed_pos = delivered_buffer.index(notification_id)
  description = description_for_code(code)
  log_error("Notification #{notification_id} failed with error: " + description)
  Rpush::Daemon.store.mark_ids_failed([notification_id], code, description, Time.now)
  reflect(:notification_id_failed, @app, notification_id, code, description)

  if failed_pos
    retry_ids = delivered_buffer[(failed_pos + 1)..-1]
    retry_notification_ids(retry_ids, notification_id)
  elsif delivered_buffer.size > 0
    log_error("Delivery sequence unknown for notifications following #{notification_id}.")
  end
end
handle_error_response(tuple) click to toggle source
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 90
def handle_error_response(tuple)
  if tuple
    _, code, notification_id = tuple.unpack('ccN')
    handle_error(code, notification_id)
  else
    handle_disconnect
  end

  if Rpush.config.push
    # Only attempt to handle a single error in Push mode.
    @stop_error_receiver = true
    return
  end

  reconnect
ensure
  delivered_buffer.clear
end
reconnect() click to toggle source
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 109
def reconnect
  return if @reconnect_disabled
  log_error("Lost connection to #{@connection.host}:#{@connection.port}, reconnecting...")
  @connection.reconnect_with_rescue
end
record_batch(batch) click to toggle source
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 68
def record_batch(batch)
  batch.each_delivered do |notification|
    delivered_buffer << notification.id
  end
end
retry_notification_ids(ids, notification_id) click to toggle source
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 139
def retry_notification_ids(ids, notification_id)
  return if ids.size == 0

  now = Time.now
  Rpush::Daemon.store.mark_ids_retryable(ids, now)
  notifications_str = 'Notification'
  notifications_str += 's' if ids.size > 1
  log_warn("#{notifications_str} #{ids.join(', ')} will be retried due to the failure of notification #{notification_id}.")
  ids.each { |id| reflect(:notification_id_will_retry, @app, id, now) }
end
start_error_receiver() click to toggle source
# File lib/rpush/daemon/dispatcher/apns_tcp.rb, line 57
def start_error_receiver
  @error_receiver_thread = Thread.new do
    check_for_error until @stop_error_receiver
    Rpush::Daemon.store.release_connection
  end
end