簡體   English   中英

c++協程連續循環導致堆棧溢出

[英]c++ coroutine continuous loop causes stack overflow

我在循環中的協程內的 Visual Studio 中遇到堆棧溢出異常,發現該循環有一個錯誤阻止它終止,但我想知道為什么堆棧溢出? 協程甚至可能沒有使用堆棧,而是使用堆,即使使用了堆棧,也根本沒有任何遞歸調用

經過一些實驗,我可以重現崩潰:

  • 微軟 19.28
  • wsl 和 mingw64 上的 g++-10
  • windows 上的 clang-cl 10 和 linux 上的 clang++-10

此代碼導致堆棧溢出:

#include <stdexcept>
#include <utility>
#include <cstdio>

#ifdef __clang__
#ifdef _WIN32
#pragma message "using clang coroutine header"
#include "clang-cl-coro.h"
#else
#pragma message "using coroutine experimental header"
#include <experimental/coroutine>
#endif

namespace std
{
    template<class P = void>
    using coroutine_handle = experimental::coroutine_handle<P>;

    using suspend_never = experimental::suspend_never;

    using suspend_always = experimental::suspend_always;

}

#else
#pragma message "using coroutine header"
#include <coroutine>
#endif

class vtask
{
    inline static size_t task_count = 0;

public:

    struct promise_type
    {
        inline static size_t promise_count = 0;

        std::coroutine_handle<> waiter;
        std::exception_ptr ex_ptr = nullptr;

        struct resume_waiter
        {
            inline static size_t awaiter_count = 0;

            std::coroutine_handle<> waiter;

            resume_waiter(std::coroutine_handle<> waiter) noexcept : waiter{ waiter }
            {
                ++awaiter_count;
                printf("[%zu] resume_waiter(std::coroutine_handle<> waiter)\n", awaiter_count);
            }

            ~resume_waiter()
            {
                --awaiter_count;
                printf("[%zu] ~resume_waiter()\n", awaiter_count);
            }

            bool await_ready() const noexcept { return false; }

            auto await_suspend(std::coroutine_handle<>) noexcept
            {
                return waiter;
            }

            void await_resume() const noexcept {}
        };

        promise_type()
        {
            ++promise_count;
            printf("[%zu] vtask::promise_type()\n", promise_count);

        }

        ~promise_type()
        {
            --promise_count;
            printf("[%zu] ~vtask::promise_type()\n", promise_count);
        }

        vtask get_return_object() { return { *this }; }

        constexpr std::suspend_always initial_suspend() noexcept { return {}; }

        resume_waiter final_suspend() const noexcept { return { waiter }; }

        void unhandled_exception() noexcept
        {
            ex_ptr = std::current_exception();
        }

        void return_void() const noexcept {}
    };

    vtask(promise_type& p) : coro{ std::coroutine_handle<promise_type>::from_promise(p) }
    {
        ++task_count;
        printf("[%zu] vtask(promise_type& p)\n", task_count);
    }

    vtask(vtask&& other) noexcept : coro{ std::exchange(other.coro, nullptr) }
    {
        ++task_count;
        printf("[%zu] vtask(vtask&& other)\n", task_count);
    }

    ~vtask()
    {
        if (coro)
            coro.destroy();
        --task_count;
        printf("[%zu] ~vtask()\n", task_count);
    }

    bool await_ready() const noexcept
    {
        return false;
    }

    void await_suspend(std::coroutine_handle<> waiter)
    {
        coro.promise().waiter = waiter;
        coro.resume();
    }

    void await_resume() noexcept {}

private:
    std::coroutine_handle<promise_type> coro;
};

struct detached_task
{
    struct promise_type
    {
        inline static size_t promise_count = 0;

        promise_type()
        {
            ++promise_count;
            printf("[%zu] detached_task::promise_type()\n", promise_count);
        }

        ~promise_type()
        {
            --promise_count;
            printf("[%zu] ~detached_task::promise_type()\n", promise_count);
        }

        detached_task get_return_object() { return {}; }

        std::suspend_never initial_suspend() noexcept { return {}; }

        std::suspend_never final_suspend() noexcept { return {}; }

        void unhandled_exception() noexcept
        {
            std::terminate();
        }

        constexpr void return_void() const noexcept {}
    };

    inline static size_t task_count = 0;

    detached_task()
    {
        ++task_count;
        printf("[%zu] detached_task()\n", task_count);
    }

    ~detached_task()
    {
        --task_count;
        printf("[%zu] ~detached_task()\n", task_count);
    }
};

vtask do_stackoverflow() { co_return; }

detached_task stackoverflow()
{
    for (;;)
        co_await do_stackoverflow();
}

int main()
{
    stackoverflow();
}

使用的命令行:

cl /std:c++latest coro-stackoverflow.cpp /EHsc for msvc

g++ -std=c++20 coro-stackoverflow.cpp -fcoroutines for mingw64

clang-cl /std:c++latest coro-stackoverflow.cpp /EHsc

g++-10 -std=c++20 coro-stackoverflow.cpp -fcoroutines -o overflow.bug on wsl

clang++-10 -std=c++20 -stdlib=libc++ coro-stackoverflow.cpp -o overflow-clang.bug on wsl

這是 windows 上的 clang coro header:

#pragma once

namespace std { namespace experimental { inline namespace coroutines_v1 {

template <typename R, typename...> struct coroutine_traits {
  using promise_type = typename R::promise_type;
};

template <typename Promise = void> struct coroutine_handle;

template <> struct coroutine_handle<void> {
  static coroutine_handle from_address(void *addr) noexcept {
    coroutine_handle me;
    me.ptr = addr;
    return me;
  }
  void operator()() { resume(); }
  void *address() const { return ptr; }
  void resume() const { __builtin_coro_resume(ptr); }
  void destroy() const { __builtin_coro_destroy(ptr); }
  bool done() const { return __builtin_coro_done(ptr); }
  coroutine_handle &operator=(decltype(nullptr)) {
    ptr = nullptr;
    return *this;
  }
  coroutine_handle(decltype(nullptr)) : ptr(nullptr) {}
  coroutine_handle() : ptr(nullptr) {}
//  void reset() { ptr = nullptr; } // add to P0057?
  explicit operator bool() const { return ptr; }

protected:
  void *ptr;
};

template <typename Promise> struct coroutine_handle : coroutine_handle<> {
  using coroutine_handle<>::operator=;

  static coroutine_handle from_address(void *addr) noexcept {
    coroutine_handle me;
    me.ptr = addr;
    return me;
  }

  Promise &promise() const {
    return *reinterpret_cast<Promise *>(
        __builtin_coro_promise(ptr, alignof(Promise), false));
  }
  static coroutine_handle from_promise(Promise &promise) {
    coroutine_handle p;
    p.ptr = __builtin_coro_promise(&promise, alignof(Promise), true);
    return p;
  }
};

  template <typename _PromiseT>
  bool operator==(coroutine_handle<_PromiseT> const& _Left,
    coroutine_handle<_PromiseT> const& _Right) noexcept
  {
    return _Left.address() == _Right.address();
  }

  template <typename _PromiseT>
  bool operator!=(coroutine_handle<_PromiseT> const& _Left,
    coroutine_handle<_PromiseT> const& _Right) noexcept
  {
    return !(_Left == _Right);
  }

  template <typename _PromiseT>
  bool operator==(coroutine_handle<_PromiseT> const& _Left,
     std::nullptr_t) noexcept
  {
      return _Left.address() == nullptr;
  }

  template <typename _PromiseT>
  bool operator==(std::nullptr_t, coroutine_handle<_PromiseT> const& _Right) noexcept
  {
      return _Right.address() == nullptr;
  }

  template <typename _PromiseT>
  bool operator!=(coroutine_handle<_PromiseT> const& _Left,
     std::nullptr_t) noexcept
  {


    return !(_Left == nullptr);
  }

  template <typename _PromiseT>
  bool operator!=(std::nullptr_t, coroutine_handle<_PromiseT> const& _Right) noexcept
  {
      return _Right.address() != nullptr;
  }

struct suspend_always {
  bool await_ready() { return false; }
  void await_suspend(coroutine_handle<>) {}
  void await_resume() {}
};
struct suspend_never {
  bool await_ready() { return true; }
  void await_suspend(coroutine_handle<>) {}
  void await_resume() {}
};

}}}

在 windows 上,msvc 和 clang-cl 構建會提前發生崩潰,但 mingw64 和 wsl 構建需要更多時間

我使用的 gcc 10.1 似乎有一個已知的錯誤,其中任務被構造兩次,但一旦泄漏任務每次迭代就被銷毀,這似乎導致溢出

但是 clang 和 msvc 沒有這個錯誤,它們也會崩潰!

編輯:嘗試了 gcc 10.3 mingw64 並且沒有提到的 gcc 錯誤,但它也會導致堆棧溢出? 錯誤的編譯器甚至更快! 也許這種行為是預期的?

我不知道上面的代碼有什么問題

問題出在這部分代碼中:

void await_suspend(std::coroutine_handle<> waiter)
{
    coro.promise().waiter = waiter;
    coro.resume(); 
}

這種不對稱傳輸導致堆棧溢出並將代碼更改為:

auto await_suspend(std::coroutine_handle<> waiter)
{
    coro.promise().waiter = waiter;
    return coro; 
}

消除了 clang 和 msvc 上的問題,但 gcc 10.3 仍然崩潰,我認為它還不支持對稱傳輸

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM