1 | /* |
2 | * Copyright 2015-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 | |
17 | #include <folly/futures/Barrier.h> |
18 | #include <folly/lang/Exception.h> |
19 | |
20 | namespace folly { |
21 | namespace futures { |
22 | |
23 | Barrier::Barrier(uint32_t n) |
24 | : size_(n), controlBlock_(allocateControlBlock()) {} |
25 | |
26 | Barrier::~Barrier() { |
27 | auto block = controlBlock_.load(std::memory_order_relaxed); |
28 | auto prev = block->valueAndReaderCount.load(std::memory_order_relaxed); |
29 | DCHECK_EQ(prev >> kReaderShift, 0u); |
30 | auto val = prev & kValueMask; |
31 | auto p = promises(block); |
32 | |
33 | for (uint32_t i = 0; i < val; ++i) { |
34 | p[i].setException( |
35 | folly::make_exception_wrapper<std::runtime_error>("Barrier destroyed" )); |
36 | } |
37 | |
38 | freeControlBlock(controlBlock_); |
39 | } |
40 | |
41 | auto Barrier::allocateControlBlock() -> ControlBlock* { |
42 | auto storage = malloc(controlBlockSize(size_)); |
43 | if (!storage) { |
44 | throw_exception<std::bad_alloc>(); |
45 | } |
46 | auto block = ::new (storage) ControlBlock(); |
47 | |
48 | auto p = promises(block); |
49 | uint32_t i = 0; |
50 | try { |
51 | for (i = 0; i < size_; ++i) { |
52 | new (p + i) BoolPromise(); |
53 | } |
54 | } catch (...) { |
55 | for (; i != 0; --i) { |
56 | p[i - 1].~BoolPromise(); |
57 | } |
58 | throw; |
59 | } |
60 | |
61 | return block; |
62 | } |
63 | |
64 | void Barrier::freeControlBlock(ControlBlock* block) { |
65 | auto p = promises(block); |
66 | for (uint32_t i = size_; i != 0; --i) { |
67 | p[i - 1].~BoolPromise(); |
68 | } |
69 | free(block); |
70 | } |
71 | |
72 | folly::Future<bool> Barrier::wait() { |
73 | // Load the current control block first. As we know there is at least |
74 | // one thread in the current epoch (us), this means that the value is |
75 | // < size_, so controlBlock_ can't change until we bump the value below. |
76 | auto block = controlBlock_.load(std::memory_order_acquire); |
77 | auto p = promises(block); |
78 | |
79 | // Bump the value and record ourselves as reader. |
80 | // This ensures that block stays allocated, as the reader count is > 0. |
81 | auto prev = block->valueAndReaderCount.fetch_add( |
82 | kReader + 1, std::memory_order_acquire); |
83 | |
84 | auto prevValue = static_cast<uint32_t>(prev & kValueMask); |
85 | DCHECK_LT(prevValue, size_); |
86 | auto future = p[prevValue].getFuture(); |
87 | |
88 | if (prevValue + 1 == size_) { |
89 | // Need to reset the barrier before fulfilling any futures. This is |
90 | // when the epoch is flipped to the next. |
91 | controlBlock_.store(allocateControlBlock(), std::memory_order_release); |
92 | |
93 | p[0].setValue(true); |
94 | for (uint32_t i = 1; i < size_; ++i) { |
95 | p[i].setValue(false); |
96 | } |
97 | } |
98 | |
99 | // Free the control block if we're the last reader at max value. |
100 | prev = |
101 | block->valueAndReaderCount.fetch_sub(kReader, std::memory_order_acq_rel); |
102 | if (prev == (kReader | uint64_t(size_))) { |
103 | freeControlBlock(block); |
104 | } |
105 | |
106 | return future; |
107 | } |
108 | |
109 | } // namespace futures |
110 | } // namespace folly |
111 | |