Repository navigation
bug(MatSlider): showTickMarks can exhaust memory during SSR #34036
Description
Is this a regression?
- Yes, this behavior used to work in the previous version
The previous version in which this bug was not present was
No response
Description
ngAfterViewInit() measures the slider only in the browser, but it builds the tick marks on the server too:
if (this._platform.isBrowser) {
this._updateDimensions();
}
// ...
this._updateTickMarkUI();_updateTickMarkUINonRange() allocates one entry per tick mark:
this._tickMarks = Array(numActive)
.fill(_MatTickMark.ACTIVE)
.concat(Array(numInactive).fill(_MatTickMark.INACTIVE));The template only renders them @if (_cachedWidth), and the server never sets _cachedWidth. So during SSR the array is built and thrown away, and nothing bounds its size: it is (max - min) / step.
A slider whose bounds come from the URL is enough:
@Component({
selector: 'price-range',
imports: [MatSliderModule],
template: `
<mat-slider [min]="min" [max]="max" [step]="step" showTickMarks>
<input matSliderThumb />
</mat-slider>
`,
})
export class PriceRange {
private readonly params = inject(ActivatedRoute).snapshot.queryParamMap;
readonly min = Number(this.params.get('min') ?? '0');
readonly max = Number(this.params.get('max') ?? '100');
readonly step = Number(this.params.get('step') ?? '1');
}I served it with RenderMode.Server and compared /?max=1e9 with a control of the same size, /?max=1e2. Each cell is three fresh workers, and fatal means a V8 FATAL ERROR with exit code 134:
| Heap | ?max=1e2 |
?max=1e9 |
|---|---|---|
| 256 MiB | 3/3 200 | 3/3 fatal, 3.0 s |
| 1 GiB | 3/3 200 | 3/3 fatal, 5.5 s |
| 4 GiB | 3/3 200 | 3/3 fatal, 5.8 s |
| 16 GiB | 3/3 200 | 3/3 fatal, 5.6 s |
| default (4,288 MiB) | 3/3 200 | 3/3 fatal, 5.6 s |
A bigger heap doesn't help. Above 2^25 entries Array(n) switches to dictionary elements, and from about 2.5e8 entries the dictionary hits V8's hash table limit before the heap fills up:
FATAL ERROR: invalid table size Allocation failed - JavaScript heap out of memory
?min=-1e9 and ?step=1e-7 also reach a billion tick marks and are fatal too. The values don't need to look odd either: ?max=1000000&step=0.01, a price with cents, is 1e8 tick marks and kills a 512 MiB worker.
#26322 fixed #26317 by skipping the array when showTickMarks is off. With it on, the count is still unbounded, and #29496 is the browser side of the same problem. #33242 rewrites the method as a push loop over Math.floor((max - min) / step) + 1, so the count doesn't change. I applied it on top of 22.2.5: ?max=1e9 still kills a 256 MiB worker, ?max=3e7 now does too, and ?step=1e-309 goes from an immediate RangeError to a fatal error.
Returning early from _updateTickMarkUI() until _cachedWidth is set, which the template already requires, makes every request above return 200. The browser still builds the full array once the slider has a width.
As a note I found this while adapting MatSlider for a design system, using its code as the base. In an e-commerce app, passing the price filter's value or range through query parameters could OOM the SSR worker, so I would think this is a normal real use case.
Reproduction
See https://github.com/SkyZeroZx/angular-material-slider-ssr-oom-lab
Steps to reproduce:
npm ci && npm run buildnpm run serve:ssr:srr-material-oomcurl "http://localhost:4000/?max=1e9"
Expected Behavior
The server renders the slider without building tick marks it can't render, and the worker keeps serving other requests.
Actual Behavior
The SSR worker aborts with a V8 fatal error (exit code 134), and every request in flight on it fails with it.
Environment
- Angular: 22.2.4,
@angular/ssr22.2.5, Node.js 24.16.0 (also 21.2.25 and 20.3.34) - CDK/Material: 22.2.5 (also 21.2.14 and 20.2.14).
mainand 22.3.0-next.4 have the same code. - Browser(s): n/a, server-side rendering
- Operating System (e.g. Windows, macOS, Ubuntu): Windows 11 x64
Activity
fix(material/slider): prevent out of memory errors from tick marks on…