class PseudoCleaner::RedisMonitorCleaner

I’m not a huge fan of sleeps. In the non-rails world, I used to be able to do a sleep(0) to signal the system to check if somebody else needed to do some work. Testing with Rails, I find I have to actually sleep, so I do a very short time like 0.01.

Constants

FLUSH_COMMANDS

SUITE_KEY = “PseudoDelete::RedisMonitorCleaner:initial_redis_state”

READ_COMMANDS
WRITE_COMMANDS

Attributes

initial_keys[R]
monitor_thread[R]
options[RW]

Public Class Methods

new(start_method, end_method, table, options) click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 315
def initialize(start_method, end_method, table, options)
  @initial_keys       = SortedSet.new
  @monitor_thread     = nil
  @redis_name         = nil
  @suite_altered_keys = SortedSet.new

  unless PseudoCleaner::MasterCleaner::VALID_START_METHODS.include?(start_method)
    raise "You must specify a valid start function from: #{PseudoCleaner::MasterCleaner::VALID_START_METHODS}."
  end
  unless PseudoCleaner::MasterCleaner::VALID_END_METHODS.include?(end_method)
    raise "You must specify a valid end function from: #{PseudoCleaner::MasterCleaner::VALID_END_METHODS}."
  end

  @options = options

  @options[:table_start_method] ||= start_method
  @options[:table_end_method]   ||= end_method
  @options[:output_diagnostics] ||= PseudoCleaner::Configuration.current_instance.output_diagnostics ||
      PseudoCleaner::Configuration.current_instance.post_transaction_analysis

  @redis = table
end

Public Instance Methods

<=>(right_object) click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 338
def <=>(right_object)
  if (right_object.is_a?(PseudoCleaner::RedisMonitorCleaner))
    return 0
  elsif (right_object.is_a?(PseudoCleaner::TableCleaner))
    return 1
  else
    if right_object.respond_to?(:<=>)
      comparison = (right_object <=> self)
      if comparison
        return -1 * comparison
      end
    end
  end

  return 1
end
ignore_key(key) click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 431
def ignore_key(key)
  ignore_regexes.detect { |ignore_regex| key =~ ignore_regex }
end
ignore_regexes() click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 427
def ignore_regexes
  []
end
peek_values() click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 456
def peek_values
  synchronize_test_values do |updated_values|
    if updated_values && !updated_values.empty?
      output_values = false

      if PseudoCleaner::MasterCleaner.report_table
        Cornucopia::Util::ReportTable.new(nested_table:         PseudoCleaner::MasterCleaner.report_table,
                                          nested_table_label:   redis_name,
                                          suppress_blank_table: true) do |report_table|
          updated_values.each do |updated_value|
            unless ignore_key(updated_value)
              output_values = true
              report_table.write_stats updated_value, report_record(updated_value)
            end
          end
        end
      else
        PseudoCleaner::Logger.write("  #{redis_name}")

        updated_values.each do |updated_value|
          unless ignore_key(updated_value)
            output_values = true
            PseudoCleaner::Logger.write("    #{updated_value}: #{report_record(updated_value)}")
          end
        end
      end

      PseudoCleaner::MasterCleaner.report_error if output_values
    end
  end
end
queue() click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 529
def queue
  @queue ||= Queue.new
end
redis() click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 355
def redis
  @redis ||= Redis.current
end
redis_name() click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 435
def redis_name
  unless @redis_name
    redis_options = redis.client.options.with_indifferent_access
    @redis_name   = "#{redis_options[:host]}:#{redis_options[:port]}/#{redis_options[:db]}"
  end

  @redis_name
end
report_dirty_values(message, test_values) click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 635
def report_dirty_values message, test_values
  if test_values && !test_values.empty?
    output_values = false

    if PseudoCleaner::MasterCleaner.report_table
      Cornucopia::Util::ReportTable.new(nested_table:         PseudoCleaner::MasterCleaner.report_table,
                                        nested_table_label:   redis_name,
                                        suppress_blank_table: true) do |report_table|
        report_table.write_stats "action", message
        test_values.each_with_index do |key_name, index|
          unless ignore_key(key_name)
            output_values = true
            report_table.write_stats index, report_record(key_name)
          end
        end
      end
    else
      PseudoCleaner::Logger.write("********* RedisMonitorCleaner - #{message}".red.on_light_white)
      test_values.each do |key_name|
        unless ignore_key(key_name)
          output_values = true
          PseudoCleaner::Logger.write("  #{key_name}: #{report_record(key_name)}".red.on_light_white)
        end
      end
    end

    PseudoCleaner::MasterCleaner.report_error if output_values
  end
end
report_end_of_suite_state(report_reason) click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 496
def report_end_of_suite_state report_reason
  current_keys = SortedSet.new(redis.keys)

  deleted_keys = initial_keys - current_keys
  new_keys     = current_keys - initial_keys

  # filter out values we inserted that will go away on their own.
  new_keys     = new_keys.select { |key| (key =~ /redis_cleaner::synchronization_(?:end_)?key_[0-9]+_[0-9]+/).nil? }

  report_dirty_values "new values as of #{report_reason}", new_keys
  report_dirty_values "values deleted before #{report_reason}", deleted_keys
  report_dirty_values "initial values changed during suite run", @suite_altered_keys

  @suite_altered_keys = SortedSet.new

  new_keys.each do |key_value|
    redis.del key_value
  end
end
report_record(key_name) click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 609
def report_record(key_name)
  key_hash = { key: key_name, type: redis.type(key_name), ttl: redis.ttl(key_name) }
  case key_hash[:type]
    when "string"
      key_hash[:value] = redis.get(key_name)
    when "list"
      key_hash[:list] = { len: redis.llen(key_name), values: redis.lrange(key_name, 0, -1) }
    when "set"
      key_hash[:set] = redis.smembers(key_name)
    when "zset"
      key_hash[:sorted_set] = redis.smembers(key_name)
    when "hash"
      key_hash[:list] = { len: redis.hlen(key_name), values: redis.hgetall(key_name) }
  end

  if key_hash[:value].nil? &&
      key_hash[:list].nil? &&
      key_hash[:set].nil? &&
      key_hash[:sorted_set].nil? &&
      key_hash[:hash].nil?
    key_hash[:value] = "[[DELETED]]"
  end

  key_hash
end
reset_suite() click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 417
def reset_suite
  report_end_of_suite_state "reset suite"

  if monitor_thread
    monitor_thread.kill
    @monitor_thread = nil
    start_monitor
  end
end
review_rows(&block) click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 444
def review_rows(&block)
  synchronize_test_values do |updated_values|
    if updated_values && !updated_values.empty?
      updated_values.each do |updated_value|
        unless ignore_key(updated_value)
          block.yield redis_name, report_record(updated_value)
        end
      end
    end
  end
end
start_monitor() click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 533
def start_monitor
  cleaner_class = self

  @initial_keys = SortedSet.new(redis.keys)
  # @initial_keys.add(PseudoCleaner::RedisMonitorCleaner::SUITE_KEY)
  # @initial_keys.each do |key_value|
  #   redis.sadd(PseudoCleaner::RedisMonitorCleaner::SUITE_KEY, key_value)
  # end
  if @options[:output_diagnostics]
    if PseudoCleaner::MasterCleaner.report_table
      Cornucopia::Util::ReportTable.new(nested_table:         PseudoCleaner::MasterCleaner.report_table,
                                        nested_table_label:   redis_name,
                                        suppress_blank_table: true) do |report_table|
        report_table.write_stats "initial keys count", @initial_keys.count
      end
    else
      PseudoCleaner::Logger.write("#{redis_name}")
      PseudoCleaner::Logger.write("    Initial keys count - #{@initial_keys.count}")
    end
  end

  unless @monitor_thread
    @monitor_thread = Thread.new do
      in_redis_cleanup = false
      updated_keys     = SortedSet.new

      monitor_redis    = Redis.new(cleaner_class.redis.client.options)
      redis_options    = monitor_redis.client.options.with_indifferent_access
      cleaner_class_db = redis_options[:db]

      monitor_redis.monitor do |message|
        redis_message = RedisMessage.new message

        if redis_message.db == cleaner_class_db
          process_command = true

          if redis_message.command == "setex"
            if redis_message.keys[0] == cleaner_class.synchronize_key
              process_command = false

              in_redis_cleanup = true
              return_values    = updated_keys
              updated_keys     = SortedSet.new
              cleaner_class.queue << return_values
            elsif redis_message.keys[0] == cleaner_class.synchronize_end_key
              in_redis_cleanup                       = false
              cleaner_class.monitor_thread[:updated] = nil
              process_command                        = false
            end
          elsif redis_message.command == "del"
            if in_redis_cleanup
              process_command = false
            end
          end

          if process_command
            # flush...
            if PseudoCleaner::RedisMonitorCleaner::WRITE_COMMANDS.include? redis_message.command
              updated_keys.merge(redis_message.keys)
            elsif PseudoCleaner::RedisMonitorCleaner::FLUSH_COMMANDS.include? redis_message.command
              # Not sure I can get the keys at this point...
              # updated_keys.merge(cleaner_class.redis.keys)
            end
          end
        elsif "flushall" == redis_message.command
          # Not sure I can get the keys at this point...
          # updated_keys.merge(cleaner_class.redis.keys)
        end
      end
    end

    sleep(0.01)
    redis.get(synchronize_key)
  end
end
suite_end(test_strategy) click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 408
def suite_end test_strategy
  report_end_of_suite_state "suite end"

  if monitor_thread
    monitor_thread.kill
    @monitor_thread = nil
  end
end
suite_start(test_strategy) click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 359
def suite_start test_strategy
  @test_strategy ||= test_strategy

  # if redis.type(PseudoCleaner::RedisMonitorCleaner::SUITE_KEY) == "set"
  #   @initial_keys = SortedSet.new(redis.smembers(PseudoCleaner::RedisMonitorCleaner::SUITE_KEY))
  #   report_end_of_suite_state "before suite start"
  # end
  # redis.del PseudoCleaner::RedisMonitorCleaner::SUITE_KEY

  start_monitor
end
synchronize_end_key() click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 492
def synchronize_end_key
  @synchronize_end_key ||= "redis_cleaner::synchronization_end_key_#{rand(1..1_000_000_000_000_000_000)}_#{rand(1..1_000_000_000_000_000_000)}"
end
synchronize_key() click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 488
def synchronize_key
  @synchronize_key ||= "redis_cleaner::synchronization_key_#{rand(1..1_000_000_000_000_000_000)}_#{rand(1..1_000_000_000_000_000_000)}"
end
synchronize_test_values(&block) click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 516
def synchronize_test_values(&block)
  updated_values = nil

  if monitor_thread
    redis.setex(synchronize_key, 1, true)
    updated_values = queue.pop
  end

  block.yield updated_values

  redis.setex(synchronize_end_key, 1, true)
end
test_end(test_strategy) click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 385
def test_end test_strategy
  synchronize_test_values do |updated_values|
    if updated_values && !updated_values.empty?
      report_keys = []

      if @options[:output_diagnostics]
        report_dirty_values "updated values", updated_values
      end

      updated_values.each do |value|
        if initial_keys.include?(value)
          report_keys << value
          @suite_altered_keys << value
        else
          redis.del(value)
        end
      end

      report_dirty_values "initial values altered by test", report_keys
    end
  end
end
test_start(test_strategy) click to toggle source
# File lib/pseudo_cleaner/redis_monitor_cleaner.rb, line 371
def test_start test_strategy
  @test_strategy ||= test_strategy

  synchronize_test_values do |test_values|
    if test_values && !test_values.empty?
      report_dirty_values "values altered before the test started", test_values

      test_values.each do |value|
        redis.del value unless initial_keys.include?(value)
      end
    end
  end
end