lewissbaker / cppcoro

A library of C++ coroutine abstractions for the coroutines TS
MIT License
3.39k stars 467 forks source link

Cancellation deadlock #221

Open apollo1321 opened 8 months ago

apollo1321 commented 8 months ago

The following code reproduces cancellation deadlock. I understand the essence of the problem, if anyone is interested, I would be happy to discuss it. Also, I have studied the facebook/folly code, there is no such problem, since in-place executors are not allowed for stackless coroutines. It seems to me that this problem might be quite general. I did not find any mention of it in the std::execution proposal and in the P2175R0 paper, and do not fully understand how it deals with it.

#include "doctest/doctest.h"

#include <cppcoro/cancellation_registration.hpp>
#include <cppcoro/cancellation_source.hpp>
#include <cppcoro/single_consumer_event.hpp>
#include <cppcoro/static_thread_pool.hpp>
#include <cppcoro/sync_wait.hpp>
#include <cppcoro/task.hpp>
#include <cppcoro/when_all.hpp>

#include <chrono>
#include <thread>

TEST_SUITE_BEGIN("cancellation with events tests");

TEST_CASE("check deadlock freedom for events in cancellation callbacks")
{
    cppcoro::static_thread_pool threadPool(3);

    for (int i = 0; i < 20000; ++i)
    {
        cppcoro::single_consumer_event event1;
        cppcoro::single_consumer_event event2;

        cppcoro::cancellation_source source1;
        cppcoro::cancellation_source source2;

        auto coro1 = [&]() -> cppcoro::task<void> {
            auto guard =
                cppcoro::cancellation_registration(source1.token(), [&]() { event2.set(); });
            co_await event1;
        };

        auto coro2 = [&]() -> cppcoro::task<void> {
            auto guard =
                cppcoro::cancellation_registration(source2.token(), [&]() { event1.set(); });
            co_await event2;
        };

        auto cancelCoro1 = [&]() -> cppcoro::task<void> {
            co_await threadPool.schedule();
            source1.request_cancellation();
        };

        auto cancelCoro2 = [&]() -> cppcoro::task<void> {
            co_await threadPool.schedule();
            source2.request_cancellation();
        };

        auto fallbackCoro = [&]() -> cppcoro::task<void> {
            co_await threadPool.schedule();
            std::this_thread::sleep_for(std::chrono::microseconds(10));
            event1.set();
            event2.set();
        };

        cppcoro::sync_wait(
            cppcoro::when_all(coro1(), coro2(), cancelCoro1(), cancelCoro2(), fallbackCoro()));
    }
}

TEST_SUITE_END();