blob: 41ea809f89c4ddb8bfb1704eac094896977dfe9a [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_async2/task.h"
#include "pw_async2/dispatcher_for_test.h"
#include "pw_sync/binary_semaphore.h"
#include "pw_sync/mutex.h"
#include "pw_thread/sleep.h"
#include "pw_thread/test_thread_context.h"
#include "pw_thread/thread.h"
#include "pw_unit_test/framework.h"
namespace {
using namespace std::chrono_literals;
using pw::async2::Context;
using pw::async2::DispatcherForTest;
using pw::async2::Pending;
using pw::async2::Poll;
using pw::async2::Ready;
using pw::async2::Task;
using pw::async2::Waker;
class BlockingTask : public Task {
public:
explicit BlockingTask(Poll<> result_to_return)
: Task(PW_ASYNC_TASK_NAME("BlockingTask")),
result_to_return_(result_to_return) {}
Poll<> DoPend(Context& cx) override {
ready_to_deregister_.release();
wait_for_deregister_.acquire();
// Store a waker in case the task completes before it is deregistered.
PW_ASYNC_STORE_WAKER(cx, waker_, "BlockingTask");
return result_to_return_;
}
void WaitUntilRunning() { ready_to_deregister_.acquire(); }
void Unblock() { wait_for_deregister_.release(); }
private:
pw::sync::BinarySemaphore ready_to_deregister_;
pw::sync::BinarySemaphore wait_for_deregister_;
Poll<> result_to_return_ = Pending();
Waker waker_;
};
class SleepingTask : public Task {
public:
SleepingTask() : Task(PW_ASYNC_TASK_NAME("SleepingTask")) {}
Poll<> DoPend(Context& cx) override {
std::lock_guard lock(lock_);
if (should_complete_) {
return Ready();
}
PW_ASYNC_STORE_WAKER(cx, waker_, "SleepingTask is sleeping");
sleeping_.release();
return Pending();
}
void WaitUntilSleeping() { sleeping_.acquire(); }
void Wake() {
std::lock_guard lock(lock_);
should_complete_ = true;
waker_.Wake();
}
private:
pw::sync::BinarySemaphore sleeping_;
pw::sync::Mutex lock_;
bool should_complete_ = false;
Waker waker_;
};
TEST(Task, IsRegistered) {
BlockingTask task(Ready());
EXPECT_FALSE(task.IsRegistered());
DispatcherForTest dispatcher;
dispatcher.Post(task);
EXPECT_TRUE(task.IsRegistered());
}
TEST(Task, DeregisterWhileSleeping) {
DispatcherForTest dispatcher;
BlockingTask task(Ready());
dispatcher.Post(task);
task.Deregister();
EXPECT_FALSE(task.IsRegistered());
}
void DeregisterWhileRunning(Poll<> task_return) {
DispatcherForTest dispatcher;
BlockingTask task(task_return);
dispatcher.Post(task);
pw::thread::test::TestThreadContext context;
pw::Thread dispatcher_thread(context.options(),
[&dispatcher] { dispatcher.RunToCompletion(); });
task.WaitUntilRunning();
// Start a thread to unblock the task. The thread sleeps for a bit, so it
// MIGHT unblock the task after Deregister() is called, but it's not
// guaranteed.
pw::thread::test::TestThreadContext unblock_context;
pw::Thread unblock_thread(unblock_context.options(), [&task]() {
pw::this_thread::sleep_for(10ms);
task.Unblock();
});
task.Deregister();
EXPECT_FALSE(task.IsRegistered());
unblock_thread.join();
dispatcher_thread.join();
}
TEST(Task, DeregisterRunningTask_TaskReturnsPending) {
DeregisterWhileRunning(Pending());
}
TEST(Task, DeregisterRunningTask_TaskReturnsReady) {
DeregisterWhileRunning(Ready());
}
TEST(Task, Join_RunningTask) {
DispatcherForTest dispatcher;
BlockingTask task(Ready());
dispatcher.Post(task);
pw::thread::test::TestThreadContext context;
pw::Thread dispatcher_thread(context.options(),
[&dispatcher] { dispatcher.RunToCompletion(); });
task.WaitUntilRunning();
pw::thread::test::TestThreadContext unblock_context;
pw::Thread unblock_thread(unblock_context.options(), [&task]() {
pw::this_thread::sleep_for(10ms);
task.Unblock();
});
task.Join();
EXPECT_FALSE(task.IsRegistered());
unblock_thread.join();
dispatcher_thread.join();
}
TEST(Task, Join_SleepingTask) {
DispatcherForTest dispatcher;
dispatcher.AllowBlocking();
SleepingTask task;
dispatcher.Post(task);
pw::thread::test::TestThreadContext context;
pw::Thread dispatcher_thread(context.options(),
[&dispatcher] { dispatcher.RunToCompletion(); });
task.WaitUntilSleeping();
pw::thread::test::TestThreadContext wake_context;
pw::Thread wake_thread(wake_context.options(), [&task]() {
pw::this_thread::sleep_for(10ms);
task.Wake();
});
task.Join();
EXPECT_FALSE(task.IsRegistered());
wake_thread.join();
dispatcher_thread.join();
}
} // namespace