From 52ca239a1b1dc70c7e7a5a578256257eaf1b5842 Mon Sep 17 00:00:00 2001 From: Jialiang Tan Date: Wed, 4 Oct 2023 13:36:37 -0700 Subject: [PATCH] Add stats collection to memory reclaimer --- velox/common/memory/MemoryArbitrator.cpp | 23 +++++++++++++------ velox/common/memory/MemoryArbitrator.h | 15 ++++++++++-- velox/common/memory/MemoryPool.cpp | 6 +++-- velox/common/memory/MemoryPool.h | 7 ++++-- velox/common/memory/SharedArbitrator.cpp | 3 ++- velox/common/memory/SharedArbitrator.h | 1 + .../memory/tests/MemoryArbitratorTest.cpp | 6 +++-- .../memory/tests/MockSharedArbitratorTest.cpp | 3 ++- .../memory/tests/SharedArbitratorTest.cpp | 6 +++-- velox/exec/HashAggregation.cpp | 4 +++- velox/exec/HashAggregation.h | 3 ++- velox/exec/HashBuild.cpp | 4 +++- velox/exec/HashBuild.h | 3 ++- velox/exec/HashJoinBridge.cpp | 7 +++--- velox/exec/HashJoinBridge.h | 5 +++- velox/exec/Operator.cpp | 5 ++-- velox/exec/Operator.h | 9 ++++++-- velox/exec/OrderBy.cpp | 4 +++- velox/exec/OrderBy.h | 3 ++- velox/exec/TableWriter.cpp | 3 ++- velox/exec/TableWriter.h | 5 +++- velox/exec/Task.cpp | 5 ++-- velox/exec/Task.h | 5 +++- 23 files changed, 97 insertions(+), 38 deletions(-) diff --git a/velox/common/memory/MemoryArbitrator.cpp b/velox/common/memory/MemoryArbitrator.cpp index 45270870368a8..f500a31f70e93 100644 --- a/velox/common/memory/MemoryArbitrator.cpp +++ b/velox/common/memory/MemoryArbitrator.cpp @@ -184,7 +184,8 @@ bool MemoryReclaimer::reclaimableBytes( return reclaimable; } -uint64_t MemoryReclaimer::reclaim(MemoryPool* pool, uint64_t targetBytes) { +uint64_t +MemoryReclaimer::reclaim(MemoryPool* pool, uint64_t targetBytes, Stats& stats) { if (pool->kind() == MemoryPool::Kind::kLeaf) { return 0; } @@ -214,7 +215,7 @@ uint64_t MemoryReclaimer::reclaim(MemoryPool* pool, uint64_t targetBytes) { uint64_t reclaimedBytes{0}; for (const auto& candidate : candidates) { - const auto bytes = candidate.pool->reclaim(targetBytes); + const auto bytes = candidate.pool->reclaim(targetBytes, stats); reclaimedBytes += bytes; if (targetBytes != 0) { if (bytes >= targetBytes) { @@ -254,7 +255,8 @@ MemoryArbitrator::Stats::Stats( uint64_t _numReclaimedBytes, uint64_t _maxCapacityBytes, uint64_t _freeCapacityBytes, - uint64_t _reclaimTimeUs) + uint64_t _reclaimTimeUs, + uint64_t _numNonReclaimableAttempts) : numRequests(_numRequests), numSucceeded(_numSucceeded), numAborted(_numAborted), @@ -265,15 +267,17 @@ MemoryArbitrator::Stats::Stats( numReclaimedBytes(_numReclaimedBytes), maxCapacityBytes(_maxCapacityBytes), freeCapacityBytes(_freeCapacityBytes), - reclaimTimeUs(_reclaimTimeUs) {} + reclaimTimeUs(_reclaimTimeUs), + numNonReclaimableAttempts(_numNonReclaimableAttempts) {} std::string MemoryArbitrator::Stats::toString() const { return fmt::format( - "STATS[numRequests {} numSucceeded {} numAborted {} numFailures {} queueTime {} arbitrationTime {} reclaimTime {} shrunkMemory {} reclaimedMemory {} maxCapacity {} freeCapacity {}]", + "STATS[numRequests {} numSucceeded {} numAborted {} numFailures {} numNonReclaimableAttempts {} queueTime {} arbitrationTime {} reclaimTime {} shrunkMemory {} reclaimedMemory {} maxCapacity {} freeCapacity {}]", numRequests, numSucceeded, numAborted, numFailures, + numNonReclaimableAttempts, succinctMicros(queueTimeUs), succinctMicros(arbitrationTimeUs), succinctMicros(reclaimTimeUs), @@ -297,6 +301,8 @@ MemoryArbitrator::Stats MemoryArbitrator::Stats::operator-( result.maxCapacityBytes = maxCapacityBytes; result.freeCapacityBytes = freeCapacityBytes; result.reclaimTimeUs = reclaimTimeUs - other.reclaimTimeUs; + result.numNonReclaimableAttempts = + numNonReclaimableAttempts - other.numNonReclaimableAttempts; return result; } @@ -312,7 +318,8 @@ bool MemoryArbitrator::Stats::operator==(const Stats& other) const { numReclaimedBytes, maxCapacityBytes, freeCapacityBytes, - reclaimTimeUs) == + reclaimTimeUs, + numNonReclaimableAttempts) == std::tie( other.numRequests, other.numSucceeded, @@ -324,7 +331,8 @@ bool MemoryArbitrator::Stats::operator==(const Stats& other) const { other.numReclaimedBytes, other.maxCapacityBytes, other.freeCapacityBytes, - other.reclaimTimeUs); + other.reclaimTimeUs, + other.numNonReclaimableAttempts); } bool MemoryArbitrator::Stats::operator!=(const Stats& other) const { @@ -355,6 +363,7 @@ bool MemoryArbitrator::Stats::operator<(const Stats& other) const { UPDATE_COUNTER(numShrunkBytes); UPDATE_COUNTER(numReclaimedBytes); UPDATE_COUNTER(reclaimTimeUs); + UPDATE_COUNTER(numNonReclaimableAttempts); #undef UPDATE_COUNTER VELOX_CHECK( !((gtCount > 0) && (ltCount > 0)), diff --git a/velox/common/memory/MemoryArbitrator.h b/velox/common/memory/MemoryArbitrator.h index 93c8a742937e8..b151f0562245e 100644 --- a/velox/common/memory/MemoryArbitrator.h +++ b/velox/common/memory/MemoryArbitrator.h @@ -176,6 +176,9 @@ class MemoryArbitrator { /// The sum of all reclaim operation durations during arbitration in /// microseconds. uint64_t reclaimTimeUs{0}; + /// The total number of times of the reclaim attempts that end up failing + /// due to reclaiming at non-reclaimable stage. + uint64_t numNonReclaimableAttempts{0}; Stats( uint64_t _numRequests, @@ -188,7 +191,8 @@ class MemoryArbitrator { uint64_t _numReclaimedBytes, uint64_t _maxCapacityBytes, uint64_t _freeCapacityBytes, - uint64_t _reclaimTimeUs); + uint64_t _reclaimTimeUs, + uint64_t _numNonReclaimableAttempts); Stats() = default; @@ -252,6 +256,12 @@ FOLLY_ALWAYS_INLINE std::ostream& operator<<( /// through techniques such as disks spilling. class MemoryReclaimer { public: + struct Stats { + /// The total number of times of the reclaim attempts that end up failing + /// due to reclaiming at non-reclaimable stage. + std::atomic numNonReclaimableAttempts{0}; + }; + virtual ~MemoryReclaimer() = default; static std::unique_ptr create(); @@ -286,7 +296,8 @@ class MemoryReclaimer { /// memory bytes but there is no guarantees. If 'targetBytes' is zero, then it /// reclaims all the reclaimable memory from the memory 'pool'. The function /// returns the actual reclaimed memory bytes. - virtual uint64_t reclaim(MemoryPool* pool, uint64_t targetBytes); + virtual uint64_t + reclaim(MemoryPool* pool, uint64_t targetBytes, Stats& stats); /// Invoked by the memory arbitrator to abort memory 'pool' and the associated /// query execution when encounters non-recoverable memory reclaim error or diff --git a/velox/common/memory/MemoryPool.cpp b/velox/common/memory/MemoryPool.cpp index 63c7dd49515d2..ab2c3b026f471 100644 --- a/velox/common/memory/MemoryPool.cpp +++ b/velox/common/memory/MemoryPool.cpp @@ -923,11 +923,13 @@ bool MemoryPoolImpl::reclaimableBytes(uint64_t& reclaimableBytes) const { return reclaimer()->reclaimableBytes(*this, reclaimableBytes); } -uint64_t MemoryPoolImpl::reclaim(uint64_t targetBytes) { +uint64_t MemoryPoolImpl::reclaim( + uint64_t targetBytes, + memory::MemoryReclaimer::Stats& stats) { if (reclaimer() == nullptr) { return 0; } - return reclaimer()->reclaim(this, targetBytes); + return reclaimer()->reclaim(this, targetBytes, stats); } void MemoryPoolImpl::enterArbitration() { diff --git a/velox/common/memory/MemoryPool.h b/velox/common/memory/MemoryPool.h index b65dfbc45e748..b12e0799a29c2 100644 --- a/velox/common/memory/MemoryPool.h +++ b/velox/common/memory/MemoryPool.h @@ -408,7 +408,9 @@ class MemoryPool : public std::enable_shared_from_this { /// noop if the reclaimer is not set, otherwise invoke the reclaimer's /// corresponding method. The function returns the actually freed capacity /// from the root of this memory pool. - virtual uint64_t reclaim(uint64_t targetBytes) = 0; + virtual uint64_t reclaim( + uint64_t targetBytes, + memory::MemoryReclaimer::Stats& stats) = 0; /// Invoked by the memory arbitrator to abort a root memory pool. The function /// forwards the request to the corresponding query object to abort its @@ -627,7 +629,8 @@ class MemoryPoolImpl : public MemoryPool { bool reclaimableBytes(uint64_t& reclaimableBytes) const override; - uint64_t reclaim(uint64_t targetBytes) override; + uint64_t reclaim(uint64_t targetBytes, memory::MemoryReclaimer::Stats& stats) + override; uint64_t shrink(uint64_t targetBytes = 0) override; diff --git a/velox/common/memory/SharedArbitrator.cpp b/velox/common/memory/SharedArbitrator.cpp index ff51e8c609960..460ccec2abdaf 100644 --- a/velox/common/memory/SharedArbitrator.cpp +++ b/velox/common/memory/SharedArbitrator.cpp @@ -404,7 +404,7 @@ uint64_t SharedArbitrator::reclaim( try { freedBytes = pool->shrink(targetBytes); if (freedBytes < targetBytes) { - pool->reclaim(targetBytes - freedBytes); + pool->reclaim(targetBytes - freedBytes, reclaimerStats_); } } catch (const std::exception& e) { VELOX_MEM_LOG(ERROR) << "Failed to reclaim from memory pool " @@ -492,6 +492,7 @@ MemoryArbitrator::Stats SharedArbitrator::statsLocked() const { stats.maxCapacityBytes = capacity_; stats.freeCapacityBytes = freeCapacity_; stats.reclaimTimeUs = reclaimTimeUs_; + stats.numNonReclaimableAttempts = reclaimerStats_.numNonReclaimableAttempts; return stats; } diff --git a/velox/common/memory/SharedArbitrator.h b/velox/common/memory/SharedArbitrator.h index e6dfd31efcea2..8e17e4f541609 100644 --- a/velox/common/memory/SharedArbitrator.h +++ b/velox/common/memory/SharedArbitrator.h @@ -200,5 +200,6 @@ class SharedArbitrator : public MemoryArbitrator { tsan_atomic numShrunkBytes_{0}; tsan_atomic numReclaimedBytes_{0}; tsan_atomic reclaimTimeUs_{0}; + MemoryReclaimer::Stats reclaimerStats_; }; } // namespace facebook::velox::memory diff --git a/velox/common/memory/tests/MemoryArbitratorTest.cpp b/velox/common/memory/tests/MemoryArbitratorTest.cpp index 22367a5e19c8c..edac86f82cc48 100644 --- a/velox/common/memory/tests/MemoryArbitratorTest.cpp +++ b/velox/common/memory/tests/MemoryArbitratorTest.cpp @@ -338,8 +338,10 @@ class MockLeafMemoryReclaimer : public MemoryReclaimer { return true; } - uint64_t reclaim(MemoryPool* /*unused*/, uint64_t targetBytes) noexcept - override { + uint64_t reclaim( + MemoryPool* /*unused*/, + uint64_t targetBytes, + Stats& stats) noexcept override { std::lock_guard l(mu_); uint64_t reclaimedBytes{0}; while (!allocations_.empty() && diff --git a/velox/common/memory/tests/MockSharedArbitratorTest.cpp b/velox/common/memory/tests/MockSharedArbitratorTest.cpp index 72d8047130502..8f092cc64e204 100644 --- a/velox/common/memory/tests/MockSharedArbitratorTest.cpp +++ b/velox/common/memory/tests/MockSharedArbitratorTest.cpp @@ -161,7 +161,8 @@ class MockMemoryOperator { return op_->reclaimableBytes(pool, reclaimableBytes); } - uint64_t reclaim(MemoryPool* pool, uint64_t targetBytes) override { + uint64_t reclaim(MemoryPool* pool, uint64_t targetBytes, Stats& stats) + override { ++numReclaims_; if (!reclaimable_) { return 0; diff --git a/velox/common/memory/tests/SharedArbitratorTest.cpp b/velox/common/memory/tests/SharedArbitratorTest.cpp index 4a03e909aa71d..f75c67759206f 100644 --- a/velox/common/memory/tests/SharedArbitratorTest.cpp +++ b/velox/common/memory/tests/SharedArbitratorTest.cpp @@ -163,7 +163,8 @@ class FakeMemoryOperator : public Operator { return canReclaim_; } - void reclaim(uint64_t targetBytes) override { + void reclaim(uint64_t targetBytes, memory::MemoryReclaimer::Stats& stats) + override { VELOX_CHECK(canReclaim()); auto* driver = operatorCtx_->driver(); VELOX_CHECK(!driver->state().isOnThread() || driver->state().isSuspended); @@ -575,7 +576,8 @@ class TestMemoryReclaimer : public MemoryReclaimer { TestMemoryReclaimer(std::function reclaimCb) : reclaimCb_(std::move(reclaimCb)) {} - uint64_t reclaim(MemoryPool* pool, uint64_t targetBytes) override { + uint64_t reclaim(MemoryPool* pool, uint64_t targetBytes, Stats& stats) + override { if (pool->kind() == MemoryPool::Kind::kLeaf) { return 0; } diff --git a/velox/exec/HashAggregation.cpp b/velox/exec/HashAggregation.cpp index cf006caa2f5e3..6ce755b67dd4c 100644 --- a/velox/exec/HashAggregation.cpp +++ b/velox/exec/HashAggregation.cpp @@ -481,7 +481,9 @@ bool HashAggregation::isFinished() { return finished_; } -void HashAggregation::reclaim(uint64_t targetBytes) { +void HashAggregation::reclaim( + uint64_t targetBytes, + memory::MemoryReclaimer::Stats& stats) { VELOX_CHECK(canReclaim()); auto* driver = operatorCtx_->driver(); diff --git a/velox/exec/HashAggregation.h b/velox/exec/HashAggregation.h index ea2e715287434..51e01897fb02f 100644 --- a/velox/exec/HashAggregation.h +++ b/velox/exec/HashAggregation.h @@ -43,7 +43,8 @@ class HashAggregation : public Operator { bool isFinished() override; - void reclaim(uint64_t targetBytes) override; + void reclaim(uint64_t targetBytes, memory::MemoryReclaimer::Stats& stats) + override; void close() override; diff --git a/velox/exec/HashBuild.cpp b/velox/exec/HashBuild.cpp index b50c82fc1514c..0119ba83d340b 100644 --- a/velox/exec/HashBuild.cpp +++ b/velox/exec/HashBuild.cpp @@ -1070,7 +1070,9 @@ bool HashBuild::testingTriggerSpill() { spillConfig()->testSpillPct; } -void HashBuild::reclaim(uint64_t /*unused*/) { +void HashBuild::reclaim( + uint64_t /*unused*/, + memory::MemoryReclaimer::Stats& stats) { VELOX_CHECK(canReclaim()); auto* driver = operatorCtx_->driver(); diff --git a/velox/exec/HashBuild.h b/velox/exec/HashBuild.h index 35f3e29e14dab..f94025d01f643 100644 --- a/velox/exec/HashBuild.h +++ b/velox/exec/HashBuild.h @@ -77,7 +77,8 @@ class HashBuild final : public Operator { bool isFinished() override; - void reclaim(uint64_t targetBytes) override; + void reclaim(uint64_t targetBytes, memory::MemoryReclaimer::Stats& stats) + override; void abort() override; diff --git a/velox/exec/HashJoinBridge.cpp b/velox/exec/HashJoinBridge.cpp index daa5ee4bb29a1..8e8d585178aaf 100644 --- a/velox/exec/HashJoinBridge.cpp +++ b/velox/exec/HashJoinBridge.cpp @@ -181,10 +181,11 @@ bool isLeftNullAwareJoinWithFilter( uint64_t HashJoinMemoryReclaimer::reclaim( memory::MemoryPool* pool, - uint64_t targetBytes) { + uint64_t targetBytes, + memory::MemoryReclaimer::Stats& stats) { uint64_t reclaimedBytes{0}; pool->visitChildren( - [&targetBytes, &reclaimedBytes](memory::MemoryPool* child) { + [&targetBytes, &reclaimedBytes, &stats](memory::MemoryPool* child) { VELOX_CHECK_EQ(child->kind(), memory::MemoryPool::Kind::kLeaf); // The hash probe operator do not support memory reclaim. if (!isHashBuildMemoryPool(*child)) { @@ -192,7 +193,7 @@ uint64_t HashJoinMemoryReclaimer::reclaim( } // We only need to reclaim from any one of the hash build operators // which will reclaim from all the peer hash build operators. - reclaimedBytes = child->reclaim(targetBytes); + reclaimedBytes = child->reclaim(targetBytes, stats); return false; }); return reclaimedBytes; diff --git a/velox/exec/HashJoinBridge.h b/velox/exec/HashJoinBridge.h index 4eadffb09516b..39d01d02fe30c 100644 --- a/velox/exec/HashJoinBridge.h +++ b/velox/exec/HashJoinBridge.h @@ -143,7 +143,10 @@ class HashJoinMemoryReclaimer final : public MemoryReclaimer { new HashJoinMemoryReclaimer()); } - uint64_t reclaim(memory::MemoryPool* pool, uint64_t targetBytes) final; + uint64_t reclaim( + memory::MemoryPool* pool, + uint64_t targetBytes, + memory::MemoryReclaimer::Stats& stats) final; private: HashJoinMemoryReclaimer() : MemoryReclaimer() {} diff --git a/velox/exec/Operator.cpp b/velox/exec/Operator.cpp index 172f0acb81d8a..a9491eb0e8716 100644 --- a/velox/exec/Operator.cpp +++ b/velox/exec/Operator.cpp @@ -558,7 +558,8 @@ bool Operator::MemoryReclaimer::reclaimableBytes( uint64_t Operator::MemoryReclaimer::reclaim( memory::MemoryPool* pool, - uint64_t targetBytes) { + uint64_t targetBytes, + memory::MemoryReclaimer::Stats& stats) { std::shared_ptr driver = ensureDriver(); if (FOLLY_UNLIKELY(driver == nullptr)) { return 0; @@ -575,7 +576,7 @@ uint64_t Operator::MemoryReclaimer::reclaim( TestValue::adjust( "facebook::velox::exec::Operator::MemoryReclaimer::reclaim", pool); - op_->reclaim(targetBytes); + op_->reclaim(targetBytes, stats); return pool->shrink(targetBytes); } diff --git a/velox/exec/Operator.h b/velox/exec/Operator.h index fad3883b005dc..1ec304e4cfa00 100644 --- a/velox/exec/Operator.h +++ b/velox/exec/Operator.h @@ -480,7 +480,9 @@ class Operator : public BaseRuntimeStatWriter { /// NOTE: this method doesn't return the actually freed memory bytes. The /// caller need to claim the actually freed memory space by shrinking the /// associated root memory pool's capacity accordingly. - virtual void reclaim(uint64_t targetBytes) {} + virtual void reclaim( + uint64_t targetBytes, + memory::MemoryReclaimer::Stats& stats) {} const core::PlanNodeId& planNodeId() const { return operatorCtx_->planNodeId(); @@ -559,7 +561,10 @@ class Operator : public BaseRuntimeStatWriter { const memory::MemoryPool& pool, uint64_t& reclaimableBytes) const override; - uint64_t reclaim(memory::MemoryPool* pool, uint64_t targetBytes) override; + uint64_t reclaim( + memory::MemoryPool* pool, + uint64_t targetBytes, + memory::MemoryReclaimer::Stats& stats) override; void abort(memory::MemoryPool* pool, const std::exception_ptr& /* error */) override; diff --git a/velox/exec/OrderBy.cpp b/velox/exec/OrderBy.cpp index 0d7981f3869e8..a258053ae05f1 100644 --- a/velox/exec/OrderBy.cpp +++ b/velox/exec/OrderBy.cpp @@ -76,7 +76,9 @@ void OrderBy::addInput(RowVectorPtr input) { sortBuffer_->addInput(input); } -void OrderBy::reclaim(uint64_t targetBytes) { +void OrderBy::reclaim( + uint64_t targetBytes, + memory::MemoryReclaimer::Stats& stats) { VELOX_CHECK(canReclaim()); auto* driver = operatorCtx_->driver(); diff --git a/velox/exec/OrderBy.h b/velox/exec/OrderBy.h index 2f6b8f69b0432..7d83a7cbff077 100644 --- a/velox/exec/OrderBy.h +++ b/velox/exec/OrderBy.h @@ -57,7 +57,8 @@ class OrderBy : public Operator { return finished_; } - void reclaim(uint64_t targetBytes) override; + void reclaim(uint64_t targetBytes, memory::MemoryReclaimer::Stats& stats) + override; void abort() override; diff --git a/velox/exec/TableWriter.cpp b/velox/exec/TableWriter.cpp index 0d0aa6446074b..56a04e3359950 100644 --- a/velox/exec/TableWriter.cpp +++ b/velox/exec/TableWriter.cpp @@ -280,7 +280,8 @@ bool TableWriter::MemoryReclaimer::reclaimableBytes( uint64_t TableWriter::MemoryReclaimer::reclaim( memory::MemoryPool* pool, - uint64_t /*unused*/) { + uint64_t /*unused*/, + memory::MemoryReclaimer::Stats& /*unused*/) { VELOX_CHECK(!pool->isLeaf()); return 0; } diff --git a/velox/exec/TableWriter.h b/velox/exec/TableWriter.h index d22bc5d622f48..930e52522ba15 100644 --- a/velox/exec/TableWriter.h +++ b/velox/exec/TableWriter.h @@ -144,7 +144,10 @@ class TableWriter : public Operator { const memory::MemoryPool& pool, uint64_t& reclaimableBytes) const override; - uint64_t reclaim(memory::MemoryPool* pool, uint64_t targetBytes) override; + uint64_t reclaim( + memory::MemoryPool* pool, + uint64_t targetBytes, + memory::MemoryReclaimer::Stats& stats) override; void abort(memory::MemoryPool* pool, const std::exception_ptr& /* error */) override; diff --git a/velox/exec/Task.cpp b/velox/exec/Task.cpp index 6c1ab0cb47351..7183cf0c72442 100644 --- a/velox/exec/Task.cpp +++ b/velox/exec/Task.cpp @@ -2424,7 +2424,8 @@ std::unique_ptr Task::MemoryReclaimer::create( uint64_t Task::MemoryReclaimer::reclaim( memory::MemoryPool* pool, - uint64_t targetBytes) { + uint64_t targetBytes, + memory::MemoryReclaimer::Stats& stats) { auto task = ensureTask(); if (FOLLY_UNLIKELY(task == nullptr)) { return 0; @@ -2443,7 +2444,7 @@ uint64_t Task::MemoryReclaimer::reclaim( if (task->isCancelled()) { return 0; } - return memory::MemoryReclaimer::reclaim(pool, targetBytes); + return memory::MemoryReclaimer::reclaim(pool, targetBytes, stats); } void Task::MemoryReclaimer::abort( diff --git a/velox/exec/Task.h b/velox/exec/Task.h index 3eb2ea22d8d55..53d306dcfbcf8 100644 --- a/velox/exec/Task.h +++ b/velox/exec/Task.h @@ -651,7 +651,10 @@ class Task : public std::enable_shared_from_this { static std::unique_ptr create( const std::shared_ptr& task); - uint64_t reclaim(memory::MemoryPool* pool, uint64_t targetBytes) override; + uint64_t reclaim( + memory::MemoryPool* pool, + uint64_t targetBytes, + memory::MemoryReclaimer::Stats& stats) override; void abort(memory::MemoryPool* pool, const std::exception_ptr& error) override;