| // 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 |