Skip to content

Add cumulative resource score tracking across partial renders#2058

Merged
ianks merged 2 commits intomainfrom
ianks/cumulative-scores
Mar 18, 2026
Merged

Add cumulative resource score tracking across partial renders#2058
ianks merged 2 commits intomainfrom
ianks/cumulative-scores

Conversation

@ianks
Copy link
Contributor

@ianks ianks commented Mar 18, 2026

Situation

Template#render unconditionally calls resource_limits.reset before rendering, which zeros render_score and assign_score. Since both {% render %} and {% include %} go through Template#render for each partial, the per-template scores reset on every partial invocation — making render_score_limit and assign_score_limit ineffective at bounding total work across partial renders. A template split across many small partials can execute unbounded cumulative work while each individual partial stays under the limit.

Execution

Added cumulative_render_score and cumulative_assign_score counters to ResourceLimits that piggyback on the existing increment_render_score/increment_assign_score methods but survive reset() calls. Two new optional limits (cumulative_render_score_limit, cumulative_assign_score_limit) are checked alongside the existing per-template limits. When unset (nil), the cumulative checks are skipped entirely — zero behavior change for existing users.

env = Liquid::Environment.build(
  default_resource_limits: {
    render_score_limit: 100_000,              # existing per-template limit (unchanged)
    cumulative_render_score_limit: 1_000_000, # new: caps total work across all partials
  },
)

Backwards compatibility

The existing render_score, assign_score, render_score_limit, assign_score_limit, and reset() behavior are completely unchanged — per-template scores still reset on every Template#render call exactly as before. The new cumulative fields default to nil (limits) and 0 (counters), so applications that never set cumulative_*_limit will never trigger the new code paths.

Add cumulative_render_score and cumulative_assign_score counters to
ResourceLimits that accumulate across reset() calls, with optional
cumulative_render_score_limit and cumulative_assign_score_limit to
cap total work across all partial renders.

Also add a reached? check in BlockBody's render loop so that once a
cumulative limit triggers, the parent template stops processing
further nodes.

Bump version to 5.12.0.
break if context.interrupt? # might have happened in a for-block
end
idx += 1
break if context.resource_limits.reached?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this necessary? why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes it so we immediately stop rendering instead of letting each subsequent partial reset the flag and blow the limit again in a loop

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be better to address this in ResourceLimits#reset

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in a911767

Instead of checking reached? in BlockBody's render loop, enforce
cumulative limits in reset() itself. Since reset() is called before
the begin/rescue MemoryError block in Template#render, the raise
propagates to the parent naturally — no changes to BlockBody needed.
@ianks ianks requested a review from Maaarcocr March 18, 2026 15:49
@ianks ianks merged commit 59d8d0d into main Mar 18, 2026
13 checks passed
@ianks ianks deleted the ianks/cumulative-scores branch March 18, 2026 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants