boostorg / boostorg/cobalt

Unfinished task is resumed after co_main returns

Open
#198 3 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
347
Forks
47
Avg merge
3d 8h
Merged PRs (30d)
1

Description

Hello,

I've been using Cobalt with Boost 1.86.0, and I've had an issue with my code that caused it to segfault due to use of destroyed resources.

I've reproduced my issue on a smaller sample code:

```c++
#include
#include
#include
#include
#include

namespace cobalt = boost::cobalt;
namespace asio = boost::asio;
using namespace std::chrono_literals;

int GLOBAL_FLAG = 0;

struct set_global_flag {
set_global_flag() {
GLOBAL_FLAG = 1;
}

~set_global_flag() {
GLOBAL_FLAG = 0;
}
};

cobalt::task long_loop_task() {
auto exec = co_await cobalt::this_coro::executor;

auto timer = cobalt::use_op.as_default_on(asio::steady_timer(exec));
while (!co_await cobalt::this_coro::cancelled) {
std::cout << "long_loop: flag is " << GLOBAL_FLAG << std::endl;
try {
timer.expires_from_now(1s);
co_await timer.async_wait();
}
catch (const boost::system::system_error& err) {
if (err.code() == asio::error::operation_aborted) {
std::cout << "long_loop: operation aborted" << std::endl;
}
}
}
}

cobalt::main co_main(int, char**) {
auto sgf = set_global_flag();
auto llt = long_loop_task();

auto exec = co_await cobalt::this_coro::executor;
auto timeout_timer = cobalt::use_op.as_default_on(asio::steady_timer(exec));
timeout_timer.expires_from_now(50ms);

co_await cobalt::race(
llt,
timeout_timer.async_wait()
);
co_return 0;
}
```

I build this code with g++13 & libstdc++ on Ubuntu 22.04.

The output of this program is the following:
```
long_loop: flag is 1
long_loop: flag is 0
long_loop: flag is 0
long_loop: flag is 0
long_loop: flag is 0
[...]
```

As we can see, the loop accesses the global flag after the `set_global_flag` object is destroyed (the flag is reset to 0), which means that the long_loop_task coroutine has been resumed after the co_main function returned. In this program, nothing much happens, but in my original code, the flag was a pointer passed by reference to the loop and the `set_global_flag` was a `unique_ptr`, which caused the loop to access through the reference an object that was destroyed.

The program never terminates, even if an interrupt (Ctrl+C) or termination (SIGTERM) signal is sent. I have to kill it with a SIGKILL or SIGQUIT.

I understand that according to `cobalt::race` specification, the loop task should not be cancelled, because it is passed by lvalue-reference. It is however surprising to me that it is resumed though, even after the task object was destroyed.

Am I missing something ?
Thank you in advance.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by running the supplied minimal C++ example with Boost 1.86.0 and tracing the lifecycle of cobalt::race, long_loop_task, and co_main. Determine why the lvalue task continues after co_main returns; done means the task is not resumed against destroyed state and the program terminates correctly on completion or interruption.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.