| // 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/fit/bridge.h> |
| #include <lib/fit/sequencer.h> |
| #include <lib/fit/single_threaded_executor.h> |
| #include <unistd.h> |
| |
| #include <string> |
| #include <thread> |
| |
| #include <zxtest/zxtest.h> |
| |
| namespace { |
| |
| TEST(SequencerTests, sequencing_tasks) { |
| fit::sequencer seq; |
| std::string str; |
| |
| // This promise writes ":a" sequentially then writes ":a2" later. |
| auto a = fit::make_promise([&] { str += ":a"; }).wrap_with(seq).then([&](const fit::result<>&) { |
| str += ":a2"; |
| }); |
| |
| // This promise writes ":b" sequentially then writes ":b2" and ":b3" later. |
| // Also schedules another sequential task that writes ":e". |
| auto b = |
| fit::make_promise([&](fit::context& context) { |
| str += ":b"; |
| context.executor()->schedule_task(fit::make_promise([&] { str += ":e"; }).wrap_with(seq)); |
| }) |
| .wrap_with(seq) |
| .then( |
| [&, count = 0](fit::context& context, const fit::result<>&) mutable -> fit::result<> { |
| if (++count == 5) { |
| str += ":b3"; |
| return fit::error(); |
| } |
| str += ":b2"; |
| context.suspend_task().resume_task(); // immediately resume |
| return fit::pending(); |
| }); |
| |
| // This promise writes ":c" sequentially then abandons itself. |
| auto c = fit::make_promise([&](fit::context& context) { |
| str += ":c"; |
| context.suspend_task(); // abandon result |
| return fit::pending(); |
| }) |
| .wrap_with(seq) |
| .then([&](const fit::result<>&) { str += ":c2"; }); |
| |
| // This promise writes ":d" sequentially. |
| auto d = fit::make_promise([&] { str += ":d"; }).wrap_with(seq); |
| |
| // These promises just write ":z1" and ":z2" whenever they happen to run. |
| auto z1 = fit::make_promise([&] { str += ":z1"; }); |
| auto z2 = fit::make_promise([&] { str += ":z2"; }); |
| |
| // Schedule the promises in an order which intentionally does not |
| // match the sequencing. |
| fit::single_threaded_executor executor; |
| executor.schedule_task(std::move(z1)); |
| executor.schedule_task(std::move(b)); |
| executor.schedule_task(std::move(c)); |
| executor.schedule_task(std::move(a)); |
| executor.schedule_task(std::move(d)); |
| executor.schedule_task(std::move(z2)); |
| executor.run(); |
| |
| // Evaluate the promises and check the execution order. |
| EXPECT_STR_EQ(":z1:a:a2:z2:b:b2:c:b2:d:b2:e:b2:b3", str.c_str()); |
| } |
| |
| TEST(SequencerTests, thread_safety) { |
| fit::sequencer seq; |
| fit::single_threaded_executor executor; |
| uint64_t run_count = 0; |
| |
| // Schedule work from a few threads, just to show that we can. |
| constexpr int num_threads = 4; |
| constexpr int num_tasks_per_thread = 100; |
| 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++) { |
| executor.schedule_task(fit::make_promise([&] { run_count++; }).wrap_with(seq)); |
| usleep(1); |
| } |
| 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 all tasks to have run. |
| EXPECT_EQ(num_threads * num_tasks_per_thread, run_count); |
| } |
| |
| } // namespace |