diff --git a/rclcpp_async/include/rclcpp_async/co_context.hpp b/rclcpp_async/include/rclcpp_async/co_context.hpp index 6ca2d46..d2d72e9 100644 --- a/rclcpp_async/include/rclcpp_async/co_context.hpp +++ b/rclcpp_async/include/rclcpp_async/co_context.hpp @@ -174,10 +174,14 @@ class CoContext : public Executor return task; } + // The callable is moved into the task's frame: a lambda's captures must + // outlive the caller's full-expression, as the task resumes after it. template [[nodiscard]] auto create_task(CallbackT && callback) { - return create_task(callback()); + return create_task([](std::decay_t cb) -> decltype(cb()) { + co_return co_await cb(); + }(std::forward(callback))); } // --- Awaiter factory methods --- diff --git a/rclcpp_async/test/test_sleep.cpp b/rclcpp_async/test/test_sleep.cpp index 91b1c68..f1b66b4 100644 --- a/rclcpp_async/test/test_sleep.cpp +++ b/rclcpp_async/test/test_sleep.cpp @@ -196,3 +196,22 @@ TEST_F(SleepTest, AwaitCreateTaskAlreadyDone) ASSERT_TRUE(task.handle.done()); EXPECT_EQ(result, 99); } + +TEST_F(SleepTest, CreateTaskKeepsTheLambdaAlive) +{ + auto captured = std::make_shared(42); + auto task = ctx_->create_task([this, captured]() -> Task { + co_await ctx_->sleep(10ms); + co_return *captured; + }); + // The lambda is a temporary of the statement above, yet still in use. + ASSERT_EQ(captured.use_count(), 2); + + auto deadline = std::chrono::steady_clock::now() + 5s; + while (!task.handle.done() && std::chrono::steady_clock::now() < deadline) { + executor_.spin_some(); + std::this_thread::sleep_for(1ms); + } + ASSERT_TRUE(task.handle.done()); + EXPECT_EQ(task.result(), 42); +}