Class: Html2rss::RequestService::Budget

Inherits:
Object
  • Object
show all
Defined in:
lib/html2rss/request_service/budget.rb

Overview

Tracks request slots, interaction budget, and wall-clock deadline for one feed build.

HTTP fetches consume request slots; Browserless preload clicks/scrolls/waits consume interaction budget. Deadline remains on #remaining_timeout_seconds.

Instance Method Summary collapse

Constructor Details

#initialize(max_requests:, max_interactions: 0, total_timeout_seconds: nil) ⇒ Budget

Returns a new instance of Budget.

Parameters:

  • max_requests (Integer)

    maximum HTTP request slots

  • max_interactions (Integer) (defaults to: 0)

    maximum preload interaction slots (default 0)

  • total_timeout_seconds (Integer, nil) (defaults to: nil)

    wall-clock timeout for the feed build



15
16
17
18
19
20
21
22
23
# File 'lib/html2rss/request_service/budget.rb', line 15

def initialize(max_requests:, max_interactions: 0, total_timeout_seconds: nil)
  validate_slot_limits!(max_requests:, max_interactions:)

  @remaining_requests = max_requests
  @remaining_interactions = max_interactions
  @start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  @total_timeout_seconds = total_timeout_seconds
  @mutex = Mutex.new
end

Instance Method Details

#consume!Integer

Consumes one HTTP request slot.

Returns:

  • (Integer)

    remaining request slots after consumption

Raises:



30
31
32
# File 'lib/html2rss/request_service/budget.rb', line 30

def consume!
  consume_request!
end

#consume_interaction!Integer

Consumes one Browserless preload interaction slot.

Preload must not steal pagination HTTP request slots.

Returns:

  • (Integer)

    remaining interaction slots after consumption

Raises:



54
55
56
57
58
59
60
# File 'lib/html2rss/request_service/budget.rb', line 54

def consume_interaction!
  @mutex.synchronize do
    raise InteractionBudgetExceeded, 'Interaction budget exhausted' if @remaining_interactions.zero?

    @remaining_interactions -= 1
  end
end

#consume_request!Integer

Consumes one HTTP request slot.

Returns:

  • (Integer)

    remaining request slots after consumption

Raises:



39
40
41
42
43
44
45
# File 'lib/html2rss/request_service/budget.rb', line 39

def consume_request!
  @mutex.synchronize do
    raise RequestBudgetExceeded, 'Request budget exhausted' if @remaining_requests.zero?

    @remaining_requests -= 1
  end
end

#effective_timeout_ms(fallback:) ⇒ Integer

Returns milliseconds available for this attempt.

Parameters:

  • fallback (Numeric)

    policy total timeout when remaining is untracked

Returns:

  • (Integer)

    milliseconds available for this attempt

Raises:



115
116
117
# File 'lib/html2rss/request_service/budget.rb', line 115

def effective_timeout_ms(fallback:)
  (effective_timeout_seconds(fallback:) * 1000).to_i
end

#effective_timeout_seconds(fallback:) ⇒ Float

Resolves wall-clock seconds for the next adapter attempt.

Prefers the tracked deadline remainder; falls back to the policy total when this Budget was constructed without a timeout (common in unit tests).

Parameters:

  • fallback (Numeric)

    policy total timeout when remaining is untracked

Returns:

  • (Float)

    seconds available for this attempt

Raises:



104
105
106
107
108
109
# File 'lib/html2rss/request_service/budget.rb', line 104

def effective_timeout_seconds(fallback:)
  timeout = remaining_timeout_seconds || fallback
  raise RequestTimedOut, 'Request timed out' if timeout <= 0

  timeout.to_f
end

#elapsed_secondsFloat

Returns seconds since this budget was created.

Returns:

  • (Float)

    seconds since this budget was created



91
92
93
# File 'lib/html2rss/request_service/budget.rb', line 91

def elapsed_seconds
  Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start_time
end

#remainingInteger

Returns HTTP request slots still available.

Returns:

  • (Integer)

    HTTP request slots still available



64
65
66
# File 'lib/html2rss/request_service/budget.rb', line 64

def remaining
  remaining_requests
end

#remaining_interactionsInteger

Returns preload interaction slots still available.

Returns:

  • (Integer)

    preload interaction slots still available



76
77
78
# File 'lib/html2rss/request_service/budget.rb', line 76

def remaining_interactions
  @mutex.synchronize { @remaining_interactions }
end

#remaining_requestsInteger

Returns HTTP request slots still available.

Returns:

  • (Integer)

    HTTP request slots still available



70
71
72
# File 'lib/html2rss/request_service/budget.rb', line 70

def remaining_requests
  @mutex.synchronize { @remaining_requests }
end

#remaining_timeout_secondsFloat?

Returns the remaining timeout in seconds, or nil if not tracked.

Returns:

  • (Float, nil)

    the remaining timeout in seconds, or nil if not tracked



82
83
84
85
86
87
# File 'lib/html2rss/request_service/budget.rb', line 82

def remaining_timeout_seconds
  return unless @total_timeout_seconds

  remaining = @total_timeout_seconds - elapsed_seconds
  [remaining, 0.0].max
end