1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2019 Couchbase, Inc
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 #pragma once
18 
19 #include <memory>
20 #include <mutex>
21 
22 /**
23  * This is a simplified implementation of std::atomic_shared_ptr
24  * (https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic2)
25  *
26  * It provides a shared_ptr like object which allows thread-safe access to the
27  * underlying pointed-to object, by associating a mutex with a shared_ptr.
28  *
29  * It provides *no* synchronization of the underlying pointed-to object - that
30  * object needs to mediate any access by multiple threads itself.
31  *
32  * Example use would be a single shared_ptr -like object which is accessed by
33  * multiple threads; this is racy with a normal shared_ptr object (The standard
34  * only guarantees thread-safety if each thread has it's own instance of a
35  * shared_ptr object which it accesses the pointed-to object via). This class
36  * avoids the race by acquiring the associated mutex around any access.
37  *
38  * Note this adds a relatively large space cost compared to a plain shared_ptr -
39  * shared_ptr is 16 Bytes on x86-64 Linux but AtomicSharedPtr is 56 Bytes.
40  * As such this may not be suitable where a large number of shared_ptr-like
41  * objects are needed and space is a concern.
42  *
43  * @todo: This can be replaced with std::atomic_shared_ptr once we get to C++20,
44  * or equivalent (e.g. folly/AtomicSharedPtr.h) when available.
45  */
46 namespace cb {
47 template <class T>
48 class AtomicSharedPtr {
49 public:
move(r)50     AtomicSharedPtr(std::shared_ptr<T> r) noexcept : ptr(std::move(r)) {
51     }
52 
operator =(std::shared_ptr<T>&& r)53     AtomicSharedPtr<T>& operator=(std::shared_ptr<T>&& r) {
54         std::lock_guard<std::mutex> lock(mutex);
55         ptr = r;
56         return *this;
57     }
58 
load() const59     std::shared_ptr<T> load() const {
60         std::lock_guard<std::mutex> lock(mutex);
61         return ptr;
62     }
63 
reset()64     void reset() {
65         // We want to perform the reset() of the underlying shared_ptr /not/
66         // under lock, for two reasons:
67         // 1. To minimise the scope of the locked region (performance)
68         // 2. If the reset() would reduce the ref-count to zero (and hence call
69         //    the dtor of the managed object), then we don't want to hold the
70         //    mutex as that could introduce lock ordering issues.
71         // Therefore move from the ptr under lock; then call reset() on the
72         // temporary to avoid holding the lock when reset() is called.
73         std::shared_ptr<T> temp;
74         {
75             std::lock_guard<std::mutex> lock(mutex);
76             temp = std::move(ptr);
77         }
78         temp.reset();
79     }
80 
81     std::shared_ptr<T> operator->() const noexcept {
82         return load();
83     }
84 
85     explicit operator bool() const noexcept {
86         return bool(operator->());
87     }
88 
89 private:
90     mutable std::mutex mutex;
91     std::shared_ptr<T> ptr;
92 };
93 
94 } // namespace cb
95