/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include // This is unlike folly::ThreadCachedInt in that the full value // is never rounded up globally and cached, it only supports readFull. // // folly/experimental/TLRefCount is similar, but does not support a // waitForZero, and is not reset-able. // // Note that the RCU implementation is completely abstracted from the // counter implementation, a rseq implementation can be dropped in // if the kernel supports it. namespace folly { namespace detail { template class ThreadCachedInts { std::atomic orphan_inc_[2] = {}; std::atomic orphan_dec_[2] = {}; folly::detail::Futex<> waiting_{0}; class Integer { public: ThreadCachedInts* ints_; constexpr Integer(ThreadCachedInts* ints) noexcept : ints_(ints), inc_{}, dec_{}, cache_(ints->int_cache_) {} std::atomic inc_[2]; std::atomic dec_[2]; Integer*& cache_; // reference to the cached ptr ~Integer() noexcept { // Increment counts must be set before decrement counts ints_->orphan_inc_[0].fetch_add( inc_[0].load(std::memory_order_relaxed), std::memory_order_relaxed); ints_->orphan_inc_[1].fetch_add( inc_[1].load(std::memory_order_relaxed), std::memory_order_relaxed); folly::asymmetricLightBarrier(); // B ints_->orphan_dec_[0].fetch_add( dec_[0].load(std::memory_order_relaxed), std::memory_order_relaxed); ints_->orphan_dec_[1].fetch_add( dec_[1].load(std::memory_order_relaxed), std::memory_order_relaxed); ints_->waiting_.store(0, std::memory_order_release); detail::futexWake(&ints_->waiting_); // reset the cache_ on destructor so we can handle the delete/recreate cache_ = nullptr; } }; folly::ThreadLocalPtr cs_; // Cache the int pointer in a threadlocal. static thread_local Integer* int_cache_; void init() { auto ret = new Integer(this); cs_.reset(ret); int_cache_ = ret; } public: FOLLY_ALWAYS_INLINE void increment(uint8_t epoch) { if (!int_cache_) { init(); } auto& c = int_cache_->inc_[epoch]; auto val = c.load(std::memory_order_relaxed); c.store(val + 1, std::memory_order_relaxed); folly::asymmetricLightBarrier(); // A } FOLLY_ALWAYS_INLINE void decrement(uint8_t epoch) { folly::asymmetricLightBarrier(); // B if (!int_cache_) { init(); } auto& c = int_cache_->dec_[epoch]; auto val = c.load(std::memory_order_relaxed); c.store(val + 1, std::memory_order_relaxed); folly::asymmetricLightBarrier(); // C if (waiting_.load(std::memory_order_acquire)) { waiting_.store(0, std::memory_order_release); detail::futexWake(&waiting_); } } int64_t readFull(uint8_t epoch) { int64_t full = -orphan_dec_[epoch].load(std::memory_order_relaxed); // Matches A - ensure all threads have seen new value of version, // *and* that we see current values of counters in readFull() // // Note that in lock_shared if a reader is currently between the // version load and counter increment, they may update the wrong // epoch. However, this is ok - they started concurrently *after* // any callbacks that will run, and therefore it is safe to run // the callbacks. folly::asymmetricHeavyBarrier(); for (auto& i : cs_.accessAllThreads()) { full -= i.dec_[epoch].load(std::memory_order_relaxed); } // Matches B - ensure that all increments are seen if decrements // are seen. This is necessary because increment and decrement // are allowed to happen on different threads. folly::asymmetricHeavyBarrier(); auto accessor = cs_.accessAllThreads(); for (auto& i : accessor) { full += i.inc_[epoch].load(std::memory_order_relaxed); } // orphan is read behind accessAllThreads lock return full + orphan_inc_[epoch].load(std::memory_order_relaxed); } void waitForZero(uint8_t phase) { // Try reading before futex sleeping. if (readFull(phase) == 0) { return; } while (true) { waiting_.store(1, std::memory_order_release); // Matches C. Ensure either decrement sees waiting_, // or we see their decrement and can safely sleep. folly::asymmetricHeavyBarrier(); if (readFull(phase) == 0) { break; } detail::futexWait(&waiting_, 1); } waiting_.store(0, std::memory_order_relaxed); } // We are guaranteed to be called while StaticMeta lock is still // held because of ordering in AtForkList. We can therefore safely // touch orphan_ and clear out all counts. void resetAfterFork() { if (int_cache_) { int_cache_->dec_[0].store(0, std::memory_order_relaxed); int_cache_->dec_[1].store(0, std::memory_order_relaxed); int_cache_->inc_[0].store(0, std::memory_order_relaxed); int_cache_->inc_[1].store(0, std::memory_order_relaxed); } orphan_inc_[0].store(0, std::memory_order_relaxed); orphan_inc_[1].store(0, std::memory_order_relaxed); orphan_dec_[0].store(0, std::memory_order_relaxed); orphan_dec_[1].store(0, std::memory_order_relaxed); } }; template thread_local typename detail::ThreadCachedInts::Integer* detail::ThreadCachedInts::int_cache_ = nullptr; } // namespace detail } // namespace folly