/* * 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 #include namespace folly { /** * This class allows you to perform torn loads and stores on the bits of a * trivially-copyable type T without triggering undefined behavior. You may * encounter corrupt data, but should not encounter nasal demons. * * This class provides no atomicity or memory ordering. Loads and stores are * expected often to be data races. Synchronization is expected to be provided * externally, and this class is helpful in building higher-level optimistic * concurrency tools in combination with externally-provided synchronization. * * To see why this is useful, consider the guarantees provided by * std::atomic. It ensures that every load returns a T that was stored in the * atomic. If T is too large to be read/written with a single load/store * instruction, std::atomic falls back to locking to provide this guarantee. * Users pay this cost even if they have some higher-level mechanism (an * external lock, version numbers, other application-level reasoning) that makes * them resilient to torn reads. Tearable allows concurrent access without * these costs. * * For types smaller than the processor word size, prefer std::atomic. */ template class Tearable { public: // We memcpy the object representation, and the destructor would not know how // to deal with an object state it doesn't understand. static_assert( is_trivially_copyable::value, "Tearable types must be trivially copyable."); Tearable() noexcept { for (std::size_t i = 0; i < kNumDataWords; ++i) { std::atomic_init(&data_[i], RawWord{}); } } Tearable(const T& val) : Tearable() { store(val); } // Note that while filling dst with invalid data should be fine, *doing // anything* with the result may trigger undefined behavior unless you've // verified that the data you read was consistent. void load(T& dst) const { RawWord newDst[kNumDataWords]; for (std::size_t i = 0; i < kNumDataWords; ++i) { newDst[i] = data_[i].load(std::memory_order_relaxed); } std::memcpy(&dst, newDst, sizeof(T)); } void store(const T& val) { RawWord newData[kNumDataWords]; std::memcpy(newData, &val, sizeof(T)); for (std::size_t i = 0; i < kNumDataWords; ++i) { data_[i].store(newData[i], std::memory_order_relaxed); } } private: // A union gets us memcpy-like copy semantics always. union RawWord { // "unsigned" here matters; we may read uninitialized values (in the // trailing data word in write(), for instance). unsigned char data alignas(void*)[sizeof(void*)]; }; const static std::size_t kNumDataWords = (sizeof(T) + sizeof(RawWord) - 1) / sizeof(RawWord); std::atomic data_[kNumDataWords]; }; } // namespace folly