Class: Rubino::Documents::Limits::Budget

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/documents/limits.rb

Overview

Per-conversion resource counter. Not thread-safe by design: a single conversion runs on one thread; the cancel_token IS the cross-thread signal and is itself lock-free/monotonic.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_elements:, max_decompressed_bytes:, wall_clock_seconds:, cancel_token: nil) ⇒ Budget

Returns a new instance of Budget.



160
161
162
163
164
165
166
167
168
169
# File 'lib/rubino/documents/limits.rb', line 160

def initialize(max_elements:, max_decompressed_bytes:, wall_clock_seconds:, cancel_token: nil)
  @max_elements = max_elements
  @max_decompressed_bytes = max_decompressed_bytes
  @wall_clock = wall_clock_seconds
  @deadline = monotonic + wall_clock_seconds
  @cancel_token = cancel_token
  @elements = 0
  @bytes = 0
  @since_clock = 0
end

Instance Attribute Details

#bytesObject (readonly)

Returns the value of attribute bytes.



158
159
160
# File 'lib/rubino/documents/limits.rb', line 158

def bytes
  @bytes
end

#elementsObject (readonly)

Returns the value of attribute elements.



158
159
160
# File 'lib/rubino/documents/limits.rb', line 158

def elements
  @elements
end

#max_decompressed_bytesObject (readonly)

Returns the value of attribute max_decompressed_bytes.



158
159
160
# File 'lib/rubino/documents/limits.rb', line 158

def max_decompressed_bytes
  @max_decompressed_bytes
end

Instance Method Details

#add_bytes(count) ⇒ Object

Account for ‘count` extracted bytes WITHOUT advancing the element count (e.g. a raw whole-file read in html/csv/json/xml/plain). Still checks the byte ceiling, the clock, and cancellation.



198
199
200
# File 'lib/rubino/documents/limits.rb', line 198

def add_bytes(count)
  tick(elements: 0, bytes: count)
end

#tick(elements: 1, bytes: 0) ⇒ Object

Account for one (or more) processed units and ‘bytes` of extracted text, then enforce every cap. Call once per element in the converter’s hot loop. Raises Rubino::Interrupted on cancel, CapExceeded on any cap.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/rubino/documents/limits.rb', line 174

def tick(elements: 1, bytes: 0)
  @elements += elements
  @bytes += bytes
  @since_clock += elements

  # Cancellation first: a cancelled turn must abort even mid-bomb.
  raise Rubino::Interrupted if @cancel_token&.cancelled?

  raise CapExceeded, "element count cap (#{@max_elements}) exceeded" if @elements > @max_elements
  if @bytes > @max_decompressed_bytes
    raise CapExceeded, "decompressed size cap (#{@max_decompressed_bytes} bytes) exceeded"
  end

  # Reading a clock per element is measurable in a tight 1M-iteration
  # loop; sample it every TICK_INTERVAL elements instead.
  return unless @since_clock >= TICK_INTERVAL

  @since_clock = 0
  raise CapExceeded, "wall-clock budget (#{format("%.0f", @wall_clock)}s) exceeded" if monotonic > @deadline
end