Add Tearable, for concurrently-modified non-atomic objects.
[folly.git] / folly / synchronization / Tearable.h
1 /*
2  * Copyright 2018-present Facebook, Inc.
3  *
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
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 #pragma once
17
18 #include <atomic>
19 #include <cstring>
20 #include <type_traits>
21
22 namespace folly {
23
24 /**
25  * This class allows you to perform torn loads and stores on the bits of a
26  * trivially-copyable type T without triggering undefined behavior. You may
27  * encounter corrupt data, but should not encounter nasal demons.
28  *
29  * This class provides no atomicity or memory ordering. Loads and stores are
30  * expected often to be data races. Synchronization is expected to be provided
31  * externally, and this class is helpful in building higher-level optimistic
32  * concurrency tools in combination with externally-provided synchronization.
33  *
34  * To see why this is useful, consider the guarantees provided by
35  * std::atomic<T>. It ensures that every load returns a T that was stored in the
36  * atomic. If T is too large to be read/written with a single load/store
37  * instruction, std::atomic<T> falls back to locking to provide this guarantee.
38  * Users pay this cost even if they have some higher-level mechanism (an
39  * external lock, version numbers, other application-level reasoning) that makes
40  * them resilient to torn reads. Tearable<T> allows concurrent access without
41  * these costs.
42  *
43  * For types smaller than the processor word size, prefer std::atomic<T>.
44  */
45 template <typename T>
46 class Tearable {
47  public:
48   // We memcpy the object representation, and the destructor would not know how
49   // to deal with an object state it doesn't understand.
50   static_assert(
51       std::is_trivially_copyable<T>::value,
52       "Tearable types must be trivially copyable.");
53
54   Tearable() {
55     for (std::size_t i = 0; i < kNumDataWords; ++i) {
56       std::atomic_init(&data_[i], RawWord{});
57     }
58   }
59
60   Tearable(const T& val) : Tearable() {
61     store(val);
62   }
63
64   // Note that while filling dst with invalid data should be fine, *doing
65   // anything* with the result may trigger undefined behavior unless you've
66   // verified that the data you read was consistent.
67   void load(T& dst) const {
68     RawWord newDst[kNumDataWords];
69
70     for (std::size_t i = 0; i < kNumDataWords; ++i) {
71       newDst[i] = data_[i].load(std::memory_order_relaxed);
72     }
73     std::memcpy(&dst, newDst, sizeof(T));
74   }
75
76   void store(const T& val) {
77     RawWord newData[kNumDataWords];
78     std::memcpy(newData, &val, sizeof(T));
79
80     for (std::size_t i = 0; i < kNumDataWords; ++i) {
81       data_[i].store(newData[i], std::memory_order_relaxed);
82     }
83   }
84
85  private:
86   // A union gets us memcpy-like copy semantics always.
87   union RawWord {
88     // "unsigned" here matters; we may read uninitialized values (in the
89     // trailing data word in write(), for instance).
90     unsigned char data alignas(void*)[sizeof(void*)];
91   };
92   const static std::size_t kNumDataWords =
93       (sizeof(T) + sizeof(RawWord) - 1) / sizeof(RawWord);
94
95   std::atomic<RawWord> data_[kNumDataWords];
96 };
97
98 } // namespace folly