[revert][fit][scope] Revert "[fit][scope] Add a mechanism for scoping promises."
This reverts commit 4c6dbccd6c3fbb0f98a2297b1ad9b3361efd11bf.
Reason for revert: Causing roller failures.
Original change's description:
> [fit][scope] Add a mechanism for scoping promises.
>
> fit::scope is a mechanism for wrapping promises such that they will
> automatically be abandoned if the scope is exited before they complete.
> Can be very useful when handing out promises that capture references
> to "this" or to local variables.
>
> Test: fit-test
> Change-Id: I31a37d67e745156d3ee255fa1ed1060379d32bab
TBR=jeffbrown@google.com,thatguy@google.com,geb@google.com
Change-Id: I93594ec7daa97be647c8c241e0aa75381ba0c525
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
diff --git a/system/ulib/fit/include/lib/fit/scope.h b/system/ulib/fit/include/lib/fit/scope.h
deleted file mode 100644
index 6dc0bfc..0000000
--- a/system/ulib/fit/include/lib/fit/scope.h
+++ /dev/null
@@ -1,279 +0,0 @@
-// 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.
-
-#ifndef LIB_FIT_SCOPE_H_
-#define LIB_FIT_SCOPE_H_
-
-#include <assert.h>
-
-#include <atomic>
-#include <mutex>
-
-#include "promise.h"
-#include "thread_safety.h"
-
-namespace fit {
-
-// Provides a mechanism for binding promises to the lifetime of another object
-// such that they are destroyed before that object goes out of scope. It is
-// particularly useful for ensuring that the lifetime of a promise does not
-// exceed the lifetime of any variables that it has captured by reference.
-//
-// A scope is thread-safe but non-reentrant: it must not be destroyed while
-// any of its associated promises are running.
-//
-// EXAMPLE
-//
-// Define a |fit::scope| as a member of the object to whose lifetime the
-// promises should be bound.
-//
-// // We mark this class final because its destructor has side-effects
-// // that rely on the order of destruction. If this object were
-// // subclassed there would be a possibility for promises bound to its
-// // scope to inadvertently access the subclass's state while the object
-// // was being destroyed.
-// class accumulator final {
-// public:
-// accumulator() = default;
-// ~accumulator() = default;
-//
-// fit::promise<int> accumulate(int value);
-//
-// private:
-// int prior_total_ = 0;
-//
-// // This member is last so that the scope is exited before all
-// // other members of the object are destroyed. Alternately, we
-// // could enforce this ordering by explicitly invoking
-// // |fit::scope::exit()| where appropriate.
-// fit::scope scope_;
-// };
-//
-// Use |fit::promise::wrap_with()| to wrap up promises that capture pointers
-// to the object. In this example, the captured pointer is "this".
-//
-// fit::promise<int> accumulator::accumulate(int value) {
-// return fit::make_promise([this, value] {
-// prior_total_ += value;
-// return fit::ok(prior_total_);
-// }).wrap_with(scope_); /* binding to scope happens here */
-// }
-//
-class scope final {
-public:
- // Creates a new scope.
- scope();
-
- // Exits the scope and destroys all of its wrapped promises.
- // Asserts that no promises are currently running.
- ~scope();
-
- // Returns true if the scope has been exited.
- //
- // This method is thread-safe.
- bool exited() const { return state_->exited(); }
-
- // Exits the scope and destroys all of its wrapped promises.
- // Assets that no promises are currently running.
- //
- // This method is thread-safe.
- void exit() { return state_->exit(false /*scope_was_destroyed*/); }
-
- // Returns a promise which wraps the specified |promise| and binds the
- // promise to this scope.
- //
- // The specified promise will automatically be destroyed when its wrapper
- // is destroyed or when the scope is exited. If the scope has already
- // exited then the wrapped promise will be immediately destroyed.
- //
- // When the returned promise is invoked before the scope is exited,
- // the promise that it wraps will be invoked as usual. However, when
- // the returned promise is invoked after the scope is exited, it
- // immediately returns a pending result (since the promise that it
- // previously wrapped has already been destroyed). By returning a
- // pending result, the return promise effectively indicates to the
- // executor that the task has been "abandoned" due to the scope being
- // exited.
- //
- // This method is thread-safe.
- template <typename Promise>
- decltype(auto) wrap(Promise promise) {
- assert(promise);
- return fit::make_promise_with_continuation(
- scoped_continuation<Promise>(
- state_->link_promise(
- new promise_holder<Promise>(std::move(promise)))));
- }
-
- scope(const scope&) = delete;
- scope(scope&&) = delete;
- scope& operator=(const scope&) = delete;
- scope& operator=(scope&&) = delete;
-
-private:
- class state;
- class promise_holder_base;
-
- // Holds a reference to a promise that is owned by the state.
- class promise_handle final {
- public:
- promise_handle() = default;
-
- private:
- // |state| and |promise_holder| belong to the state object.
- // Invariant: If |promise_holder| is non-null then |state| is
- // also non-null.
- friend state;
- promise_handle(state* state, promise_holder_base* promise_holder)
- : state(state), promise_holder(promise_holder) {}
-
- state* state = nullptr;
- promise_holder_base* promise_holder = nullptr;
- };
-
- // Holds the shared state of the scope.
- // This object is destroyed once the scope and all of its promises
- // have been destroyed.
- class state final {
- public:
- state();
- ~state();
-
- // The following methods are called from the |scope|.
-
- bool exited() const;
- void exit(bool scope_was_destroyed);
-
- // The following methods are called from the |scoped_continuation|.
-
- // Links a promise to the scope's lifecycle such that it will be
- // destroyed when the scope is exited. Returns a handle that may
- // be used to access the promise later.
- // The state takes ownership of the promise.
- promise_handle link_promise(promise_holder_base* promise_holder);
-
- // Unlinks a promise from the scope's lifecycle given its handle
- // and causes the underlying promise to be destroyed if it hasn't
- // already been destroyed due to the scope exiting.
- // Does nothing if the handle was default-initialized.
- static void unlink_and_drop_promise(promise_handle promise_handle);
-
- // Acquires a promise given its handle.
- // Returns nullptr if the handle was default-initialized or if
- // the scope exited, meaning that the promise was not acquired.
- // The promise must be released before it can be acquired again.
- static promise_holder_base* try_acquire_promise(
- promise_handle promise_handle);
-
- // Releases a promise that was successfully acquired.
- static void release_promise(promise_handle promise_handle);
-
- state(const state&) = delete;
- state(state&&) = delete;
- state& operator=(const state&) = delete;
- state& operator=(state&&) = delete;
-
- private:
- bool should_delete_self() const FIT_GUARDED(mutex_) {
- return scope_was_destroyed_ && promise_handle_count_ == 0;
- }
-
- static constexpr uint64_t scope_exited = static_cast<uint64_t>(1u) << 63;
-
- // Tracks of the number of promises currently running ("acquired").
- // The top bit is set when the scope is exited, at which point no
- // new promises can be acquired. After exiting, the count can
- // be incremented transiently but is immediately decremented again
- // until all promise handles have been released. Once no promise
- // handles remain, the count will equal |scope_exited| and will not
- // change again.
- std::atomic_uint64_t acquired_promise_count_{0};
-
- mutable std::mutex mutex_;
- bool scope_was_destroyed_ FIT_GUARDED(mutex_) = false;
- uint64_t promise_handle_count_ FIT_GUARDED(mutex_) = 0;
- promise_holder_base* head_promise_holder_ FIT_GUARDED(mutex_) = nullptr;
- };
-
- // Base type for managing the lifetime of a promise of any type.
- // It is owned by the state and retained indirectly by the continuation
- // using a |promise_handle|.
- class promise_holder_base {
- public:
- promise_holder_base() = default;
- virtual ~promise_holder_base() = default;
-
- promise_holder_base(const promise_holder_base&) = delete;
- promise_holder_base(promise_holder_base&&) = delete;
- promise_holder_base& operator=(const promise_holder_base&) = delete;
- promise_holder_base& operator=(promise_holder_base&&) = delete;
-
- private:
- // |next| and |prev| belong to the state object.
- friend class state;
- promise_holder_base* next = nullptr;
- promise_holder_base* prev = nullptr;
- };
-
- // Holder for a promise of a particular type.
- template <typename Promise>
- class promise_holder final : public promise_holder_base {
- public:
- explicit promise_holder(Promise promise)
- : promise(std::move(promise)) {}
- ~promise_holder() override = default;
-
- Promise promise;
- };
-
- // Wraps a promise whose lifetime is managed by the scope.
- template <typename Promise>
- class scoped_continuation final {
- public:
- explicit scoped_continuation(promise_handle promise_handle)
- : promise_handle_(promise_handle) {}
-
- scoped_continuation(scoped_continuation&& other)
- : promise_handle_(other.promise_handle_) {
- other.promise_handle_ = promise_handle{};
- }
-
- ~scoped_continuation() {
- state::unlink_and_drop_promise(promise_handle_);
- }
-
- typename Promise::result_type operator()(context& context) {
- typename Promise::result_type result;
- auto holder = static_cast<promise_holder<Promise>*>(
- state::try_acquire_promise(promise_handle_));
- if (holder) {
- result = holder->promise(context);
- state::release_promise(promise_handle_);
- }
- return result;
- }
-
- scoped_continuation& operator=(scoped_continuation&& other) {
- if (this != &other) {
- state::unlink_and_drop_promise(promise_handle_);
- promise_handle_ = other.promise_handle_;
- other.promise_handle_ = promise_handle{};
- }
- return *this;
- }
-
- scoped_continuation(const scoped_continuation&) = delete;
- scoped_continuation& operator=(const scoped_continuation&) = delete;
-
- private:
- promise_handle promise_handle_;
- };
-
- // The scope's shared state.
- state* const state_;
-};
-
-} // namespace fit
-
-#endif // LIB_FIT_SCOPE_H_
diff --git a/system/ulib/fit/rules.mk b/system/ulib/fit/rules.mk
index 692bbb7..80e0b82 100644
--- a/system/ulib/fit/rules.mk
+++ b/system/ulib/fit/rules.mk
@@ -7,7 +7,6 @@
fit_srcs := \
$(LOCAL_DIR)/promise.cpp \
$(LOCAL_DIR)/scheduler.cpp \
- $(LOCAL_DIR)/scope.cpp \
$(LOCAL_DIR)/sequencer.cpp \
$(LOCAL_DIR)/single_threaded_executor.cpp \
diff --git a/system/ulib/fit/scope.cpp b/system/ulib/fit/scope.cpp
deleted file mode 100644
index c1cbea7..0000000
--- a/system/ulib/fit/scope.cpp
+++ /dev/null
@@ -1,159 +0,0 @@
-// 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::link_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::unlink_and_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
diff --git a/system/utest/fit/rules.mk b/system/utest/fit/rules.mk
index efaa2aa..9443e4a 100644
--- a/system/utest/fit/rules.mk
+++ b/system/utest/fit/rules.mk
@@ -21,7 +21,6 @@
$(LOCAL_DIR)/promise_tests.cpp \
$(LOCAL_DIR)/result_tests.cpp \
$(LOCAL_DIR)/scheduler_tests.cpp \
- $(LOCAL_DIR)/scope_tests.cpp \
$(LOCAL_DIR)/sequencer_tests.cpp \
$(LOCAL_DIR)/single_threaded_executor_tests.cpp \
$(LOCAL_DIR)/suspended_task_tests.cpp \
diff --git a/system/utest/fit/scope_tests.cpp b/system/utest/fit/scope_tests.cpp
deleted file mode 100644
index aa41cdf..0000000
--- a/system/utest/fit/scope_tests.cpp
+++ /dev/null
@@ -1,253 +0,0 @@
-// 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 <unistd.h>
-
-#include <thread>
-
-#include <lib/fit/bridge.h>
-#include <lib/fit/defer.h>
-#include <lib/fit/scope.h>
-#include <lib/fit/single_threaded_executor.h>
-#include <unittest/unittest.h>
-
-#include "unittest_utils.h"
-
-namespace {
-
-class fake_context : public fit::context {
-public:
- fit::executor* executor() const override {
- ASSERT_CRITICAL(false);
- }
- fit::suspended_task suspend_task() override {
- ASSERT_CRITICAL(false);
- }
-};
-
-// Asynchronously accumulates a sum.
-// This is an example of an object that offers promises that captures
-// the "this" pointer, thereby needing a scope to prevent dangling pointers
-// in case it is destroyed before the promises complete.
-class accumulator {
-public:
- // Adds a value to the counter then returns it.
- // Takes time proportional to the value being added.
- fit::promise<uint32_t> add(uint32_t value) {
- return fit::make_promise(
- [this, cycles = value](fit::context& context) mutable
- -> fit::result<uint32_t> {
- if (cycles == 0)
- return fit::ok(counter_);
- counter_++;
- cycles--;
- context.suspend_task().resume_task();
- return fit::pending();
- })
- .wrap_with(scope_);
- }
-
- // Gets the current count, immediately.
- uint32_t count() const { return counter_; }
-
-private:
- fit::scope scope_;
- uint32_t counter_ = 0;
-};
-
-bool scoping_tasks() {
- BEGIN_TEST;
-
- auto acc = std::make_unique<accumulator>();
- fit::single_threaded_executor executor;
- uint32_t sums[4] = {};
-
- // Schedule some tasks which accumulate values asynchronously.
- executor.schedule_task(acc->add(2).and_then(
- [&](uint32_t value) { sums[0] = value; }));
- executor.schedule_task(acc->add(1).and_then(
- [&](uint32_t value) { sums[1] = value; }));
- executor.schedule_task(acc->add(5).and_then(
- [&](uint32_t value) { sums[2] = value; }));
-
- // Schedule a task which accumulates and then destroys the accumulator
- // so that the scope is exited. Any remaining promises will be aborted.
- uint32_t last_count = 0;
- executor.schedule_task(acc->add(3).and_then(
- [&](uint32_t value) {
- sums[3] = value;
- // Schedule destruction in another task to avoid re-entrance.
- executor.schedule_task(fit::make_promise([&] {
- last_count = acc->count();
- acc.reset();
- }));
- }));
-
- // Run the tasks.
- executor.run();
-
- // The counts reflect the fact that the scope is exited part-way through
- // the cycle. For example, the sums[2] task doesn't get to run since
- // it only runs after 5 cycles and the scope is exited on the third.
- EXPECT_EQ(11, last_count);
- EXPECT_EQ(7, sums[0]);
- EXPECT_EQ(5, sums[1]);
- EXPECT_EQ(0, sums[2]);
- EXPECT_EQ(10, sums[3]);
-
- END_TEST;
-}
-
-bool exit_destroys_wrapped_promises() {
- BEGIN_TEST;
-
- fit::scope scope;
- EXPECT_FALSE(scope.exited());
-
- // Set up three wrapped promises.
- bool destroyed[4] = {};
- auto p0 = scope.wrap(fit::make_promise(
- [d = fit::defer([&] { destroyed[0] = true; })] { return fit::ok(); }));
- auto p1 = scope.wrap(fit::make_promise(
- [d = fit::defer([&] { destroyed[1] = true; })] { return fit::ok(); }));
- auto p2 = scope.wrap(fit::make_promise(
- [d = fit::defer([&] { destroyed[2] = true; })] { return fit::ok(); }));
- EXPECT_FALSE(destroyed[0]);
- EXPECT_FALSE(destroyed[1]);
- EXPECT_FALSE(destroyed[2]);
-
- // Execute one of them to completion, causing it to be destroyed.
- EXPECT_TRUE(fit::run_single_threaded(std::move(p1)).is_ok());
- EXPECT_FALSE(destroyed[0]);
- EXPECT_TRUE(destroyed[1]);
- EXPECT_FALSE(destroyed[2]);
-
- // Exit the scope, causing the wrapped promise to be destroyed
- // while still leaving the wrapper alive (but aborted).
- scope.exit();
- EXPECT_TRUE(scope.exited());
- EXPECT_TRUE(destroyed[0]);
- EXPECT_TRUE(destroyed[1]);
- EXPECT_TRUE(destroyed[2]);
-
- // Wrapping another promise causes the wrapped promise to be immediately
- // destroyed.
- auto p3 = scope.wrap(fit::make_promise(
- [d = fit::defer([&] { destroyed[3] = true; })] { return fit::ok(); }));
- EXPECT_TRUE(destroyed[3]);
-
- // Executing the wrapped promises returns pending.
- EXPECT_TRUE(fit::run_single_threaded(std::move(p0)).is_pending());
- EXPECT_TRUE(fit::run_single_threaded(std::move(p2)).is_pending());
- EXPECT_TRUE(fit::run_single_threaded(std::move(p3)).is_pending());
-
- // Exiting again has no effect.
- scope.exit();
- EXPECT_TRUE(scope.exited());
-
- END_TEST;
-}
-
-bool double_wrap() {
- BEGIN_TEST;
-
- fit::scope scope;
- fake_context context;
-
- // Here we wrap a task that's already been wrapped to see what happens
- // when the scope is exited. This is interesting because it means that
- // the destruction of one wrapped promise will cause the destruction of
- // another wrapped promise and could uncover re-entrance issues.
- uint32_t run_count = 0;
- bool destroyed = false;
- auto promise =
- fit::make_promise(
- [&, d = fit::defer([&] { destroyed = true; })](fit::context& context) {
- run_count++;
- return fit::pending();
- })
- .wrap_with(scope)
- .wrap_with(scope); // wrap again!
-
- // Run the promise once to show that we can.
- EXPECT_EQ(fit::result_state::pending, promise(context).state());
- EXPECT_EQ(1, run_count);
- EXPECT_FALSE(destroyed);
-
- // Now exit the scope, which should cause the promise to be destroyed.
- scope.exit();
- EXPECT_EQ(1, run_count);
- EXPECT_TRUE(destroyed);
-
- // Running the promise again should do nothing.
- EXPECT_EQ(fit::result_state::pending, promise(context).state());
- EXPECT_EQ(1, run_count);
- EXPECT_TRUE(destroyed);
-
- END_TEST;
-}
-
-bool thread_safety() {
- BEGIN_TEST;
-
- fit::scope scope;
- fit::single_threaded_executor executor;
- uint64_t run_count = 0;
-
- // Schedule work from a few threads, just to show that we can.
- // Part way through, exit the scope.
- constexpr int num_threads = 4;
- constexpr int num_tasks_per_thread = 100;
- constexpr int exit_threshold = 75;
- std::thread threads[num_threads];
- for (int i = 0; i < num_threads; i++) {
- fit::bridge bridge;
- threads[i] =
- std::thread([&, completer = std::move(bridge.completer)]() mutable {
- for (int j = 0; j < num_tasks_per_thread; j++) {
- if (j == exit_threshold) {
- executor.schedule_task(fit::make_promise([&] {
- scope.exit();
- }));
- }
-
- executor.schedule_task(
- fit::make_promise([&] {
- run_count++;
- }).wrap_with(scope));
- }
- completer.complete_ok();
- });
- executor.schedule_task(bridge.consumer.promise());
- }
-
- // Run the tasks.
- executor.run();
- for (int i = 0; i < num_threads; i++)
- threads[i].join();
-
- // We expect some non-deterministic number of tasks to have run
- // related to the exit threshold.
- // We scheduled num_threads * num_tasks_per_thread tasks, but on each thread
- // we exited the (common) scope after scheduling its first exit_threshold
- // tasks. Once one of those threads exits the scope, no more tasks
- // (scheduled by any thread) will run within the scope, so the number of
- // executed tasks cannot increase any further. Therefore we know that at
- // least exit_threshold tasks have run but we could have run as many as
- // num_threads * exit_threshold in a perfect world where all of the threads
- // called scope.exit() at the same time.
- EXPECT_GE(run_count, exit_threshold);
- EXPECT_LE(run_count, num_threads * exit_threshold);
-
- END_TEST;
-}
-
-} // namespace
-
-BEGIN_TEST_CASE(scope_tests)
-RUN_TEST(scoping_tasks)
-RUN_TEST(exit_destroys_wrapped_promises)
-RUN_TEST(double_wrap)
-RUN_TEST(thread_safety)
-END_TEST_CASE(scope_tests)