You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix#1629: resolve memory leaks in LazilyTransformingAstService and UndoRedo (#1638)
## Summary
Fixes#1629. Closes#1633. Closes#1634.
Two unbounded memory leaks in long-running HyperFormula instances:
- **`LTAS.transformations[]`** grew linearly with every structural
operation (addRows, removeRows, moveCells, etc.) and was never cleaned
up. Fixed by introducing **threshold-based compaction** with
`versionOffset` — once 50+ transformations accumulate, all consumers
(FormulaVertex, ColumnIndex) are force-updated, then the array is
released while the logical version remains monotonically increasing.
- **`UndoRedo.oldData`** grew linearly even when entries were evicted
from the undo stack. Fixed by tracking which LTAS versions each
`UndoEntry` references (`getReferencedOldDataVersions()`), cleaning up
on eviction/clear, guarding against writes when `undoLimit === 0`, and
running orphan cleanup after compaction to handle a race condition where
lazy-apply re-inserts already-evicted keys.
### Changed files
| File | Change |
|------|--------|
| `LazilyTransformingAstService.ts` | `versionOffset`, `compact()`,
`needsCompaction()` with threshold=50, offset-aware iteration |
| `UndoRedo.ts` | `getReferencedOldDataVersions()` on interface + 7
subclasses, eviction cleanup, `undoLimit===0` guard,
`cleanupOrphanedOldData()`, `forceApply` parity in
`undoMoveRows`/`undoMoveColumns` |
| `HyperFormula.ts` | Compaction trigger in
`recomputeIfDependencyGraphNeedsIt()` |
| `ColumnIndex.ts` | `forceApplyPostponedTransformations()` — iterates
all ValueIndex entries |
| `ColumnBinarySearch.ts` | No-op `forceApplyPostponedTransformations()`
|
| `SearchStrategy.ts` | New method on `ColumnSearchStrategy` interface |
| `Operations.ts` | Added `columnSearch.forceApply` to undo path (no
compact — centralized in HyperFormula.ts) |
### What is NOT fixed here
- **Parser cache** (`ParserWithCaching`) — unbounded growth tracked
separately in #1635
## Test plan
- 18 dedicated tests in `hyperformula-tests` — see companion PR in that
repo
- Full test suite: **480 suites / 5396 tests passed**
- Benchmark validated threshold=50 as optimal (eager compaction is ~18×
slower on a 2.5k formula sheet)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Touches core recalculation/transform and undo/redo paths; while aimed
at memory safety, compaction/cleanup timing could affect formula
correctness or undo behavior in edge cases.
>
> **Overview**
> Prevents unbounded memory growth in long-running engines by **adding
threshold-based compaction** of lazy formula transformations and by
**cleaning up undo snapshot (`oldData`) entries** when undo/redo stack
entries are cleared or evicted.
>
> Introduces new config `maxPendingLazyTransformations` (default `50`)
and wires it into engine construction; when the threshold is reached,
`HyperFormula` forces pending transformations to be applied (dependency
graph + column search), compacts the transformation history, and prunes
orphaned `UndoRedo.oldData`. Column search strategies now expose
`forceApplyPostponedTransformations()` (real implementation for
`ColumnIndex`, no-op for binary search), and undo for move operations
ensures postponed transformations are applied before restoring old data.
Documentation and changelog are updated accordingly.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
d8ebe1d. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+3Lines changed: 3 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,10 +9,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
9
9
10
10
### Added
11
11
12
+
- Added `maxPendingLazyTransformations` configuration option to control memory usage by limiting accumulated transformations before cleanup. [#1629](https://github.com/handsontable/hyperformula/issues/1629)
12
13
- Added a new function: TEXTJOIN. [#1640](https://github.com/handsontable/hyperformula/pull/1640)
13
14
14
15
### Fixed
15
16
17
+
- Fixed a memory leak in `LazilyTransformingAstService` where the transformations array grew unboundedly, causing increasing memory usage over time. [#1629](https://github.com/handsontable/hyperformula/issues/1629)
18
+
- Fixed a memory leak in `UndoRedo` where `oldData` entries for evicted undo stack entries were never cleaned up, causing increasing memory usage over time. [#1629](https://github.com/handsontable/hyperformula/issues/1629)
16
19
- Fixed the IRR function returning `#NUM!` error when the initial investment significantly exceeds the sum of returns. [#1628](https://github.com/handsontable/hyperformula/issues/1628)
17
20
- Fixed the ADDRESS function ignoring `defaultValue` when arguments are syntactically empty (e.g., `=ADDRESS(2,3,,FALSE())`). [#1632](https://github.com/handsontable/hyperformula/issues/1632)
0 commit comments