Skip to content

KLL: keep the sketch valid when a merge fails to allocate - #574

Open
leerho wants to merge 1 commit into
masterfrom
kll-merge-allocation-failure
Open

leerho wants to merge 1 commit into
masterfrom
kll-merge-allocation-failure

Conversation

@leerho

@leerho leerho commented Nov 12, 2026

Copy link
Copy Markdown
Member

Problem

kll_sketch::merge() doesn't keep the basic exception guarantee when an allocation fails while merging levels above level zero. merge_higher_levels() moves this sketch's items into a work buffer, frees the item storage, and only then allocates the new storage. If that allocation throws, items_ still points at the freed storage, and the sketch's levels still describe items that were moved out. Using or destroying the sketch afterwards is a use-after-free. ASan reports it with an allocator that fails on purpose.

Only the application's allocator can trigger this, by failing during a merge in estimation mode. The serialized format is not involved. The same code is in 5.3.0 and 5.3.1.

Fix

The number of levels and items after compression depends only on the level sizes, not on the item values. A new helper, kll_helper::predict_compress(), computes that result from the level sizes, mirroring the level loop of general_compress(). merge_higher_levels() now does all of its allocations before it moves any items:

  • the new item storage, if the capacity changes
  • the space for the final levels, using levels_.reserve()

It then checks that general_compress() produced the predicted result. The merge results are unchanged.

Tests

  • New kll_sketch_merge_failure_test.cpp. It fails each allocation of a merge in turn. Targets are empty, in exact mode, or in estimation mode, and sources are smaller or larger than the target, passed as lvalues and as rvalues. After each failure, the test checks that the target can still be queried, updated, merged into and round-tripped. It also checks that an lvalue source is unchanged and that nothing leaks. Without the fix, this test fails under ASan with the use-after-free.
  • The whole KLL suite passes under ASan and UBSan, and the prediction check held in every merge. I also checked it on 9,600 randomized merges with k from 8 to 1000 and n up to 2·10^6.

Related

🤖 Generated with Claude Code

merge_higher_levels() moved the items of this sketch into a work buffer,
freed the item storage, and only then allocated the new storage. If that
allocation failed, items_ was left pointing at freed memory.

The capacity after compression depends only on the level sizes, so a new
helper, kll_helper::predict_compress(), computes it before any item moves.
The merge now allocates the new item storage and reserves the levels first,
and checks that general_compress() matches the prediction.

The new test fails each allocation of a merge in turn, for several target
and source sizes, and checks that the target stays usable and nothing leaks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@leerho
leerho requested review from proost and tisonkun November 12, 2026 19:43
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 43288534927

Warning

No base build found for commit 386f335 on master.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 82.892%

Details

  • Patch coverage: 1 uncovered change across 1 file (41 of 42 lines covered, 97.62%).

Uncovered Changes

File Changed Covered %
kll/include/kll_sketch_impl.hpp 18 17 94.44%
Total (2 files) 42 41 97.62%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 21622
Covered Lines: 17923
Line Coverage: 82.89%
Coverage Strength: 1377831.26 hits per line

💛 - Coveralls

This branch has not been deployed

No deployments
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