proxygen
entangle.h
Go to the documentation of this file.
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 
19 
20 namespace folly {
21 namespace pushmi {
22 
23 // template <class T, class Dual>
24 // struct entangled {
25 // T t;
26 // entangled<Dual, T>* dual;
27 //
28 // ~entangled() {
29 // if (!!dual) {
30 // dual->dual = nullptr;
31 // }
32 // }
33 // explicit entangled(T t) : t(std::move(t)), dual(nullptr) {}
34 // entangled(entangled&& o) : t(std::move(o.t)), dual(o.dual) {
35 // o.dual = nullptr;
36 // if (!!dual) {
37 // dual->dual = this;
38 // }
39 // }
40 //
41 // entangled() = delete;
42 // entangled(const entangled&) = delete;
43 // entangled& operator=(const entangled&) = delete;
44 // entangled& operator=(entangled&&) = delete;
45 //
46 // Dual* lockPointerToDual() {
47 // if (!!dual) {
48 // return std::addressof(dual->t);
49 // }
50 // return nullptr;
51 // }
52 //
53 // void unlockPointerToDual() {
54 // }
55 // };
56 
57 // This class can be used to keep a pair of values with pointers to each other
58 // in sync, even when both objects are allowed to move. Ordinarily you'd do this
59 // with a heap-allocated, refcounted, control block (or do something else using
60 // external storage, like a lock table chosen by the current addresses of both
61 // objects).
62 // Another thing you could do is have locks, and a backoff strategy for dealing
63 // with deadlock: lock locally, trylock your dual, if the trylock fails,
64 // unlock yourself, wait a little while (giving a thread touching the dual a
65 // chance to acquire the local lock), and repeat. That's kind of ugly.
66 // This algorithm (which, to be clear, is untested and I haven't really even
67 // thought through carefully) provides the same guarantees, but without using
68 // external storage or backoff-based deadlock recovery.
69 
70 template <class T, class Dual>
71 struct entangled {
72  // must be constructed first so that the other.lockBoth() in the move
73  // constructor is run before moving other.t and other.dual
74  std::atomic<int> stateMachine;
75 
76  T t;
77  // In a couple places, we can save on some atomic ops by making this atomic,
78  // and adding a "dual == null" fast-path without locking.
80 
81  const static int kUnlocked = 0;
82  const static int kLocked = 1;
83  const static int kLockedAndLossAcknowledged = 2;
84 
85  // Note: *not* thread-safe; it's a bug for two threads to concurrently call
86  // lockBoth() on the same entangled (just as it's a bug for two threads to
87  // concurrently move from the same object).
88  // However, calling lockBoth() on two entangled objects at once is
89  // thread-safe.
90  // Note also that this may wait indefinitely; it's not the usual non-blocking
91  // tryLock().
92  bool tryLockBoth() {
93  // Try to acquire the local lock. We have to start locally, since local
94  // addresses are the only ones we know are safe at first. The rule is, you
95  // have to hold *both* locks to write any of either entangled object's
96  // metadata, but need only one to read it.
97  int expected = kUnlocked;
98  if (!stateMachine.compare_exchange_weak(
99  expected,
100  kLocked,
101  std::memory_order_seq_cst,
102  std::memory_order_relaxed)) {
103  return false;
104  }
105  // Having *either* object local-locked protects the data in both objects.
106  // Once we hold our lock, no control data can change, in either object.
107  if (dual == nullptr) {
108  return true;
109  }
110  expected = kUnlocked;
111  if (dual->stateMachine.compare_exchange_strong(
112  expected, kLocked, std::memory_order_seq_cst)) {
113  return true;
114  }
115  // We got here, and so hit the race; we're deadlocked if we stick to
116  // locking. Revert to address-ordering. Note that address-ordering would
117  // not be safe on its own, because of the lifetime issues involved; the
118  // addresses here are only stable *because* we know both sides are locked,
119  // and because of the invariant that you must hold both locks to modify
120  // either piece of data.
121  if ((uintptr_t)this < (uintptr_t)dual) {
122  // I get to win the race. I'll acquire the locks, but have to make sure
123  // my memory stays valid until the other thread acknowledges its loss.
124  while (stateMachine.load(std::memory_order_relaxed) !=
126  // Spin.
127  }
128  stateMachine.store(kLocked, std::memory_order_relaxed);
129  return true;
130  } else {
131  // I lose the race, but have to coordinate with the winning thread, so
132  // that it knows that I'm not about to try to touch it's data
133  dual->stateMachine.store(
134  kLockedAndLossAcknowledged, std::memory_order_relaxed);
135  return false;
136  }
137  }
138 
139  void lockBoth() {
140  while (!tryLockBoth()) {
141  // Spin. But, note that all the unbounded spinning in tryLockBoth can be
142  // straightforwardly futex-ified. There's a potentialy starvation issue
143  // here, but note that it can be dealt with by adding a "priority" bit to
144  // the state machine (i.e. if my priority bit is set, the thread for whom
145  // I'm the local member of the pair gets to win the race, rather than
146  // using address-ordering).
147  }
148  }
149 
150  void unlockBoth() {
151  // Note that unlocking locally and then remotely is the right order. There
152  // are no concurrent accesses to this object (as an API constraint -- lock
153  // and unlock are not thread safe!), and no other thread will touch the
154  // other object so long as its locked. Going in the other order could let
155  // another thread incorrectly think we're going down the deadlock-avoidance
156  // path in tryLock().
157  stateMachine.store(kUnlocked, std::memory_order_release);
158  if (dual != nullptr) {
159  dual->stateMachine.store(kUnlocked, std::memory_order_release);
160  }
161  }
162 
163  entangled() = delete;
164  entangled(const entangled&) = delete;
165  entangled& operator=(const entangled&) = delete;
166  entangled& operator=(entangled&&) = delete;
167 
168  explicit entangled(T t)
169  : stateMachine(kUnlocked), t(std::move(t)), dual(nullptr) {}
171  : stateMachine((other.lockBoth(), kLocked)),
172  t(std::move(other.t)),
173  dual(std::move(other.dual)) {
174  // Note that, above, we initialized stateMachine to the locked state; the
175  // address of this object hasn't escaped yet, and won't (until we unlock
176  // the dual), so it doesn't *really* matter, but it's conceptually helpful
177  // to maintain that invariant.
178 
179  // Update our dual's data.
180  if (dual != nullptr) {
181  dual->dual = this;
182  }
183 
184  // Update other's data.
185  other.dual = nullptr;
186  // unlock other so that its destructor can complete
187  other.stateMachine.store(kUnlocked);
188 
189  // We locked on other, but will unlock on *this. The locking protocol
190  // ensured that no accesses to other will occur after lock() returns, and
191  // since then we updated dual's dual to be us.
192  unlockBoth();
193  }
194 
196  lockBoth();
197  if (dual != nullptr) {
198  dual->dual = nullptr;
199  }
200  unlockBoth();
201  }
202 
203  // Must unlock later even if dual is nullptr. This is fixable.
205  lockBoth();
206  return !!dual ? std::addressof(dual->t) : nullptr;
207  }
208 
210  unlockBoth();
211  }
212 };
213 
214 template <class First, class Second>
215 using entangled_pair =
216  std::pair<entangled<First, Second>, entangled<Second, First>>;
217 
218 template <class First, class Second>
219 auto entangle(First f, Second s) -> entangled_pair<First, Second> {
221  entangled<Second, First> es(std::move(s));
222  ef.dual = std::addressof(es);
223  es.dual = std::addressof(ef);
224  return {std::move(ef), std::move(es)};
225 }
226 
227 template <class T, class Dual>
228 struct locked_entangled_pair : std::pair<T*, Dual*> {
231  if (!!e) {
232  e->unlockBoth();
233  }
234  }
235  explicit locked_entangled_pair(entangled<T, Dual>& e) : e(std::addressof(e)) {
236  this->e->lockBoth();
237  this->first = std::addressof(this->e->t);
238  this->second = !!this->e->dual ? std::addressof(this->e->dual->t) : nullptr;
239  }
240  locked_entangled_pair() = delete;
244  : std::pair<T*, Dual*>(o), e(o.e) {
245  o.e = nullptr;
246  }
248  static_cast<std::pair<T*, Dual*>&>(*this) =
249  static_cast<std::pair<T*, Dual*>&&>(o);
250  e = o.e;
251  o.e = nullptr;
252  return *this;
253  }
254 };
255 
256 template <class T, class Dual>
259 }
260 
261 template <class T, class Dual>
262 struct shared_entangled : std::shared_ptr<T> {
263  Dual* dual;
265 
266  template <class P>
267  explicit shared_entangled(std::shared_ptr<P>& p, T& t, Dual& d, std::mutex& l)
268  : std::shared_ptr<T>(p, std::addressof(t)),
269  dual(std::addressof(d)),
270  lock(std::addressof(l)) {}
271  shared_entangled() = delete;
272 };
273 
274 template <class First, class Second>
275 using shared_entangled_pair =
276  std::pair<shared_entangled<First, Second>, shared_entangled<Second, First>>;
277 
278 template <class First, class Second>
279 auto shared_entangle(First f, Second s)
281  struct storage {
282  storage(First&& f, Second&& s) : p((First &&) f, (Second &&) s) {}
283  std::tuple<First, Second> p;
285  };
286  auto p = std::make_shared<storage>(std::move(f), std::move(s));
288  p, std::get<0>(p->p), std::get<1>(p->p), p->lock);
289  shared_entangled<Second, First> es(
290  p, std::get<1>(p->p), std::get<0>(p->p), p->lock);
291  return {std::move(ef), std::move(es)};
292 }
293 
294 template <class T, class Dual>
295 struct locked_shared_entangled_pair : std::pair<T*, Dual*> {
298  if (!!e && !!e.lock) {
299  e.lock->unlock();
300  }
301  }
303  : e(std::move(e)) {
304  this->e.lock->lock();
305  this->first = this->e.get();
306  this->second = this->e.dual;
307  }
308  locked_shared_entangled_pair() = delete;
309 };
310 
311 template <class T, class Dual>
314 }
315 
316 } // namespace pushmi
317 } // namespace folly
locked_entangled_pair< T, Dual > lock_both(entangled< T, Dual > &e)
Definition: entangle.h:257
auto f
locked_entangled_pair(locked_entangled_pair &&o)
Definition: entangle.h:243
static const int kLocked
Definition: entangle.h:82
static const int kUnlocked
Definition: entangle.h:81
locked_shared_entangled_pair(shared_entangled< T, Dual > &e)
Definition: entangle.h:302
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
STL namespace.
entangled & operator=(const entangled &)=delete
folly::std T
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
#define nullptr
Definition: http_parser.c:41
auto shared_entangle(First f, Second s) -> shared_entangled_pair< First, Second >
Definition: entangle.h:279
std::pair< shared_entangled< First, Second >, shared_entangled< Second, First >> shared_entangled_pair
Definition: entangle.h:276
auto entangle(First f, Second s) -> entangled_pair< First, Second >
Definition: entangle.h:219
auto lock(Synchronized< D, M > &synchronized, Args &&...args)
shared_entangled(std::shared_ptr< P > &p, T &t, Dual &d, std::mutex &l)
Definition: entangle.h:267
Dual * lockPointerToDual()
Definition: entangle.h:204
entangled< T, Dual > * e
Definition: entangle.h:229
locked_entangled_pair & operator=(locked_entangled_pair &&o)
Definition: entangle.h:247
std::mutex mutex
std::atomic< int > stateMachine
Definition: entangle.h:74
shared_entangled< T, Dual > e
Definition: entangle.h:296
static set< string > s
Definition: Traits.h:577
std::pair< entangled< First, Second >, entangled< Second, First >> entangled_pair
Definition: entangle.h:216
entangled< Dual, T > * dual
Definition: entangle.h:79
entangled(entangled &&other)
Definition: entangle.h:170
static const int kLockedAndLossAcknowledged
Definition: entangle.h:83
locked_entangled_pair(entangled< T, Dual > &e)
Definition: entangle.h:235
constexpr detail::First first
Definition: Base-inl.h:2553