Skip to content

Coroutine frames leaked during shutdown across all backends #159

@sgerbino

Description

@sgerbino

Summary

All four scheduler backends (IOCP, epoll, kqueue, select) leak coroutine frames during shutdown instead of properly destroying them. This is a workaround for a circular dependency in capy's io_awaitable_promise_base where destroying a child coroutine triggers destruction of the parent continuation, causing re-entrant destruction and stack overflow.

Current behavior

IOCP (win_scheduler::shutdown)

  • Drains completed ops but abandons coroutine handles (h_ = {} instead of h_.destroy())
  • Forces outstanding_work_ = 0 at the end

epoll / kqueue / select (shutdown)

  • Calls scheduler_op::destroy() on queued ops, but post_handler::destroy() only does delete this — the stored coroutine_handle<> h_ is silently dropped without calling h_.destroy()
  • Forces outstanding_work_ = 0 at the end

Timer service (timer_service::shutdown)

  • Abandons waiter handles (w->h_ = {}) instead of destroying them

Root cause

The circular dependency lives in capy's io_awaitable_promise_base:

~io_awaitable_promise_base()
{
    // Abnormal teardown: destroy orphaned continuation
    if(cont_ != std::noop_coroutine())
        cont_.destroy();
}

During normal execution, final_suspend() calls continuation() which atomically swaps cont_ to noop_coroutine() before the frame is destroyed — no cycle. But during shutdown, final_suspend is never reached. The destructor tries to destroy the parent continuation, which owns the child, causing recursive destruction.

Expected behavior

All coroutine frames should be properly destroyed during shutdown without leaking memory. Boost.Asio accomplishes this during its shutdown/cleanup — we should investigate their approach (possibly deferred destruction or breaking the ownership cycle before destroying).

Affected files

  • include/boost/corosio/native/detail/iocp/win_scheduler.hppshutdown()
  • include/boost/corosio/native/detail/epoll/epoll_scheduler.hppshutdown(), post_handler::destroy()
  • include/boost/corosio/native/detail/kqueue/kqueue_scheduler.hppshutdown(), post_handler::destroy()
  • include/boost/corosio/native/detail/select/select_scheduler.hppshutdown(), post_handler::destroy()
  • include/boost/corosio/detail/timer_service.hppshutdown()
  • Upstream: capy/ex/io_awaitable_promise_base.hpp — destructor circular dependency

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions