/* * WriteOnlySet.h * * This source file is part of the FoundationDB open source project * * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors * * 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 "flow/Error.h" #include "flow/FastRef.h" #include "flow/Trace.h" #include #ifdef ENABLE_SAMPLING /** * This is a Write-Only set that supports copying the whole content. This data structure is lock-free and allows a user * to insert and remove objects up to a given capacity (passed by a template). * * Template parameters: * \param T The type to store. * \param IndexType The type used as an index * \param CAPACITY The maximum number of object this structure can store (if a user tries to store more, insert will * fail gracefully) * \pre T implements `void addref() const` and `void delref() const` * \pre IndexType must have a copy constructor * \pre IndexType must have a trivial assignment operator * \pre IndexType must have a trivial destructor * \pre IndexType can be used as an index into a std::vector */ template class WriteOnlySet { public: // The type we use for lookup into the set. Gets assigned during insert using Index = IndexType; // For now we use a fixed size capacity constexpr static Index npos = std::numeric_limits::max(); constexpr static IndexType capacity = CAPACITY; explicit WriteOnlySet(); WriteOnlySet(const WriteOnlySet&) = delete; WriteOnlySet(WriteOnlySet&&) = delete; WriteOnlySet& operator=(const WriteOnlySet&) = delete; WriteOnlySet& operator=(WriteOnlySet&&) = delete; /** * Attempts to insert \p lineage into the set. This method can fail if the set is full (its size is equal to its * capacity). Calling insert on a full set is safe but the method will return \ref npos if the operation fails. * * \param lineage A reference to the object the user wants to insert. * \ret An index that can later be used to erase the value again or \ref npos if the insert failed. * \pre lineage.getPtr() % 2 == 0 (the memory for lineage has to be at least 2 byte aligned) */ [[nodiscard]] Index insert(const Reference& lineage); /** * Erases the object associated with \p idx from the set. * * \ret Whether the reference count was decremented. Usually the return value is only interesting for testing and * benchmarking purposes and will in most cases be ignored. If \ref delref wasn't called, it will be called * later. Note that at the time the return value is checked, \ref delref might already have been called. */ bool erase(Index idx); /** * Replaces the object associated with \p idx with \p lineage. * * \ret Whether the reference count of the replaced object was decremented. Usually the return value is only * interesting for testing and benchmarking purposes and will in most cases be ignored. If \ref delref * wasn't called, it will be called later. Note that at the time the return value is checked, \ref delref * might already have been called. */ bool replace(Index idx, const Reference& lineage); /** * Copies all elements that are stored in the set into a vector. This copy operation does NOT provide a snapshot of * the data structure. The contract is weak: * - All object that were in the set before copy is called and weren't removed until after copy returned are * guaranteed to be in the result. * - Any object that was inserted while copy is running might be in the result. * - Any object that was erased while copy is running might be in the result. */ std::vector> copy(); protected: // the implementation of erase -- the wrapper just makes the function a bit more readable. bool eraseImpl(Index idx); // the last bit of a pointer within the set is used like a boolean and true means that the object is locked. Locking // an object is only relevant for memory management. A locked pointer can still be erased from the set, but the // erase won't call delref on the object. Instead it will push the pointer into the \ref freeList and copy will call // delref later. static constexpr uintptr_t LOCK = 0b1; // The actual memory std::vector> _set; static_assert(std::atomic::is_always_lock_free, "Index type can't be used as a lock-free type"); static_assert(std::atomic::is_always_lock_free, "uintptr_t can't be used as a lock-free type"); // The freeQueue. On creation all indexes (0..capacity-1) are pushed into this queue. On insert one element from // this queue is consumed and the resulting number is used as an index into the set. On erase the index is given // back to the freeQueue. boost::lockfree::queue, boost::lockfree::capacity> freeQueue; // The freeList is used for memory management. Generally copying a shared pointer can't be done in a lock-free way. // Instead, when we copy the data structure we first copy the address, then attempt to set the last bit to 1 and // only if that succeeds we will increment the reference count. Whenever we attempt to remove an object // in \ref erase we remove the object from the set (using an atomic compare and swap) and only decrement the // reference count if the last bit is 0. If it's not we'll push the pointer into this free list. // \ref copy will consume all elements from this freeList each time it runs and decrements the refcount for each // element. boost::lockfree::queue, boost::lockfree::capacity> freeList; }; /** * Provides a thread safe, lock-free write only variable. * * Template parameters: * \param T The type to store. * \param IndexType The type used as an index * \pre T implements `void addref() const` and `void delref() const` * \pre IndexType must have a copy constructor * \pre IndexType must have a trivial assignment operator * \pre IndexType must have a trivial destructor * \pre IndexType can be used as an index into a std::vector */ template class WriteOnlyVariable : private WriteOnlySet { public: explicit WriteOnlyVariable(); /** * Returns a copied reference to the stored variable. */ Reference get(); /** * Replaces the variable with \p lineage. \p lineage is permitted to be an invalid pointer. * * \ret Whether the reference count of the replaced object was decremented. Note that if the reference being * replaced is invalid, this function will always return false. If \ref delref wasn't called and the reference was * valid, it will be called later. Note that at the time the return value is checked, \ref delref might already have * been called. */ bool replace(const Reference& element); }; class ActorLineage; extern template class WriteOnlySet; using ActorLineageSet = WriteOnlySet; #endif