#include <atomic>
#include <coroutine>
#include <cstdio>
#include <cstdlib>
#include <new>
#include <thread>

static std::atomic<long> allocated{0};
static std::atomic<long> freed{0};

// a one-slot mailbox to the resumer thread
static std::atomic<void *> slot{nullptr};
static std::atomic<bool> done{false};

struct Task {
  struct promise_type {
    static void *operator new(std::size_t n) {
      allocated.fetch_add(1);
      return ::operator new(n);
    }
    static void operator delete(void *p) {
      freed.fetch_add(1);
      ::operator delete(p);
    }
    Task get_return_object() { return {}; }
    // hands the handle to the other thread, which resumes it
    auto initial_suspend() noexcept {
      struct Handoff {
        bool await_ready() noexcept { return false; }
        void await_suspend(std::coroutine_handle<> h) noexcept {
          void *expected = nullptr;
          while (!slot.compare_exchange_weak(expected, h.address())) {
            expected = nullptr;
          }
        }
        void await_resume() noexcept {}
      };
      return Handoff{};
    }
    std::suspend_never final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() { std::abort(); }
  };
};

Task coro() { co_return; }

int main() {
  std::thread resumer([] {
    while (!done.load()) {
      if (void *a = slot.exchange(nullptr)) {
        std::coroutine_handle<>::from_address(a).resume();
      }
    }
  });

  constexpr long N = 10'000'000;
  for (long i = 0; i < N; i++) {
    coro();
  }
  // let the resumer finish what's in flight
  while (slot.load() != nullptr) {
  }
  std::this_thread::sleep_for(std::chrono::milliseconds(100));
  done = true;
  resumer.join();

  std::printf("allocated=%ld freed=%ld leaked=%ld\n", allocated.load(),
              freed.load(), allocated.load() - freed.load());
  return allocated.load() == freed.load() ? 0 : 1;
}

