<commit_before>module Rapns
  module Daemon
    class DeliveryHandler
      include DatabaseReconnectable

      STOP = 0x666
      SELECT_TIMEOUT = 0.5
      ERROR_TUPLE_BYTES = 6
      APN_ERRORS = {
        1 => "Processing error",
        2 => "Missing device token",
        3 => "Missing topic",
        4 => "Missing payload",
        5 => "Missing token size",
        6 => "Missing topic size",
        7 => "Missing payload size",
        8 => "Invalid token",
        255 => "None (unknown error)"
      }

      attr_reader :name

      def initialize(queue, name, host, port, certificate, password)
        @queue = queue
        @name = "DeliveryHandler:#{name}"
        @connection = Connection.new(@name, host, port, certificate, password)
      end

      def start
        @connection.connect

        Thread.new do
          loop do
            break if @stop
            handle_next_notification
          end
        end
      end

      def stop
        @stop = true
        @queue.push(STOP)
      end

      protected

      def deliver(notification)
        begin
          @connection.write(notification.to_binary)
          check_for_error

          with_database_reconnect_and_retry do
            notification.delivered = true
            notification.delivered_at = Time.now
            notification.save!(:validate => false)
          end

          Rapns::Daemon.logger.info("[#{@name}] #{notification.id} sent to #{notification.device_token}")
        rescue Rapns::DeliveryError, Rapns::DisconnectionError => error
          handle_delivery_error(notification, error)
          raise
        end
      end

      def handle_delivery_error(notification, error)
        with_database_reconnect_and_retry do
          notification.delivered = false
          notification.delivered_at = nil
          notification.failed = true
          notification.failed_at = Time.now
          notification.error_code = error.code
          notification.error_description = error.description
          notification.save!(:validate => false)
        end
      end

      def check_for_error
        if @connection.select(SELECT_TIMEOUT)
          error = nil

          if tuple = @connection.read(ERROR_TUPLE_BYTES)
            cmd, code, notification_id = tuple.unpack("ccN")

            description = APN_ERRORS[code.to_i] || "Unknown error. Possible rapns bug?"
            error = Rapns::DeliveryError.new(code, notification_id, description)
          else
            error = Rapns::DisconnectionError.new
          end

          begin
            Rapns::Daemon.logger.error("[#{@name}] Error received, reconnecting...")
            @connection.reconnect
          ensure
            raise error if error
          end
        end
      end

      def handle_next_notification
        notification = @queue.pop

        if notification == STOP
          @connection.close
          return
        end

        begin
          deliver(notification)
        rescue StandardError => e
          Rapns::Daemon.logger.error(e)
        ensure
          @queue.notification_processed
        end
      end
    end
  end
end<commit_msg>Reduce error timeout<commit_after>module Rapns
  module Daemon
    class DeliveryHandler
      include DatabaseReconnectable

      STOP = 0x666
      SELECT_TIMEOUT = 0.2
      ERROR_TUPLE_BYTES = 6
      APN_ERRORS = {
        1 => "Processing error",
        2 => "Missing device token",
        3 => "Missing topic",
        4 => "Missing payload",
        5 => "Missing token size",
        6 => "Missing topic size",
        7 => "Missing payload size",
        8 => "Invalid token",
        255 => "None (unknown error)"
      }

      attr_reader :name

      def initialize(queue, name, host, port, certificate, password)
        @queue = queue
        @name = "DeliveryHandler:#{name}"
        @connection = Connection.new(@name, host, port, certificate, password)
      end

      def start
        @connection.connect

        Thread.new do
          loop do
            break if @stop
            handle_next_notification
          end
        end
      end

      def stop
        @stop = true
        @queue.push(STOP)
      end

      protected

      def deliver(notification)
        begin
          @connection.write(notification.to_binary)
          check_for_error

          with_database_reconnect_and_retry do
            notification.delivered = true
            notification.delivered_at = Time.now
            notification.save!(:validate => false)
          end

          Rapns::Daemon.logger.info("[#{@name}] #{notification.id} sent to #{notification.device_token}")
        rescue Rapns::DeliveryError, Rapns::DisconnectionError => error
          handle_delivery_error(notification, error)
          raise
        end
      end

      def handle_delivery_error(notification, error)
        with_database_reconnect_and_retry do
          notification.delivered = false
          notification.delivered_at = nil
          notification.failed = true
          notification.failed_at = Time.now
          notification.error_code = error.code
          notification.error_description = error.description
          notification.save!(:validate => false)
        end
      end

      def check_for_error
        if @connection.select(SELECT_TIMEOUT)
          error = nil

          if tuple = @connection.read(ERROR_TUPLE_BYTES)
            cmd, code, notification_id = tuple.unpack("ccN")

            description = APN_ERRORS[code.to_i] || "Unknown error. Possible rapns bug?"
            error = Rapns::DeliveryError.new(code, notification_id, description)
          else
            error = Rapns::DisconnectionError.new
          end

          begin
            Rapns::Daemon.logger.error("[#{@name}] Error received, reconnecting...")
            @connection.reconnect
          ensure
            raise error if error
          end
        end
      end

      def handle_next_notification
        notification = @queue.pop

        if notification == STOP
          @connection.close
          return
        end

        begin
          deliver(notification)
        rescue StandardError => e
          Rapns::Daemon.logger.error(e)
        ensure
          @queue.notification_processed
        end
      end
    end
  end
end
{'fraction_non_alphanumeric': 0.0788348899794206, 'fraction_numerical': 0.005698907709355707, 'mean_word_length': 1.8217954443948192, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4608980', 'n_tokens_mistral': 1664, 'n_tokens_neox': 1625, 'n_words': 405}