123456789_123456789_123456789_123456789_123456789_

Class: Mongoid::Matcher::RegexpBudget::Budget Private

Relationships & Source Files
Inherits: Object
Defined in: lib/mongoid/matcher/regexp_budget.rb

Overview

The state of one open budget: what is left of the limit, and the patterns compiled under it.

Class Method Summary

Instance Attribute Summary

Instance Method Summary

  • #charge(elapsed) Internal use only

    Draws the elapsed time down from the budget.

  • #compile(condition) ⇒ Regexp Internal use only

    Returns the condition as a ::Regexp which, where the Ruby in use supports it, gives up once its timeout is spent.

  • #bake(regexp) private Internal use only

    Rebuilds the pattern with the budget's timeout, where the Ruby in use has them.

  • #cache_key(condition) private Internal use only

    BSON::Regexp::Raw aliases eql? to == but leaves hash alone, so two equal instances hash differently and cannot key the cache.

Constructor Details

.new(limit) ⇒ Budget

Parameters:

  • limit (Float) —

    The seconds this budget may spend.

[ GitHub ]

  
# File 'lib/mongoid/matcher/regexp_budget.rb', line 62

def initialize(limit)
  @limit = limit
  @remaining = limit
  @cache = {}
  # A per-Regexp timeout bounds one match; the cumulative budget bounds
  # the operation. So the timeout is fixed for the life of the scope
  # rather than following the drawdown, which is what lets a pattern be
  # compiled once instead of once per match. It means a match that
  # starts with almost nothing left can still run for a whole limit, so
  # an operation can overshoot by at most one limit -- bounded, which is
  # the point, and far cheaper than recompiling.
  #
  # An application that has set a stricter global Regexp.timeout keeps
  # it: baking in a larger value would leave it less protected than it
  # asked to be. A timeout set on one individual pattern is a different
  # matter, and is not preserved -- Budget#compile rebuilds the pattern
  # from its source and flags, neither of which carries one, so this
  # value takes its place. Only trusted code can supply such a pattern:
  # a condition decoded from JSON or BSON arrives as a string or a
  # BSON::Regexp::Raw, with no timeout of its own.
  @timeout = [ limit, ::Regexp.timeout ].compact.min if PER_REGEXP_TIMEOUT
end

Instance Attribute Details

#exhausted? ⇒ true | false (readonly)

Returns:

  • (true | false) —

    Whether the budget is spent.

[ GitHub ]

  
# File 'lib/mongoid/matcher/regexp_budget.rb', line 93

def exhausted?
  @remaining <= 0
end

#limit ⇒ Float (readonly)

Returns:

  • (Float) —

    The limit this budget started with.

[ GitHub ]

  
# File 'lib/mongoid/matcher/regexp_budget.rb', line 56

attr_reader :limit

#remaining ⇒ Float (readonly)

Returns:

  • (Float) —

    The seconds left before the budget is spent.

[ GitHub ]

  
# File 'lib/mongoid/matcher/regexp_budget.rb', line 59

attr_reader :remaining

Instance Method Details

#bake(regexp) (private)

Rebuilds the pattern with the budget's timeout, where the Ruby in use has them.

[ GitHub ]

  
# File 'lib/mongoid/matcher/regexp_budget.rb', line 128

def bake(regexp)
  return regexp unless PER_REGEXP_TIMEOUT

  ::Regexp.new(regexp.source, regexp.options, timeout: @timeout)
end

#cache_key(condition) (private)

BSON::Regexp::Raw aliases eql? to == but leaves hash alone, so two equal instances hash differently and cannot key the cache. What they are equal by can. Anything else keys on itself and is left to coerce to reject.

[ GitHub ]

  
# File 'lib/mongoid/matcher/regexp_budget.rb', line 119

def cache_key(condition)
  case condition
  when BSON::Regexp::Raw then [ condition.pattern, condition.options ]
  else condition
  end
end

#charge(elapsed)

Draws the elapsed time down from the budget.

Parameters:

  • elapsed (Float) —

    The seconds to charge.

[ GitHub ]

  
# File 'lib/mongoid/matcher/regexp_budget.rb', line 88

def charge(elapsed)
  @remaining -= elapsed
end

#compile(condition) ⇒ Regexp

Returns the condition as a ::Regexp which, where the Ruby in use supports it, gives up once its timeout is spent.

The condition is taken uncompiled so that the cache can answer before any compiling happens. A BSON::Regexp::Raw memoizes its own compile, but ::Mongoid::Matcher::FieldExpression builds a fresh one for every $regex it evaluates, so that memo is worth nothing across documents and the source would otherwise be compiled once per document.

Parameters:

  • condition (Regexp | BSON::Regexp::Raw) —

    The condition.

Returns:

  • (Regexp) —

    The compiled pattern.

[ GitHub ]

  
# File 'lib/mongoid/matcher/regexp_budget.rb', line 109

def compile(condition)
  @cache[cache_key(condition)] ||= bake(RegexpBudget.coerce(condition))
end