blob: a160dfee09853d61976d7c446511b7affb3cfad2 [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.
#include <lib/fpromise/scope.h>
namespace fpromise {
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 fpromise::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 fpromise