1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Can't compile this for Zircon userspace yet since libstdc++ isn't available.
6 #ifndef FIT_NO_STD_FOR_ZIRCON_USERSPACE
7
8 #include <lib/fit/scope.h>
9
10 namespace fit {
11
scope()12 scope::scope()
13 : state_(new state()) {}
14
~scope()15 scope::~scope() {
16 state_->exit(true /*scope_was_destroyed*/);
17 }
18
19 scope::state::state() = default;
20
~state()21 scope::state::~state() {
22 assert(acquired_promise_count_.load(std::memory_order_relaxed) ==
23 scope_exited);
24 assert(scope_was_destroyed_);
25 assert(promise_handle_count_ == 0);
26 assert(head_promise_holder_ == nullptr);
27 }
28
exited() const29 bool scope::state::exited() const {
30 return acquired_promise_count_.load(std::memory_order_relaxed) & scope_exited;
31 }
32
exit(bool scope_was_destroyed)33 void scope::state::exit(bool scope_was_destroyed) {
34 promise_holder_base* release_head = nullptr;
35 bool delete_self = false;
36 {
37 std::lock_guard<std::mutex> lock(mutex_);
38 assert(!scope_was_destroyed_);
39 scope_was_destroyed_ = scope_was_destroyed;
40
41 // Atomically exit the scope. We cannot do this safely if there are
42 // any running promises since they might still be accessing state which
43 // is guarded by the scope. Worse, if a promise re-entrantly destroys
44 // the scope during its execution then as a side-effect the promise
45 // itself will be destroyed. So assert!
46 uint64_t prior_count = acquired_promise_count_.exchange(
47 scope_exited, std::memory_order_relaxed);
48 if (!(prior_count & scope_exited)) {
49 // Cannot exit fit::scope while any of its promises are running!
50 assert(prior_count == 0);
51
52 // Take the promises so they can be deleted outside of the lock.
53 release_head = head_promise_holder_;
54 head_promise_holder_ = nullptr;
55 }
56
57 // If there are no more handles then we can delete the state now.
58 delete_self = should_delete_self();
59 }
60
61 // Delete aborted promises and self outside of the lock.
62 while (release_head) {
63 promise_holder_base* release_next = release_head->next;
64 delete release_head;
65 release_head = release_next;
66 }
67 if (delete_self) {
68 delete this;
69 }
70 }
71
adopt_promise(promise_holder_base * promise_holder)72 scope::promise_handle scope::state::adopt_promise(
73 promise_holder_base* promise_holder) {
74 {
75 std::lock_guard<std::mutex> lock(mutex_);
76 assert(!scope_was_destroyed_); // otherwise how did we get here?
77
78 // If the scope hasn't been exited yet, link the promise and mint
79 // a new handle. Otherwise we will abort the promise.
80 if (!exited()) {
81 if (head_promise_holder_) {
82 head_promise_holder_->prev = promise_holder;
83 promise_holder->next = head_promise_holder_;
84 }
85 head_promise_holder_ = promise_holder;
86 promise_handle_count_++;
87 return promise_handle(this, promise_holder);
88 }
89 }
90
91 // Delete aborted promise outside of the lock.
92 delete promise_holder;
93 return promise_handle{};
94 }
95
drop_promise(promise_handle promise_handle)96 void scope::state::drop_promise(promise_handle promise_handle) {
97 if (!promise_handle.promise_holder) {
98 return; // invalid handle, nothing to do
99 }
100
101 {
102 std::lock_guard<std::mutex> lock(promise_handle.state->mutex_);
103
104 // If the scope hasn't been exited yet, unlink the promise and
105 // prepare to destroy it. Otherwise, it's already been unlinked
106 // and destroyed so release the handle but don't touch the pointer!
107 assert(promise_handle.state->promise_handle_count_ > 0);
108 promise_handle.state->promise_handle_count_--;
109 if (!promise_handle.state->exited()) {
110 if (promise_handle.promise_holder->next) {
111 promise_handle.promise_holder->next->prev =
112 promise_handle.promise_holder->prev;
113 }
114 if (promise_handle.promise_holder->prev) {
115 promise_handle.promise_holder->prev->next =
116 promise_handle.promise_holder->next;
117 } else {
118 promise_handle.state->head_promise_holder_ =
119 promise_handle.promise_holder->next;
120 }
121 // Fallthrough to delete the promise.
122 } else if (!promise_handle.state->should_delete_self()) {
123 return;
124 } else {
125 // Fallthrough to delete self.
126 promise_handle.promise_holder = nullptr;
127 }
128 }
129
130 // Delete the promise or scope outside of the lock.
131 if (promise_handle.promise_holder) {
132 delete promise_handle.promise_holder;
133 } else {
134 delete promise_handle.state;
135 }
136 }
137
try_acquire_promise(promise_handle promise_handle)138 scope::promise_holder_base* scope::state::try_acquire_promise(
139 promise_handle promise_handle) {
140 if (promise_handle.promise_holder) {
141 uint64_t prior_count = promise_handle.state->acquired_promise_count_.fetch_add(
142 1u, std::memory_order_relaxed);
143 if (!(prior_count & scope_exited)) {
144 return promise_handle.promise_holder;
145 }
146 promise_handle.state->acquired_promise_count_.fetch_sub(
147 1u, std::memory_order_relaxed);
148 }
149 return nullptr;
150 }
151
release_promise(promise_handle promise_handle)152 void scope::state::release_promise(promise_handle promise_handle) {
153 promise_handle.state->acquired_promise_count_.fetch_sub(
154 1u, std::memory_order_relaxed);
155 }
156
157 } // namespace fit
158
159 #endif // FIT_NO_STD_FOR_ZIRCON_USERSPACE
160