class Unravel::Session

Attributes

config[R]
registry[R]

Public Class Methods

new(config = DefaultConfig.new) click to toggle source
# File lib/unravel.rb, line 121
def initialize(config = DefaultConfig.new)
  @config = config
  @registry = Registry.new
end

Public Instance Methods

achieve(name, *args) click to toggle source
# File lib/unravel.rb, line 126
def achieve(name, *args)
  check # TODO: overhead?

  prev_causes = Set.new
  max_retries = config.max_retries
  retries_left = max_retries

  begin
    Unravel.logger.info("Achieving (attempt: #{max_retries - retries_left + 1}/#{max_retries}): #{name.inspect}")

    block = registry.get_achievement(name)
    if block.arity >= 0
      unless block.arity == args.size
        fail ArgumentError, "expected #{block.arity} args for #{name.inspect}, got: #{args.inspect}"
      end
    end

    error_contexts = registry.error_contexts_for_achievement(name)

    begin
      res = return_wrap(*args, &block)
    rescue *error_contexts.keys
      ex = $!
      econtext = error_contexts[ex.class]
      unless econtext
        # TODO: not tested
        fail NoErrorHandler.new(name, ex)
      end

      econtext.each do |fix_name|
        error = $!.message
        fix! fix_name, error
      end
      fail
    end

    if res == true
      config.after_achievement_success(name)
      return true
    end

    fail NotImplementedError, "#{name} unexpectedly returned #{res.inspect} (expected true or exception)"

  rescue FixableError => error
    Unravel.logger.info("#{name}: Symptom: #{error.symptom.inspect}")

    #Unravel.logger.debug("  -> failed: #{name.inspect}: #{error.symptom.inspect}\n")
    cause = get_root_cause_for(error.symptom)

    fail NoKnownRootCause, "Can't find root cause for: #{error.symptom}, #{error.message}" unless cause

    Unravel.logger.info("#{name}: Cause: #{cause.inspect}")

    # Since causes can be generic (parametized), they're unique based on error matchers
    unique_context = error.extracted_info
    unique_cause = [cause, unique_context]

    if prev_causes.include? unique_cause
      fail SameCauseReoccurringCause, "#{cause.to_s} (with #{unique_context.inspect}) wasn't ultimately fixed (it occured again)"
    end

    prev_causes << unique_cause
    fix = registry.get_fix_for(cause)
    fix.call(error)

    retries_left -= 1
    retry if retries_left > 0
    fail
  end
end
achievement(name, error_contexts, &block) click to toggle source
# File lib/unravel.rb, line 197
def achievement(name, error_contexts, &block)
  registry.add_achievement(name, &block)
  registry.add_error_contexts(name, error_contexts)
end
error() click to toggle source
# File lib/unravel.rb, line 202
def error
  registry.errors
end
fix_for(*args, &block) click to toggle source

TODO: move logic to registry

# File lib/unravel.rb, line 212
def fix_for(*args, &block)
  name, achievement = *args
  if block_given?
    if args.size > 1
      fail ArgumentError, "#{args[1..-1].inspect} ignored because of block"
    end
    registry.add_fix(name, block)
  else
    if name.is_a?(Hash)
      name, achievement = *name.first
    end
    # TODO: this recursively calls self (just to provied block), though -
    # is the block_given check needed?
    unless block_given?
      fix_for(name)  do |error|
        achieve(achievement, *error.extracted_info)
      end
    end
  end
end
quickfix(error_name, regexp, fix_name, handlers={}) click to toggle source

Shorthand for easy-to-fix and name problems

# File lib/unravel.rb, line 234
def quickfix(error_name, regexp, fix_name, handlers={})
  root_cause_name = "no_#{fix_name}".to_sym
  error[error_name] = regexp
  root_cause_for error_name => root_cause_name
  unless registry.has_fix_for?(root_cause_name)
    fix_for root_cause_name => fix_name
  end
  achievement fix_name, handlers, &method(fix_name)
end
root_cause_for(mapping) click to toggle source
# File lib/unravel.rb, line 206
def root_cause_for(mapping)
  symptom, root_cause = *mapping.first
  registry.add_symptom(symptom, root_cause)
end

Private Instance Methods

check() click to toggle source
# File lib/unravel.rb, line 254
def check
  logger = Unravel.logger

  res = registry.fixes.keys - registry.symptoms.values
  logger.warn "Unused: #{res.inspect}" unless res.empty?

  res = registry.symptoms.values - registry.fixes.keys
  logger.warn "Unhandled: #{res.inspect}" unless res.empty?

  errors = registry.contexts.values.map(&:values).flatten(2)
  res = errors - registry.symptoms.keys
  logger.warn "Unknown contexts: #{res.inspect}" unless res.empty?

  res = registry.symptoms.keys - errors
  logger.warn "Unused errors: #{res.inspect}" unless res.empty?
end
fix!(name, error) click to toggle source
# File lib/unravel.rb, line 271
def fix!(name, error)
  regexp = registry.fixable_error(name)
  unless regexp
    fail HumanInterventionNeeded, "Unregistered error: #{name} to match #{error.inspect}"
  end

  # TODO: encoding not tested
  match = regexp.match(error.force_encoding(Encoding::ASCII_8BIT))
  fail FixableError.new(name, match) if match
end
get_root_cause_for(symptom) click to toggle source
# File lib/unravel.rb, line 282
def get_root_cause_for(symptom)
  registry.get_root_cause(symptom)
end
return_wrap(*args, &block) click to toggle source
# File lib/unravel.rb, line 246
def return_wrap(*args, &block)
  Thread.new do
    return block.yield(*args)
  end.join
rescue LocalJumpError => ex
  ex.exit_value
end