Skip to content

Commit 44d6934

Browse files
tests: verify TaskHandle cancelation from child task
This happens e.g when using Unichannel. Make sure that outer task handle becomes inactive in this scenario.
1 parent 0552bcb commit 44d6934

2 files changed

Lines changed: 75 additions & 0 deletions

File tree

test/dispatcher.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ struct ManualDispatcher
4545
}
4646
};
4747

48+
Executor GetExecutor() { return Executor{this}; }
49+
4850
bool ProcessOneTask()
4951
{
5052
if (queue.empty())

test/test_taskhandle.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "counter.hpp"
55
#include "crhandle/detachedhandle.hpp"
66
#include "crhandle/taskhandle.hpp"
7+
#include "dispatcher.hpp"
78

89
#include <deque>
910
#include <optional>
@@ -79,6 +80,15 @@ struct TaskHandleFixture : public ::testing::Test
7980
void await_suspend(stdcr::coroutine_handle<> h) { state.handle = h; }
8081
void await_resume() {}
8182
};
83+
84+
template <typename S>
85+
struct Canceler
86+
{
87+
S & state;
88+
bool await_ready() { return false; }
89+
void await_suspend(stdcr::coroutine_handle<> h) { state.handle = h; }
90+
void await_resume() { throw cr::CanceledException(); }
91+
};
8292
};
8393

8494
TEST_F(TaskHandleFixture, task_runs_if_handle_is_alive)
@@ -629,4 +639,67 @@ TEST_F(TaskHandleFixture, eager_task_resumes_its_continuation)
629639
EXPECT_EQ(42, value);
630640
}
631641

642+
TEST_F(TaskHandleFixture, nested_lazy_tasks_can_be_canceled_top_down)
643+
{
644+
using TaskType = cr::TaskHandle<void, ::ManualDispatcher::Executor>;
645+
646+
::ManualDispatcher dispatcher;
647+
648+
struct State
649+
{
650+
bool beforeSuspend = false;
651+
bool afterSuspend = false;
652+
stdcr::coroutine_handle<> handle = nullptr;
653+
} state;
654+
655+
static auto InnerTask = [](State & state) -> TaskType {
656+
co_await Canceler<State>{state};
657+
};
658+
659+
static auto OuterTask = [](State & state) -> TaskType {
660+
state.beforeSuspend = true;
661+
co_await InnerTask(state);
662+
state.afterSuspend = true;
663+
};
664+
665+
auto task = OuterTask(state);
666+
task.Run(dispatcher.GetExecutor());
667+
dispatcher.ProcessAll();
668+
EXPECT_TRUE(state.handle);
669+
EXPECT_TRUE(state.beforeSuspend);
670+
EXPECT_FALSE(state.afterSuspend);
671+
EXPECT_TRUE(task);
672+
673+
state.handle.resume();
674+
EXPECT_TRUE(state.handle.done());
675+
676+
EXPECT_TRUE(dispatcher.ProcessOneTask());
677+
EXPECT_FALSE(dispatcher.ProcessOneTask());
678+
EXPECT_FALSE(state.afterSuspend);
679+
EXPECT_FALSE(task);
680+
}
681+
682+
TEST_F(TaskHandleFixture, nested_eager_tasks_can_be_canceled_top_down)
683+
{
684+
struct ImmediateCanceler
685+
{
686+
bool await_ready() { return true; }
687+
void await_suspend(stdcr::coroutine_handle<>) {}
688+
void await_resume() { throw cr::CanceledException(); }
689+
};
690+
691+
static auto InnerTask = []() -> cr::TaskHandle<void> {
692+
co_await ImmediateCanceler{};
693+
};
694+
695+
static auto OuterTask = []() -> cr::TaskHandle<void> {
696+
co_await InnerTask();
697+
};
698+
699+
auto task = OuterTask();
700+
EXPECT_TRUE(task);
701+
task.Run();
702+
EXPECT_FALSE(task);
703+
}
704+
632705
} // namespace

0 commit comments

Comments
 (0)