Raise Rouge Fallthrough without capturing a backtrace

What does this MR do and why?

Restores pre-Rouge 5 performance for syntax-highlighted diffs and blobs. Rouge 5 raises a Fallthrough exception for control flow, up to three times per non-keyword identifier in Ruby files, and each raise captures a full backtrace. Inside a Rails request that is expensive: slower diff rendering, roughly 3x the GC time, and about 100 MiB of extra RSS per Puma worker.

This adds an initializer that raises Fallthrough with an empty backtrace. Highlighted output is unchanged. The spec fails once Rouge fixes this upstream, so the patch gets removed.

Details

Root cause. rouge#2210 added RegexLexer#fallthrough! (raise Fallthrough) so lexers could replace large keyword regexes with runtime Set lookups. rouge#2225 moved the Ruby lexer to it; Go, Swift, Perl, LiveScript and others use it too. Its performance was measured from a shallow stack, where backtrace capture is cheap.

Why a fresh exception instead of a shared one. The issue suggests raising a pre-built instance. Raising an exception inside a rescue block sets its cause, so a shared instance would be mutated across Puma threads and could retain unrelated exceptions. raise Fallthrough, nil, [] allocates a new exception with an explicit empty backtrace, so Ruby skips capture.

Micro-benchmark (200k raises at stack depth 300, Ruby 3.4):

Strategy Time
raise Fallthrough (current) 0.40s
raise Fallthrough, nil, [] (this MR) 0.08s
Shared pre-built instance 0.06s

Lexing 52 files from app/services/merge_requests/ 5 times at stack depth 300: 0.90s and 25 ms GC unpatched, 0.76s and 18 ms GC patched. Real request stacks are deeper, so the effect in production is larger. The issue's in-process measurements on the 10k environment show 4.7-level wall time, GC and RSS with the backtrace-free raise.

Removal. spec/initializers/rouge_fallthrough_patch_spec.rb checks that the unpatched method still captures a backtrace and fails with a removal message once it doesn't.

References

How to set up and validate locally

bundle exec rspec spec/initializers/rouge_fallthrough_patch_spec.rb

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

Closes #634283 (closed)

🤖 Generated with Claude Code

Merge request reports

Loading
Loading