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