From ebd356d3156d1713e76466010c15ec7e5232102c Mon Sep 17 00:00:00 2001 From: Jerry Zhao Date: Fri, 10 Jul 2026 09:37:22 +0000 Subject: [PATCH] fix: reject Floor in pinned mode instead of leaking GC-pool segments A FloorRequest resolves a large-value floor entry into its own IoStringBuffer. In KV Cache pinned mode GetGlobalRegisteredMemory() returns the shard-private GC pool, whose segments no public API can Recycle, so each large-value Floor permanently drains it -- eventually stalling segment compaction and hanging later large-value Floors. Read already rejects its IoStringBuffer overload in pinned mode for the same reason; Floor has no pinned-destination variant, so reject it too. Regression test issues a Floor against a pinned-mode store and expects InvalidArgs (returns NoError, leaking, without the guard). --- src/storage/shard.cpp | 7 +++++++ tests/large_value_e2e.cpp | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/storage/shard.cpp b/src/storage/shard.cpp index 02296430..75d1444e 100644 --- a/src/storage/shard.cpp +++ b/src/storage/shard.cpp @@ -641,6 +641,13 @@ bool Shard::ProcessReq(KvRequest *req) auto lbd = [task, req]() -> KvError { auto floor_req = static_cast(req); + // Floor's large_value_ is an IoStringBuffer; in pinned mode its + // fragments come from the unrecyclable shard-private GC pool, same + // as the Read IoStringBuffer arm rejected above. + if (!shard->store_->Options().pinned_memory_chunks.empty()) + { + return KvError::InvalidArgs; + } KvError err = task->Floor(req->TableId(), floor_req->Key(), floor_req->floor_key_, diff --git a/tests/large_value_e2e.cpp b/tests/large_value_e2e.cpp index 3a404d85..71bc98f9 100644 --- a/tests/large_value_e2e.cpp +++ b/tests/large_value_e2e.cpp @@ -714,6 +714,28 @@ TEST_CASE("EloqStore pinned write + read round-trips with metadata", CleanupStore(opts); } +// Floor has only an IoStringBuffer destination, which draws from the +// unrecyclable shard-private GC pool in pinned mode, so it is rejected. +TEST_CASE("EloqStore rejects Floor in pinned mode", "[large-value-e2e][pinned]") +{ + PinnedHarness harness; + auto opts = MakePinnedOpts(harness); + auto *store = InitStore(opts); + + eloqstore::TableIdent tbl{"pinned-floor", 0}; + const size_t value_size = harness.SegmentSize() * 3; + auto write_buf = harness.AllocateSegmentAligned(value_size); + WritePinned(store, harness, tbl, "k", write_buf, 0xf0u); + + eloqstore::FloorRequest r; + r.SetArgs(tbl, "k"); + store->ExecSync(&r); + REQUIRE(r.Error() == eloqstore::KvError::InvalidArgs); + + store->Stop(); + CleanupStore(opts); +} + TEST_CASE("EloqStore mixed metadata / no-metadata large values via overload A", "[large-value-e2e][pinned]") {