Home » Correction: Bug in the priority scheduler for coroutines in the C++ blog

Correction: Bug in the priority scheduler for coroutines in the C++ blog

by admin
Correction: Bug in the priority scheduler for coroutines in the C++ blog

In my last two blog posts I introduced a priority scheduler for coroutines. The code for this had an error.

Advertisement

Rainer Grimm has been working as a software architect, team and training manager for many years. He enjoys writing articles on the programming languages ​​C++, Python and Haskell, but also enjoys speaking frequently at specialist conferences. On his blog Modern C++ he deals intensively with his passion C++.

This is what the faulty scheduler looks like:

// priority_queueSchedulerPriority.cpp

#include
#include
#include
#include
#include
#include

struct Task {

struct promise_type {
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }

Task get_return_object() {
return std::coroutine_handle::from_promise(*this);
}
void return_void() {}
void unhandled_exception() {}
};

Task(std::coroutine_handle handle): handle{handle}{}

auto get_handle() { return handle; }

std::coroutine_handle handle;
};

using job = std::pair>;

template
requires std::invocable && // (2)
std::predicate
class Scheduler {

std::priority_queueComperator> _prioTasks;

public:
void emplace(int prio, std::coroutine_handle task) {
_prioTasks.push(std::make_pair(prio, task));
}

void schedule() {
Updater upd = {}; // (3)
while(!_prioTasks.empty()) {
auto [prio, task] = _prioTasks.top();
_prioTasks.pop();
task.resume();

if(!task.done()) {
_prioTasks.push(std::make_pair(upd(prio), task)); // (4)
}
else {
task.destroy();
}
}
}

};

Task createTask(const std::string& name) {
std::cout scheduler2; // (7)

scheduler2.emplace(0, createTask(“TaskA”).get_handle());
scheduler2.emplace(1, createTask(” TaskB”).get_handle());
scheduler2.emplace(2, createTask(” TaskC”).get_handle());

scheduler2.schedule();

std::cout This was the output of the program that I received:

Christof Meerwald received a different edition with the GCC. Thank you for this hint. Here is the GCC output with optimization enabled.

The Windows output was also incorrect:

Here are the crucial lines with the error:

Task createTask(const std::string& name) { // (1)
std::cout scheduler2;

scheduler2.emplace(0, createTask(“TaskA”).get_handle()); // (5)
scheduler2.emplace(1, createTask(” TaskB”).get_handle()); // (6)
scheduler2.emplace(2, createTask(” TaskC”).get_handle());// (7)

See also  C++ programming language: The automatically generated equality operator

scheduler2.schedule();

std::cout The createTask coroutine takes its string as a const lvalue reference (1), but its arguments “TaskA” – “TaskC” are rvalues ​​(2 – 7). It is undefined behavior to use a reference to a temporary variable. The other schedulers priority_SchedulerSimplified and priority_queueSchedulerComparator in the articles “C++ programming language: A priority scheduler for coroutines” and “C++ programming language: A sophisticated priority scheduler for coroutines” have the same problem.

Fixing the problem is easy. Either the createTask coroutine takes its argument by Value (Task createTask(std::string name)) or its arguments become lvalues. Here is the second approach in (1) – (3):

// priority_queueSchedulerPriority.cpp

#include
#include
#include
#include
#include
#include

struct Task {

struct promise_type {
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }

Task get_return_object() {
return std::coroutine_handle::from_promise(*this);
}
void return_void() {}
void unhandled_exception() {}
};

Task(std::coroutine_handle handle): handle{handle}{}

auto get_handle() { return handle; }

std::coroutine_handle handle;
};

using job = std::pair>;

template
requires std::invocable &&
std::predicate
class Scheduler {

std::priority_queueComperator> _prioTasks;

public:
void emplace(int prio, std::coroutine_handle task) {
_prioTasks.push(std::make_pair(prio, task));
}

void schedule() {
Updater upd = {};
while(!_prioTasks.empty()) {
auto [prio, task] = _prioTasks.top();
_prioTasks.pop();
task.resume();

if(!task.done()) {
_prioTasks.push(std::make_pair(upd(prio), task));
}
else {
task.destroy();
}
}
}

};

Task createTask(const std::string& name) {
std::cout scheduler2;

scheduler2.emplace(0, createTask(taskA).get_handle());
scheduler2.emplace(1, createTask(taskB).get_handle());
scheduler2.emplace(2, createTask(taskC).get_handle());

scheduler2.schedule();

std::cout What’s next?

Coroutines provide an intuitive way to write asynchronous code. My next post will be a guest post by Ljubic Damir presenting a single-producer – single-consumer workflow based on coroutines. (rme)

To home page

You may also like

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More

Privacy & Cookies Policy