diff --git a/helio b/helio index b7755de9b..0ceb8599b 160000 --- a/helio +++ b/helio @@ -1 +1 @@ -Subproject commit b7755de9b28e0185d6aba2f2424b8e9c64ae5328 +Subproject commit 0ceb8599b2b3a66e471b7294bc63f00d168ab306 diff --git a/src/core/dash.h b/src/core/dash.h index 690981ce5..720804d1b 100644 --- a/src/core/dash.h +++ b/src/core/dash.h @@ -122,7 +122,8 @@ class DashTable : public detail::DashTableBase { // false for duplicate, true if inserted. template std::pair Insert(U&& key, V&& value) { - return InsertInternal(std::forward(key), std::forward(value), DefaultEvictionPolicy{}); + DefaultEvictionPolicy policy; + return InsertInternal(std::forward(key), std::forward(value), policy); } template @@ -244,7 +245,7 @@ class DashTable : public detail::DashTableBase { private: template - std::pair InsertInternal(U&& key, V&& value, EvictionPolicy&& policy); + std::pair InsertInternal(U&& key, V&& value, EvictionPolicy& policy); void IncreaseDepth(unsigned new_depth); void Split(uint32_t seg_id); @@ -566,7 +567,7 @@ template bool DashTable<_Key, _Value, Policy>::ShiftRight(bucket_iterator it) { auto* seg = segment_[it.seg_id_]; - typename Segment_t::Hash_t hash_val; + typename Segment_t::Hash_t hash_val = 0; auto& bucket = seg->GetBucket(it.bucket_id_); if (bucket.GetBusy() & (1 << (kBucketWidth - 1))) { @@ -675,7 +676,7 @@ void DashTable<_Key, _Value, Policy>::Reserve(size_t size) { template template -auto DashTable<_Key, _Value, Policy>::InsertInternal(U&& key, V&& value, EvictionPolicy&& ev) +auto DashTable<_Key, _Value, Policy>::InsertInternal(U&& key, V&& value, EvictionPolicy& ev) -> std::pair { uint64_t key_hash = DoHash(key); uint32_t seg_id = SegmentId(key_hash); @@ -700,7 +701,7 @@ auto DashTable<_Key, _Value, Policy>::InsertInternal(U&& key, V&& value, Evictio // At this point we must split the segment. // try garbage collect or evict. - if constexpr (ev.can_evict || ev.can_gc) { + if constexpr (EvictionPolicy::can_evict || EvictionPolicy::can_gc) { // Try gc. uint8_t bid[2]; SegmentType::FillProbeArray(key_hash, bid); @@ -716,7 +717,7 @@ auto DashTable<_Key, _Value, Policy>::InsertInternal(U&& key, V&& value, Evictio // The difference between gc and eviction is that gc can be applied even if // the table can grow since we throw away logically deleted items. // For eviction to be applied we should reach the growth limit. - if constexpr (ev.can_gc) { + if constexpr (EvictionPolicy::can_gc) { unsigned res = ev.GarbageCollect(buckets, this); garbage_collected_ += res; if (res) @@ -731,7 +732,7 @@ auto DashTable<_Key, _Value, Policy>::InsertInternal(U&& key, V&& value, Evictio } // We evict only if our policy says we can not grow - if constexpr (ev.can_evict) { + if constexpr (EvictionPolicy::can_evict) { bool can_grow = ev.CanGrow(*this); if (!can_grow) { unsigned res = ev.Evict(buckets, this); diff --git a/src/core/dash_internal.h b/src/core/dash_internal.h index 27fa58a04..caf2ad9ee 100644 --- a/src/core/dash_internal.h +++ b/src/core/dash_internal.h @@ -681,7 +681,7 @@ class DashCursor { */ template void SlotBitmap::SetSlot(unsigned index, bool probe) { - if (SINGLE) { + if constexpr (SINGLE) { assert(((val_[0].d >> (index + 18)) & 1) == 0); val_[0].d |= (1 << (index + 18)); val_[0].d |= (unsigned(probe) << (index + 4)); @@ -698,7 +698,7 @@ template void SlotBitmap::SetSlot(unsigned index template void SlotBitmap::ClearSlot(unsigned index) { assert(Size() > 0); - if (SINGLE) { + if constexpr (SINGLE) { uint32_t new_bitmap = val_[0].d & (~(1u << (index + 18))) & (~(1u << (index + 4))); new_bitmap -= 1; val_[0].d = new_bitmap; @@ -712,7 +712,7 @@ template void SlotBitmap::ClearSlot(unsigned ind template bool SlotBitmap::ShiftLeft() { constexpr uint32_t kBusyLastSlot = (kAllocMask >> 1) + 1; bool res; - if (SINGLE) { + if constexpr (SINGLE) { constexpr uint32_t kShlMask = kAllocMask - 1; // reset lsb res = (val_[0].d & (kBusyLastSlot << 18)) != 0; uint32_t l = (val_[0].d << 1) & (kShlMask << 4); @@ -745,7 +745,7 @@ template void SlotBitmap::Swap(unsigned slot_a, if (slot_a > slot_b) std::swap(slot_a, slot_b); - if (SINGLE) { + if constexpr (SINGLE) { uint32_t a = (val_[0].d << (slot_b - slot_a)) ^ val_[0].d; uint32_t bm = (1 << (slot_b + 4)) | (1 << (slot_b + 18)); a &= bm; diff --git a/src/core/external_alloc.h b/src/core/external_alloc.h index dcafb35c2..18be3533e 100644 --- a/src/core/external_alloc.h +++ b/src/core/external_alloc.h @@ -33,7 +33,7 @@ constexpr inline unsigned long long operator""_KB(unsigned long long x) { */ namespace detail { -class Page; +struct Page; constexpr unsigned kNumFreePages = 29; diff --git a/src/facade/error.h b/src/facade/error.h index 331a393de..7d90e93f9 100644 --- a/src/facade/error.h +++ b/src/facade/error.h @@ -27,6 +27,6 @@ extern const char kExpiryOutOfRange[]; extern const char kSyntaxErrType[]; extern const char kScriptErrType[]; extern const char kIndexOutOfRange[]; - +extern const char kOutOfMemory[]; } // namespace dfly diff --git a/src/facade/facade.cc b/src/facade/facade.cc index d25a83d35..bde726a88 100644 --- a/src/facade/facade.cc +++ b/src/facade/facade.cc @@ -72,6 +72,7 @@ const char kExpiryOutOfRange[] = "expiry is out of range"; const char kSyntaxErrType[] = "syntax_error"; const char kScriptErrType[] = "script_error"; const char kIndexOutOfRange[] = "index out of range"; +const char kOutOfMemory[] = "Out of memory"; const char* RespExpr::TypeName(Type t) { diff --git a/src/facade/facade_test.h b/src/facade/facade_test.h index 7f9d30ac7..baa0342d2 100644 --- a/src/facade/facade_test.h +++ b/src/facade/facade_test.h @@ -70,6 +70,10 @@ inline bool operator==(const RespExpr& left, std::string_view s) { return left.type == RespExpr::STRING && ToSV(left.GetBuf()) == s; } +inline bool operator!=(const RespExpr& left, std::string_view s) { + return !(left == s); +} + void PrintTo(const RespExpr::Vec& vec, std::ostream* os); } // namespace facade diff --git a/src/facade/reply_builder.cc b/src/facade/reply_builder.cc index 7e10978d0..05af7450c 100644 --- a/src/facade/reply_builder.cc +++ b/src/facade/reply_builder.cc @@ -240,6 +240,9 @@ void RedisReplyBuilder::SendError(OpStatus status) { case OpStatus::SYNTAX_ERR: SendError(kSyntaxErr); break; + case OpStatus::OUT_OF_MEMORY: + SendError(kOutOfMemory); + break; default: LOG(ERROR) << "Unsupported status " << status; SendError("Internal error"); @@ -254,6 +257,7 @@ void RedisReplyBuilder::SendLong(long num) { void RedisReplyBuilder::SendDouble(double val) { char buf[64]; + StringBuilder sb(buf, sizeof(buf)); CHECK(dfly_conv.ToShortest(val, &sb)); diff --git a/src/facade/service_interface.h b/src/facade/service_interface.h index 68bcc64be..da0e0ac98 100644 --- a/src/facade/service_interface.h +++ b/src/facade/service_interface.h @@ -13,7 +13,7 @@ namespace facade { class ConnectionContext; class Connection; -class ConnectionStats; +struct ConnectionStats; class ServiceInterface { public: diff --git a/src/server/db_slice.cc b/src/server/db_slice.cc index 384e69636..62bbb16c6 100644 --- a/src/server/db_slice.cc +++ b/src/server/db_slice.cc @@ -150,6 +150,7 @@ SliceEvents& SliceEvents::operator+=(const SliceEvents& o) { #undef ADD + DbSlice::DbSlice(uint32_t index, bool caching_mode, EngineShard* owner) : shard_id_(index), caching_mode_(caching_mode), owner_(owner) { db_arr_.emplace_back(); @@ -263,7 +264,8 @@ OpResult> DbSlice::FindFirst(DbIndex db_index, Arg return OpStatus::KEY_NOTFOUND; } -auto DbSlice::AddOrFind(DbIndex db_index, string_view key) -> pair { +auto DbSlice::AddOrFind(DbIndex db_index, string_view key) noexcept(false) + -> pair { DCHECK(IsDbValid(db_index)); auto& db = db_arr_[db_index]; @@ -288,8 +290,16 @@ auto DbSlice::AddOrFind(DbIndex db_index, string_view key) -> pairprime.Insert(std::move(co_key), PrimeValue{}, evp); + } catch (bad_alloc& e) { + throw e; + } - auto [it, inserted] = db->prime.Insert(std::move(co_key), PrimeValue{}, evp); if (inserted) { // new entry db->stats.inline_keys += it->first.IsInline(); db->stats.obj_memory_usage += it->first.MallocUsed(); @@ -440,20 +450,22 @@ uint32_t DbSlice::GetMCFlag(DbIndex db_ind, const PrimeKey& key) const { return it.is_done() ? 0 : it->second; } + PrimeIterator DbSlice::AddNew(DbIndex db_ind, string_view key, PrimeValue obj, - uint64_t expire_at_ms) { - auto [res, added] = AddOrFind(db_ind, key, std::move(obj), expire_at_ms); + uint64_t expire_at_ms) noexcept(false) { + auto [it, added] = AddOrFind(db_ind, key, std::move(obj), expire_at_ms); CHECK(added); - return res; + return it; } + pair DbSlice::AddOrFind(DbIndex db_ind, string_view key, PrimeValue obj, - uint64_t expire_at_ms) { + uint64_t expire_at_ms) noexcept(false) { DCHECK_LT(db_ind, db_arr_.size()); DCHECK(!obj.IsRef()); - auto res = AddOrFind(db_ind, key); + pair res = AddOrFind(db_ind, key); if (!res.second) // have not inserted. return res; @@ -476,6 +488,7 @@ pair DbSlice::AddOrFind(DbIndex db_ind, string_view key, Pr return res; } + size_t DbSlice::DbSize(DbIndex db_ind) const { DCHECK_LT(db_ind, db_array_size()); diff --git a/src/server/db_slice.h b/src/server/db_slice.h index b749cc6bb..06846b733 100644 --- a/src/server/db_slice.h +++ b/src/server/db_slice.h @@ -123,13 +123,13 @@ class DbSlice { // Return .second=true if insertion ocurred, false if we return the existing key. // throws: bad_alloc is insertion could not happen due to out of memory. - std::pair AddOrFind(DbIndex db_ind, std::string_view key); + std::pair AddOrFind(DbIndex db_ind, std::string_view key) noexcept(false); // Returns second=true if insertion took place, false otherwise. // expire_at_ms equal to 0 - means no expiry. // throws: bad_alloc is insertion could not happen due to out of memory. std::pair AddOrFind(DbIndex db_ind, std::string_view key, PrimeValue obj, - uint64_t expire_at_ms); + uint64_t expire_at_ms) noexcept(false); // Either adds or removes (if at == 0) expiry. Returns true if a change was made. // Does not change expiry if at != 0 and expiry already exists. @@ -141,7 +141,8 @@ class DbSlice { // Adds a new entry. Requires: key does not exist in this slice. // Returns the iterator to the newly added entry. // throws: bad_alloc is insertion could not happen due to out of memory. - PrimeIterator AddNew(DbIndex db_ind, std::string_view key, PrimeValue obj, uint64_t expire_at_ms); + PrimeIterator AddNew(DbIndex db_ind, std::string_view key, PrimeValue obj, + uint64_t expire_at_ms) noexcept(false); // Creates a database with index `db_ind`. If such database exists does nothing. void ActivateDb(DbIndex db_ind); diff --git a/src/server/dfly_main.cc b/src/server/dfly_main.cc index e63610712..1ce1278c1 100644 --- a/src/server/dfly_main.cc +++ b/src/server/dfly_main.cc @@ -2,7 +2,10 @@ // See LICENSE for licensing terms. // -// #include +#ifdef NDEBUG +#include +#endif + #include #include "base/init.h" @@ -15,6 +18,7 @@ #include "util/uring/uring_pool.h" #include "util/varz.h" + DECLARE_uint32(port); DECLARE_uint32(memcache_port); DECLARE_uint64(maxmemory); diff --git a/src/server/dragonfly_test.cc b/src/server/dragonfly_test.cc index befc95741..de115668c 100644 --- a/src/server/dragonfly_test.cc +++ b/src/server/dragonfly_test.cc @@ -357,9 +357,7 @@ TEST_F(DflyEngineTest, LimitMemory) { } TEST_F(DflyEngineTest, FlushAll) { - auto fb0 = pp_->at(0)->LaunchFiber([&] { - Run({"flushall"}); - }); + auto fb0 = pp_->at(0)->LaunchFiber([&] { Run({"flushall"}); }); auto fb1 = pp_->at(1)->LaunchFiber([&] { Run({"select", "2"}); @@ -375,6 +373,37 @@ TEST_F(DflyEngineTest, FlushAll) { fb1.join(); } +TEST_F(DflyEngineTest, OOM) { + shard_set->TEST_EnableHeartBeat(); + max_memory_limit = 0; + size_t i = 0; + RespExpr resp; + for (; i < 10000; i += 3) { + resp = Run({"mset", StrCat("key", i), "bar", StrCat("key", i + 1), "bar", StrCat("key", i + 2), + "bar"}); + if (resp != "OK") + break; + ASSERT_EQ(resp, "OK"); + } + + EXPECT_THAT(resp, ErrArg("Out of mem")); + for (; i < 10000; ++i) { + resp = Run({"set", StrCat("key", i), "bar"}); + if (resp != "OK") + break; + } + EXPECT_THAT(resp, ErrArg("Out of mem")); + + for (; i < 10000; ++i) { + resp = Run({"rpush", StrCat("list", i), "bar"}); + + if (resp.type == RespExpr::ERROR) + break; + ASSERT_THAT(resp, IntArg(1)); + } + EXPECT_THAT(resp, ErrArg("Out of mem")); +} + // TODO: to test transactions with a single shard since then all transactions become local. // To consider having a parameter in dragonfly engine controlling number of shards // unconditionally from number of cpus. TO TEST BLPOP under multi for single/multi argument case. diff --git a/src/server/engine_shard_set.cc b/src/server/engine_shard_set.cc index 61cf1033d..9e6148c7f 100644 --- a/src/server/engine_shard_set.cc +++ b/src/server/engine_shard_set.cc @@ -300,27 +300,6 @@ void EngineShard::Heartbeat() { } void EngineShard::CacheStats() { -#if 0 - mi_heap_t* tlh = mi_resource_.heap(); - struct Sum { - size_t used = 0; - size_t comitted = 0; - } sum; - - auto visit_cb = [](const mi_heap_t* heap, const mi_heap_area_t* area, void* block, - size_t block_size, void* arg) -> bool { - DCHECK(!block); - Sum* sum = (Sum*)arg; - - // mimalloc mistakenly exports used in blocks instead of bytes. - sum->used += block_size * area->used; - sum->comitted += area->committed; - - DVLOG(1) << "block_size " << block_size << "/" << area->block_size << ", reserved " - << area->reserved << " comitted " << area->committed << " used: " << area->used; - return true; // continue iteration - }; -#endif // mi_heap_visit_blocks(tlh, false /* visit all blocks*/, visit_cb, &sum); mi_stats_merge(); @@ -344,6 +323,11 @@ void EngineShard::AddBlocked(Transaction* trans) { blocking_controller_->AddWatched(trans); } +void EngineShard::TEST_EnableHeartbeat() { + auto* pb = ProactorBase::me(); + periodic_task_ = pb->AddPeriodic(1, [this] { Heartbeat(); }); +} + /** @@ -361,7 +345,7 @@ void EngineShardSet::Init(uint32_t sz, bool update_db_time) { cached_stats.resize(sz); shard_queue_.resize(sz); - pp_->AwaitFiberOnAll([&](uint32_t index, ProactorBase* pb) { + pp_->AwaitFiberOnAll([&](uint32_t index, ProactorBase* pb) { if (index < shard_queue_.size()) { InitThreadLocal(pb, update_db_time); } @@ -382,4 +366,8 @@ const vector& EngineShardSet::GetCachedStats() { return cached_stats; } +void EngineShardSet::TEST_EnableHeartBeat() { + RunBriefInParallel([](EngineShard* shard) { shard->TEST_EnableHeartbeat(); }); +} + } // namespace dfly diff --git a/src/server/engine_shard_set.h b/src/server/engine_shard_set.h index c43415d1a..909ebb5f3 100644 --- a/src/server/engine_shard_set.h +++ b/src/server/engine_shard_set.h @@ -121,21 +121,6 @@ class EngineShard { // for everyone to use for string transformations during atomic cpu sequences. sds tmp_str1; -#if 0 - size_t TEST_WatchedDbsLen() const { - return watched_dbs_.size(); - } - - size_t TEST_AwakenIndicesLen() const { - return awakened_indices_.size(); - } - - size_t TEST_AwakenTransLen() const { - return awakened_transactions_.size(); - } - - bool HasResultConverged(TxId notifyid) const; -#endif // Moving average counters. enum MovingCnt { @@ -148,6 +133,8 @@ class EngineShard { return counter_[unsigned(type)].SumTail(); } + void TEST_EnableHeartbeat(); + private: EngineShard(util::ProactorBase* pb, bool update_db_time, mi_heap_t* heap); @@ -235,6 +222,9 @@ class EngineShardSet { template void RunBlockingInParallel(U&& func); + // Used in tests + void TEST_EnableHeartBeat(); + private: void InitThreadLocal(util::ProactorBase* pb, bool update_db_time); diff --git a/src/server/list_family.cc b/src/server/list_family.cc index f60329625..a93147a0b 100644 --- a/src/server/list_family.cc +++ b/src/server/list_family.cc @@ -299,8 +299,16 @@ OpResult OpRPopLPushSingleShard(const OpArgs& op_args, string_view src, } quicklist* dest_ql = nullptr; - auto [dest_it, created] = db_slice.AddOrFind(op_args.db_ind, dest); - if (created) { + pair res; + try { + res = db_slice.AddOrFind(op_args.db_ind, dest); + } catch (bad_alloc&) { + return OpStatus::OUT_OF_MEMORY; + } + + PrimeIterator& dest_it = res.first; + + if (res.second) { robj* obj = createQuicklistObject(); dest_ql = (quicklist*)obj->ptr; quicklistSetOptions(dest_ql, FLAGS_list_max_listpack_size, FLAGS_list_compress_depth); @@ -366,8 +374,13 @@ OpResult OpPush(const OpArgs& op_args, std::string_view key, ListDir d return it_res.status(); it = *it_res; } else { - tie(it, new_key) = es->db_slice().AddOrFind(op_args.db_ind, key); + try { + tie(it, new_key) = es->db_slice().AddOrFind(op_args.db_ind, key); + } catch (bad_alloc&) { + return OpStatus::OUT_OF_MEMORY; + } } + quicklist* ql = nullptr; if (new_key) { @@ -599,11 +612,11 @@ void ListFamily::LInsert(CmdArgList args, ConnectionContext* cntx) { }; OpResult result = cntx->transaction->ScheduleSingleHopT(std::move(cb)); - if (result.status() == OpStatus::WRONG_TYPE) { - return (*cntx)->SendError(kWrongTypeErr); + if (result) { + return (*cntx)->SendLong(result.value()); } - (*cntx)->SendLong(result.value()); + (*cntx)->SendError(result.status()); } void ListFamily::LTrim(CmdArgList args, ConnectionContext* cntx) { @@ -751,11 +764,11 @@ void ListFamily::PushGeneric(ListDir dir, bool skip_notexists, CmdArgList args, }; OpResult result = cntx->transaction->ScheduleSingleHopT(std::move(cb)); - if (result.status() == OpStatus::WRONG_TYPE) { - return (*cntx)->SendError(kWrongTypeErr); + if (result) { + return (*cntx)->SendLong(result.value()); } - return (*cntx)->SendLong(result.value()); + return (*cntx)->SendError(result.status()); } void ListFamily::PopGeneric(ListDir dir, CmdArgList args, ConnectionContext* cntx) { diff --git a/src/server/main_service.cc b/src/server/main_service.cc index 4eea48ad4..05bd483e3 100644 --- a/src/server/main_service.cc +++ b/src/server/main_service.cc @@ -789,7 +789,7 @@ void Service::EvalInternal(const EvalArgs& eval_args, Interpreter* interpreter, // TODO: to determine whether the script is RO by scanning all "redis.p?call" calls // and checking whether all invocations consist of RO commands. // we can do it once during script insertion into script mgr. - cntx->conn_state.script_info.emplace(); + cntx->conn_state.script_info.emplace(ConnectionState::Script{}); for (size_t i = 0; i < eval_args.keys.size(); ++i) { cntx->conn_state.script_info->keys.insert(ArgS(eval_args.keys, i)); } diff --git a/src/server/string_family.cc b/src/server/string_family.cc index 75633420b..60cd8236e 100644 --- a/src/server/string_family.cc +++ b/src/server/string_family.cc @@ -288,6 +288,10 @@ void StringFamily::Set(CmdArgList args, ConnectionContext* cntx) { return builder->SendStored(); } + if (result == OpStatus::OUT_OF_MEMORY) { + return builder->SendError(kOutOfMemory); + } + CHECK_EQ(result, OpStatus::SKIPPED); // in case of NX option return builder->SendSetSkipped(); @@ -587,8 +591,12 @@ void StringFamily::MSet(CmdArgList args, ConnectionContext* cntx) { return OpMSet(OpArgs{es, t->db_index()}, args); }; - transaction->ScheduleSingleHop(std::move(cb)); - (*cntx)->SendOk(); + OpStatus status = transaction->ScheduleSingleHop(std::move(cb)); + if (status == OpStatus::OK) { + (*cntx)->SendOk(); + } else { + (*cntx)->SendError(status); + } } void StringFamily::MSetNx(CmdArgList args, ConnectionContext* cntx) { @@ -745,9 +753,8 @@ OpStatus StringFamily::OpMSet(const OpArgs& op_args, ArgSlice args) { for (size_t i = 0; i < args.size(); i += 2) { DVLOG(1) << "MSet " << args[i] << ":" << args[i + 1]; auto res = sg.Set(params, args[i], args[i + 1]); - if (!res) { - LOG(ERROR) << "Unexpected error " << res.status(); - return OpStatus::OK; // Multi-key operations must return OK. + if (!res) { // OOM for example. + return res.status(); } } @@ -768,8 +775,11 @@ OpResult StringFamily::OpIncrBy(const OpArgs& op_args, std::string_view CompactObj cobj; cobj.SetInt(incr); - db_slice.AddNew(op_args.db_ind, key, std::move(cobj), 0); - + try { + db_slice.AddNew(op_args.db_ind, key, std::move(cobj), 0); + } catch (bad_alloc&) { + return OpStatus::OUT_OF_MEMORY; + } return incr; } diff --git a/src/server/table.cc b/src/server/table.cc index a92f8c132..4f6b2e087 100644 --- a/src/server/table.cc +++ b/src/server/table.cc @@ -26,7 +26,7 @@ DbTableStats& DbTableStats::operator+=(const DbTableStats& o) { } DbTable::DbTable(std::pmr::memory_resource* mr) - : prime(4, detail::PrimeTablePolicy{}, mr), + : prime(2, detail::PrimeTablePolicy{}, mr), expire(0, detail::ExpireTablePolicy{}, mr), mcflag(0, detail::ExpireTablePolicy{}, mr) { } diff --git a/src/server/transaction.cc b/src/server/transaction.cc index facb0f366..13bb2663b 100644 --- a/src/server/transaction.cc +++ b/src/server/transaction.cc @@ -338,7 +338,11 @@ bool Transaction::RunInShard(EngineShard* shard) { cb_ = nullptr; // We can do it because only a single thread runs the callback. local_result_ = status; } else { - CHECK_EQ(OpStatus::OK, status); + if (status == OpStatus::OUT_OF_MEMORY) { + local_result_ = status; + } else { + CHECK_EQ(OpStatus::OK, status); + } } } catch (std::bad_alloc&) { // TODO: to log at most once per sec.