blob: 18fdd74453b78a053daf0dba5743e01ca90113d2 [file] [log] [blame]
// Copyright 2019 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/barrier.h>
#include <lib/fpromise/sequencer.h>
#include <lib/fpromise/single_threaded_executor.h>
#include <unistd.h>
#include <string>
#include <thread>
#include <zxtest/zxtest.h>
namespace {
// Wrapping tasks with a barrier should still allow them to complete, even without a sync.
TEST(BarrierTests, wrapping_tasks_no_sync) {
bool array[3] = {};
auto a = fpromise::make_promise([&] { array[0] = true; });
auto b = fpromise::make_promise([&] { array[1] = true; });
auto c = fpromise::make_promise([&] { array[2] = true; });
for (size_t i = 0; i < std::size(array); i++) {
EXPECT_FALSE(array[i]);
}
fpromise::barrier barrier;
fpromise::single_threaded_executor executor;
executor.schedule_task(a.wrap_with(barrier));
executor.schedule_task(b.wrap_with(barrier));
executor.schedule_task(c.wrap_with(barrier));
executor.run();
for (size_t i = 0; i < std::size(array); i++) {
EXPECT_TRUE(array[i]);
}
}
// Syncing tasks with should still allow them to complete, even without pending work.
TEST(BarrierTests, sync_no_wrapped_tasks) {
bool array[3] = {};
auto a = fpromise::make_promise([&] { array[0] = true; });
auto b = fpromise::make_promise([&] { array[1] = true; });
auto c = fpromise::make_promise([&] { array[2] = true; });
for (size_t i = 0; i < std::size(array); i++) {
EXPECT_FALSE(array[i]);
}
fpromise::barrier barrier;
fpromise::single_threaded_executor executor;
executor.schedule_task(barrier.sync().and_then(std::move(a)));
executor.schedule_task(barrier.sync().and_then(std::move(b)));
executor.schedule_task(barrier.sync().and_then(std::move(c)));
executor.run();
for (size_t i = 0; i < std::size(array); i++) {
EXPECT_TRUE(array[i]);
}
}
// Wrap up a bunch of work in the barrier before syncing a barrier.
// Observe that the wrapped work completes before the sync.
TEST(BarrierTests, wrap_then_sync) {
bool array[3] = {};
auto a = fpromise::make_promise([&] { array[0] = true; });
auto b = fpromise::make_promise([&] { array[1] = true; });
auto c = fpromise::make_promise([&] { array[2] = true; });
bool sync_complete = false;
auto sync_promise = fpromise::make_promise([&] {
for (size_t i = 0; i < std::size(array); i++) {
EXPECT_TRUE(array[i]);
}
sync_complete = true;
});
for (size_t i = 0; i < std::size(array); i++) {
EXPECT_FALSE(array[i]);
}
fpromise::barrier barrier;
auto a_tracked = a.wrap_with(barrier);
auto b_tracked = b.wrap_with(barrier);
auto c_tracked = c.wrap_with(barrier);
// Note that we schedule the "sync" task first, even though we expect
// it to actually be executed last. This is just a little extra nudge to ensure
// our executor isn't implicitly supplying this order for us.
fpromise::single_threaded_executor executor;
executor.schedule_task(barrier.sync().and_then(std::move(sync_promise)));
executor.schedule_task(std::move(a_tracked));
executor.schedule_task(std::move(b_tracked));
executor.schedule_task(std::move(c_tracked));
executor.run();
EXPECT_TRUE(sync_complete);
}
// Observe that the order of "barrier.wrap" does not re-order the wrapped promises, but
// merely provides ordering before the sync point.
TEST(BarrierTests, wrap_preserves_initial_order) {
// Create three promises.
//
// They will be sequencer-wrapped in the order "a, b, c".
// They will be barrier-wrapped in the order "c, b, a".
//
// Observe that by wrapping them, the sequence order is still preserved.
bool array[3] = {};
auto a = fpromise::make_promise([&] {
array[0] = true;
assert(!array[1]);
assert(!array[2]);
});
auto b = fpromise::make_promise([&] {
assert(array[0]);
array[1] = true;
assert(!array[2]);
});
auto c = fpromise::make_promise([&] {
assert(array[0]);
assert(array[1]);
array[2] = true;
});
bool sync_complete = false;
auto sync_promise = fpromise::make_promise([&] {
for (size_t i = 0; i < std::size(array); i++) {
EXPECT_TRUE(array[i]);
}
sync_complete = true;
});
for (size_t i = 0; i < std::size(array); i++) {
EXPECT_FALSE(array[i]);
}
fpromise::sequencer seq;
auto a_sequenced = a.wrap_with(seq);
auto b_sequenced = b.wrap_with(seq);
auto c_sequenced = c.wrap_with(seq);
fpromise::barrier barrier;
auto c_tracked = c_sequenced.wrap_with(barrier);
auto b_tracked = b_sequenced.wrap_with(barrier);
auto a_tracked = a_sequenced.wrap_with(barrier);
fpromise::single_threaded_executor executor;
executor.schedule_task(barrier.sync().and_then(std::move(sync_promise)));
executor.schedule_task(std::move(a_tracked));
executor.schedule_task(std::move(b_tracked));
executor.schedule_task(std::move(c_tracked));
executor.run();
EXPECT_TRUE(sync_complete);
}
// Observe that promises chained after the "wrap" request do not block the sync.
TEST(BarrierTests, work_after_wrap_non_blocking) {
bool work_complete = false;
auto work = fpromise::make_promise([&] { work_complete = true; });
bool sync_complete = false;
auto sync_promise = fpromise::make_promise([&] {
assert(work_complete);
sync_complete = true;
});
fpromise::barrier barrier;
auto work_wrapped =
barrier.wrap(std::move(work))
.then([&](fpromise::context& context, fpromise::result<>&) -> fpromise::result<> {
// If the full chain of execution after "work" was required to complete
// before sync, then "sync_complete" will remain false forever, and this
// task will never be completed.
if (!sync_complete) {
context.suspend_task().resume_task();
return fpromise::pending();
}
return fpromise::ok();
});
fpromise::single_threaded_executor executor;
executor.schedule_task(std::move(work_wrapped));
executor.schedule_task(barrier.sync().and_then(std::move(sync_promise)));
executor.run();
EXPECT_TRUE(work_complete);
EXPECT_TRUE(sync_complete);
}
// Observe that back-to-back sync operations are still ordered, and cannot
// skip ahead of previously wrapped work.
TEST(BarrierTests, multiple_syncs_after_work_are_ordered) {
bool work_complete = false;
auto work = fpromise::make_promise([&] { work_complete = true; });
bool syncs_complete[] = {false, false};
fpromise::promise<void, void> sync_promises[] = {
fpromise::make_promise([&] {
assert(work_complete);
assert(!syncs_complete[1]);
syncs_complete[0] = true;
}),
fpromise::make_promise([&] {
assert(work_complete);
assert(syncs_complete[0]);
syncs_complete[1] = true;
}),
};
fpromise::barrier barrier;
auto work_wrapped = work.wrap_with(barrier);
fpromise::single_threaded_executor executor;
executor.schedule_task(barrier.sync().and_then(std::move(sync_promises[0])));
executor.schedule_task(barrier.sync().and_then(std::move(sync_promises[1])));
executor.schedule_task(std::move(work_wrapped));
executor.run();
EXPECT_TRUE(work_complete);
EXPECT_TRUE(syncs_complete[0]);
EXPECT_TRUE(syncs_complete[1]);
}
// Abandoning promises should still allow sync to complete.
TEST(BarrierTests, abandoned_promises_are_ordered_by_sync) {
auto work = fpromise::make_promise([&] { assert(false); });
bool sync_complete = false;
auto sync_promise = fpromise::make_promise([&] { sync_complete = true; });
fpromise::barrier barrier;
fpromise::single_threaded_executor executor;
{
auto work_wrapped = work.wrap_with(barrier);
executor.schedule_task(barrier.sync().and_then(std::move(sync_promise)));
// "work_wrapped" is destroyed (abandoned) here.
}
executor.run();
EXPECT_TRUE(sync_complete);
}
} // namespace