# encoding: UTF-8 require 'mauve/mauve_thread' module Mauve # # This class is a singlton thread which pops updates off the # Server#packet_buffer and processes them as alert updates. # # It is responsible for de-bouncing updates, i.e. ones with duplicate # transmission IDs. # class Processor < MauveThread include Singleton # This is the time after which transmission IDs are expired. # attr_reader :transmission_cache_expire_time # Initialize the processor # def initialize super # # Set up the transmission id cache # @transmission_id_cache = {} @transmission_cache_expire_time = 300 @transmission_cache_checked_at = Time.now end # @return [Log4r::Logger] def logger @logger ||= Log4r::Logger.new(self.class.to_s) end # Set the expiry time # # @param [Integer] i The number of seconds after which transmission IDs are considered unseen. # @raise [ArgumentError] If +i+ is not an Integer def transmission_cache_expire_time=(i) raise ArgumentError, "transmission_cache_expire_time must be an integer" unless i.is_a?(Integer) @transmission_cache_expire_time = i end # This expries the transmission cache # # def expire_transmission_id_cache now = Time.now # # Only check once every minute. # return unless (now - @transmission_cache_checked_at) > 60 to_delete = [] @transmission_id_cache.each do |tid, received_at| to_delete << tid if (now - received_at) > @transmission_cache_expire_time end to_delete.each do |tid| @transmission_id_cache.delete(tid) end @transmission_cache_checked_at = now end # This stops the processor, making sure all pending updates are saved. # def stop super # # flush the queue # do_processor end # This processes an incoming packet. It is in a seperate method so it can # be (de)coupled as needed from the UDP server. # def process_packet(data, client, received_at) # # Uh-oh. Nil data? That's craaaazy # return nil if data.nil? ip_source = "#{client[3]}" update = Proto::AlertUpdate.new update.parse_from_string(data) if @transmission_id_cache[update.transmission_id.to_s] logger.debug("Ignoring duplicate transmission id #{update.transmission_id}") return nil end logger.debug "Update #{update.transmission_id} sent at #{update.transmission_time} received at #{received_at.to_i} from "+ "'#{update.source}'@#{ip_source} alerts #{update.alert.length}" Alert.receive_update(update, received_at, ip_source) rescue Protobuf::InvalidWireType, NotImplementedError, DataObjects::IntegrityError => ex logger.error "#{ex} (#{ex.class}) while parsing #{data.length} bytes "+ "starting '#{data[0..15].inspect}' from #{ip_source}" logger.debug ex.backtrace.join("\n") ensure @transmission_id_cache[update.transmission_id.to_s] = Time.now end private def main_loop do_processor do_timer unless timer_should_stop? end def do_timer # # Get the next alert. # next_alert = Alert.find_next_with_event # # If we didn't find an alert, or the alert we found is due in the future, # look for the next alert_changed object. # if next_alert.nil? or next_alert.due_at > Time.now next_alert_changed = AlertChanged.find_next_with_event end if next_alert_changed.nil? and next_alert.nil? next_to_notify = nil elsif next_alert.nil? or next_alert_changed.nil? next_to_notify = (next_alert || next_alert_changed) else next_to_notify = ( next_alert.due_at < next_alert_changed.due_at ? next_alert : next_alert_changed ) end # # Nothing to notify? # if next_to_notify.nil? # # Sleep indefinitely # logger.info("Nothing to notify about -- snoozing for a while.") sleep_loops = 600 else # # La la la nothing to do. # logger.info("Next to notify: #{next_to_notify} #{next_to_notify.is_a?(AlertChanged) ? "(reminder)" : "(heartbeat)"} -- snoozing until #{next_to_notify.due_at.iso8601}") sleep_loops = ((next_to_notify.due_at - Time.now).to_f / 0.1).round.to_i end sleep_loops = 0 if sleep_loops.nil? or sleep_loops < 1 # # Ah-ha! Sleep with a break clause. # sleep_loops.times do # # Start again if the situation has changed. # break if timer_should_stop? # # This is a rate-limiting step for alerts. # Kernel.sleep 0.1 end return if timer_should_stop? or next_to_notify.nil? next_to_notify.poll end # This is processor loop # def do_processor sz = Server.packet_buffer_size sz.times do process_packet(*Server.packet_pop) end end def timer_should_stop? (Server.packet_buffer_size > 0) or self.should_stop? end end end