blob: cf6a99c4a10925e85e08792a0f8a1f8556236819 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Can't compile this for Zircon userspace yet since libstdc++ isn't available.
#ifndef FIT_NO_STD_FOR_ZIRCON_USERSPACE
#include <lib/fit/scope.h>
namespace fit {
scope::scope()
: state_(new state()) {}
scope::~scope() {
state_->exit(true /*scope_was_destroyed*/);
}
scope::state::state() = default;
scope::state::~state() {
assert(acquired_promise_count_.load(std::memory_order_relaxed) ==
scope_exited);
assert(scope_was_destroyed_);
assert(promise_handle_count_ == 0);
assert(head_promise_holder_ == nullptr);
}
bool scope::state::exited() const {
return acquired_promise_count_.load(std::memory_order_relaxed) & scope_exited;
}
void scope::state::exit(bool scope_was_destroyed) {
promise_holder_base* release_head = nullptr;
bool delete_self = false;
{
std::lock_guard<std::mutex> lock(mutex_);
assert(!scope_was_destroyed_);
scope_was_destroyed_ = scope_was_destroyed;
// Atomically exit the scope. We cannot do this safely if there are
// any running promises since they might still be accessing state which
// is guarded by the scope. Worse, if a promise re-entrantly destroys
// the scope during its execution then as a side-effect the promise
// itself will be destroyed. So assert!
uint64_t prior_count = acquired_promise_count_.exchange(
scope_exited, std::memory_order_relaxed);
if (!(prior_count & scope_exited)) {
// Cannot exit fit::scope while any of its promises are running!
assert(prior_count == 0);
// Take the promises so they can be deleted outside of the lock.
release_head = head_promise_holder_;
head_promise_holder_ = nullptr;
}
// If there are no more handles then we can delete the state now.
delete_self = should_delete_self();
}
// Delete aborted promises and self outside of the lock.
while (release_head) {
promise_holder_base* release_next = release_head->next;
delete release_head;
release_head = release_next;
}
if (delete_self) {
delete this;
}
}
scope::promise_handle scope::state::adopt_promise(
promise_holder_base* promise_holder) {
{
std::lock_guard<std::mutex> lock(mutex_);
assert(!scope_was_destroyed_); // otherwise how did we get here?
// If the scope hasn't been exited yet, link the promise and mint
// a new handle. Otherwise we will abort the promise.
if (!exited()) {
if (head_promise_holder_) {
head_promise_holder_->prev = promise_holder;
promise_holder->next = head_promise_holder_;
}
head_promise_holder_ = promise_holder;
promise_handle_count_++;
return promise_handle(this, promise_holder);
}
}
// Delete aborted promise outside of the lock.
delete promise_holder;
return promise_handle{};
}
void scope::state::drop_promise(promise_handle promise_handle) {
if (!promise_handle.promise_holder) {
return; // invalid handle, nothing to do
}
{
std::lock_guard<std::mutex> lock(promise_handle.state->mutex_);
// If the scope hasn't been exited yet, unlink the promise and
// prepare to destroy it. Otherwise, it's already been unlinked
// and destroyed so release the handle but don't touch the pointer!
assert(promise_handle.state->promise_handle_count_ > 0);
promise_handle.state->promise_handle_count_--;
if (!promise_handle.state->exited()) {
if (promise_handle.promise_holder->next) {
promise_handle.promise_holder->next->prev =
promise_handle.promise_holder->prev;
}
if (promise_handle.promise_holder->prev) {
promise_handle.promise_holder->prev->next =
promise_handle.promise_holder->next;
} else {
promise_handle.state->head_promise_holder_ =
promise_handle.promise_holder->next;
}
// Fallthrough to delete the promise.
} else if (!promise_handle.state->should_delete_self()) {
return;
} else {
// Fallthrough to delete self.
promise_handle.promise_holder = nullptr;
}
}
// Delete the promise or scope outside of the lock.
if (promise_handle.promise_holder) {
delete promise_handle.promise_holder;
} else {
delete promise_handle.state;
}
}
scope::promise_holder_base* scope::state::try_acquire_promise(
promise_handle promise_handle) {
if (promise_handle.promise_holder) {
uint64_t prior_count = promise_handle.state->acquired_promise_count_.fetch_add(
1u, std::memory_order_relaxed);
if (!(prior_count & scope_exited)) {
return promise_handle.promise_holder;
}
promise_handle.state->acquired_promise_count_.fetch_sub(
1u, std::memory_order_relaxed);
}
return nullptr;
}
void scope::state::release_promise(promise_handle promise_handle) {
promise_handle.state->acquired_promise_count_.fetch_sub(
1u, std::memory_order_relaxed);
}
} // namespace fit
#endif // FIT_NO_STD_FOR_ZIRCON_USERSPACE