<commit_before>require "cognizant/util/rotational_array"

module Cognizant
  class Process
    module Triggers
      class Flapping < Trigger
        TRIGGER_STATES = [:starting, :restarting]

        attr_accessor :times, :within, :retry_after
        attr_reader :timeline

        def initialize(options = {})
          options = { :times => 5, :within => 1, :retry_after => 5 }.merge(options)

          options.each_pair do |name, value|
            self.send("#{name}=", value) if self.respond_to?("#{name}=")
          end

          @timeline = Util::RotationalArray.new(@times)
        end

        def notify(transition)
          if TRIGGER_STATES.include?(transition.to_name)
            self.timeline << Time.now.to_i
            self.check_flapping
          end
        end

        def reset!
          @timeline.clear
        end

        def check_flapping
          # The process has not flapped if we haven't encountered enough incidents.
          return unless (@timeline.compact.length == self.times)

          # Check if the incident happend within the timeframe.
          duration = (@timeline.last - @timeline.first) <= self.within

          if duration
            puts "Flapping detected: retrying in #{self.retry_after} seconds"

            # @delegate is set by TriggerDelegate.
            # retry_after = 0 means do not retry.
            @delegate.schedule_event(:start, self.retry_after) unless self.retry_after == 0
            @delegate.schedule_event(:unmonitor, 0)

            @timeline.clear

            # This will prevent a transition from happening in the process state_machine.
            throw :halt
          end
        end
      end
    end
  end
end<commit_msg>Add number of retries option to flapping trigger.<commit_after>require "cognizant/util/rotational_array"

module Cognizant
  class Process
    module Triggers
      class Flapping < Trigger
        TRIGGER_STATES = [:starting, :restarting]

        attr_accessor :times, :within, :retry_after, :retries
        attr_reader :timeline

        def initialize(options = {})
          options = { :times => 5, :within => 1, :retry_after => 5, :retries => 0 }.merge(options)

          options.each_pair do |name, value|
            self.send("#{name}=", value) if self.respond_to?("#{name}=")
          end

          @timeline = Util::RotationalArray.new(@times)
          @num_of_tries = 0
        end

        def notify(transition)
          if TRIGGER_STATES.include?(transition.to_name)
            self.timeline << Time.now.to_i
            self.check_flapping
          end
        end

        def reset!
          @timeline.clear
          @num_of_tries = 0
        end

        def check_flapping
          # The process has not flapped if we haven't encountered enough incidents.
          return unless (@timeline.compact.length == self.times)

          # Check if the incident happend within the timeframe.
          duration = (@timeline.last - @timeline.first) <= self.within

          if duration
            puts "Flapping detected (#{@num_of_tries}): retrying in #{self.retry_after} seconds"

            @delegate.schedule_event(:unmonitor, 0)

            # @delegate is set by TriggerDelegate.
            # retries = 0 means always retry.
            if self.retries == 0 or (self.retries > 0 and @num_of_tries < self.retries)
              # retry_after = 0 means do not retry.
              @delegate.schedule_event(:start, self.retry_after) unless self.retry_after == 0
            end

            @timeline.clear

            @num_of_tries += 1

            # This will prevent a transition from happening in the process state_machine.
            throw :halt
          end
        end
      end
    end
  end
end
{'fraction_non_alphanumeric': 0.08761651131824234, 'fraction_numerical': 0.005059920106524634, 'mean_word_length': 1.9738717339667458, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 11, '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': '3942591', 'n_tokens_mistral': 1083, 'n_tokens_neox': 1045, 'n_words': 293}