2 * Copyright 2015 Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 /* -*- Mode: C++; tab-width: 2; c-basic-offset: 2; indent-tabs-mode: nil -*- */
21 #include <folly/Optional.h>
22 #include <folly/ThreadLocal.h>
23 #include <folly/SpinLock.h>
28 * @file ReadMostlySharedPtr is a smart pointer that allows for high
29 * performance shared ownership of an object. In order to provide
30 * this, ReadMostlySharedPtr may potentially delay the destruction of
31 * a shared object for longer than a std::shared_ptr would, and
32 * depending on the implementation, may have slower updates.
34 * The load() method allows a reader to acquire a ReadPtr that
35 * maintains a reference to a single version of the object. Even if a
36 * writer calls store(), the ReadPtr will point to the version of the
37 * object that was in use at the time of the read. The old version of
38 * the object will only be destroyed after all outstanding ReadPtrs to
39 * that version have been destroyed.
44 class ReadMostlySharedPtr {
46 constexpr explicit ReadMostlySharedPtr(std::unique_ptr<T>&& ptr = nullptr)
47 : masterPtr_(std::move(ptr)) {}
50 * Replaces the managed object.
52 void store(std::unique_ptr<T>&& uptr) {
54 std::shared_ptr<T> ptr(std::move(uptr));
55 std::lock_guard<std::mutex> lock(mutex_);
56 // Swap to avoid calling ~T() under the lock
57 std::swap(masterPtr_, ptr);
61 // This also holds a lock that prevents destruction of thread cache
62 // entries, but not creation. If creating a thread cache entry for a new
63 // thread happens duting iteration, the entry is not guaranteed to
64 // be seen. It's fine for us: if load() created a new cache entry after
65 // we got accessor, it will see the updated pointer, so we don't need to
67 auto accessor = threadLocalCache_.accessAllThreads();
69 for (CachedPointer& local: accessor) {
70 std::lock_guard<folly::SpinLock> local_lock(local.lock);
71 // We could instead just assign masterPtr_ to local.ptr, but it's better
72 // if the thread allocates the Ptr for itself - the allocator is more
73 // likely to place its reference counter in a region optimal for access
81 friend class ReadMostlySharedPtr;
88 explicit operator bool() const {
89 return (ref_ != nullptr);
91 bool operator ==(T* ptr) const {
94 bool operator ==(std::nullptr_t) const {
95 return ref_ == nullptr;
97 T* operator->() const { return ref_; }
98 T& operator*() const { return *ref_; }
99 T* get() const { return ref_; }
101 explicit ReadPtr(std::shared_ptr<T>& ptr)
104 std::shared_ptr<T> ptr_;
109 * Returns a shared_ptr to the managed object.
111 ReadPtr load() const {
112 auto& local = *threadLocalCache_;
114 std::lock_guard<folly::SpinLock> local_lock(local.lock);
116 if (!local.ptr.hasValue()) {
117 std::lock_guard<std::mutex> lock(mutex_);
119 local.ptr.emplace(nullptr);
121 // The following expression is tricky.
123 // It creates a shared_ptr<shared_ptr<T>> that points to a copy of
124 // masterPtr_. The reference counter of this shared_ptr<shared_ptr<T>>
125 // will normally only be modified from this thread, which avoids
126 // cache line bouncing. (Though the caller is free to pass the pointer
127 // to other threads and bump reference counter from there)
129 // Then this shared_ptr<shared_ptr<T>> is turned into shared_ptr<T>.
130 // This means that the returned shared_ptr<T> will internally point to
131 // control block of the shared_ptr<shared_ptr<T>>, but will dereference
132 // to T, not shared_ptr<T>.
133 local.ptr = makeCachedCopy(masterPtr_);
137 // The return statement makes the copy before destroying local variables,
138 // so local.ptr is only accessed under local.lock here.
139 return ReadPtr(local.ptr.value());
145 ReadMostlySharedPtr(const ReadMostlySharedPtr&) = delete;
146 ReadMostlySharedPtr& operator=(const ReadMostlySharedPtr&) = delete;
148 struct CachedPointer {
149 folly::Optional<std::shared_ptr<T>> ptr;
150 folly::SpinLock lock;
153 std::shared_ptr<T> masterPtr_;
155 // Instead of using Tag as tag for ThreadLocal, effectively use pair (T, Tag),
156 // which is more granular.
157 struct ThreadLocalTag {};
159 mutable folly::ThreadLocal<CachedPointer, ThreadLocalTag> threadLocalCache_;
161 // Ensures safety between concurrent store() and load() calls
162 mutable std::mutex mutex_;
165 makeCachedCopy(const std::shared_ptr<T> &ptr) const {
166 // For std::shared_ptr wrap a copy in another std::shared_ptr to
167 // avoid cache line bouncing.
169 // The following expression is tricky.
171 // It creates a shared_ptr<shared_ptr<T>> that points to a copy of
172 // masterPtr_. The reference counter of this shared_ptr<shared_ptr<T>>
173 // will normally only be modified from this thread, which avoids
174 // cache line bouncing. (Though the caller is free to pass the pointer
175 // to other threads and bump reference counter from there)
177 // Then this shared_ptr<shared_ptr<T>> is turned into shared_ptr<T>.
178 // This means that the returned shared_ptr<T> will internally point to
179 // control block of the shared_ptr<shared_ptr<T>>, but will dereference
180 // to T, not shared_ptr<T>.
181 return std::shared_ptr<T>(
182 std::make_shared<std::shared_ptr<T>>(ptr), ptr.get());