{"id":51083447,"url":"https://github.com/ejunjsh/cpp-coroutine-epoll","last_synced_at":"2026-06-23T20:30:52.710Z","repository":{"id":361772045,"uuid":"1255762549","full_name":"ejunjsh/cpp-coroutine-epoll","owner":"ejunjsh","description":"Small Linux/macOS networking example that wraps readiness events with C++20 coroutines.","archived":false,"fork":false,"pushed_at":"2026-06-19T06:22:15.000Z","size":64,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-19T07:26:26.562Z","etag":null,"topics":["coroutine","cpp","cpp20","epoll","kqueue"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ejunjsh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-01T06:38:35.000Z","updated_at":"2026-06-19T06:22:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ejunjsh/cpp-coroutine-epoll","commit_stats":null,"previous_names":["ejunjsh/cpp-coroutine-epoll"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ejunjsh/cpp-coroutine-epoll","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ejunjsh%2Fcpp-coroutine-epoll","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ejunjsh%2Fcpp-coroutine-epoll/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ejunjsh%2Fcpp-coroutine-epoll/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ejunjsh%2Fcpp-coroutine-epoll/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ejunjsh","download_url":"https://codeload.github.com/ejunjsh/cpp-coroutine-epoll/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ejunjsh%2Fcpp-coroutine-epoll/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34706579,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coroutine","cpp","cpp20","epoll","kqueue"],"created_at":"2026-06-23T20:30:51.942Z","updated_at":"2026-06-23T20:30:52.703Z","avatar_url":"https://github.com/ejunjsh.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# C++20 Coroutine Event Loop\n\n![CMake Build](https://github.com/ejunjsh/cpp-coroutine-epoll/actions/workflows/cmake-multi-platform.yml/badge.svg)\n\nA C++20 coroutine-based networking library that wraps readiness events with `co_await`. Linux uses `epoll` + `eventfd`; macOS uses `kqueue` + `pipe`. All I/O is non-blocking, and coroutines are suspended/resumed by the event loop without any callback nesting.\n\n## Library\n\n### `coro_epoll::Task\u003cT\u003e`\n\nThe coroutine return type. A `Task\u003cT\u003e` bridges the synchronous world (event loop) with `co_await`-based async code.\n\n- `Task\u003cT\u003e` for coroutines that produce a value; `Task\u003cvoid\u003e` for fire-and-forget or side-effect-only coroutines.\n- Move-only: each `Task` uniquely owns a coroutine handle. Destroying a `Task` destroys the coroutine.\n- `co_await task` suspends the caller and resumes when the inner coroutine completes, propagating the result (or exception).\n- `promise_type::initial_suspend()` returns `suspend_always` — newly created tasks are lazily started via `EventLoop::spawn()`.\n\n### `coro_epoll::EventLoop`\n\nSingle-threaded reactor. Owns one `epoll` (Linux) or `kqueue` (macOS) fd.\n\n| Method | Description |\n|--------|-------------|\n| `spawn(Task\u003cvoid\u003e\u0026\u0026)` | Start a coroutine on this loop. |\n| `post(std::function\u003cvoid()\u003e)` | Enqueue a callback from another thread. Thread-safe. |\n| `run()` | Blocking event loop. Returns when `stop()` is called. |\n| `stop()` | Signal the loop to exit. Thread-safe. |\n| `readable(int fd)` | Returns an awaiter that suspends until `fd` is readable (level-triggered). |\n| `writable(int fd)` | Returns an awaiter that suspends until `fd` is writable (level-triggered). |\n| `readable_et(int fd)` | Edge-triggered variant of `readable`. Fires only on state change. |\n| `writable_et(int fd)` | Edge-triggered variant of `writable`. Fires only on state change. |\n| `remove(int fd)` | Deregister `fd` and clean up associated coroutine handles. |\n\n`post()` enables cross-thread scheduling: a business thread can post a continuation back to the owning `EventLoop`, keeping socket I/O on the correct thread.\n\n### `coro_epoll::WorkerGroup`\n\nOwns N `EventLoop` instances, each running on its own thread. Provides round-robin access via `next()`.\n\n| Method | Description |\n|--------|-------------|\n| `next()` | Returns the next `EventLoop\u0026` in round-robin order. Thread-safe. |\n| `stop()` / `join()` | Gracefully shut down all worker loops. |\n\nCombined with `SO_REUSEPORT`, each worker can bind its own listening socket and the kernel distributes incoming connections.\n\n### `coro_epoll::ThreadPool`\n\nRuns CPU-heavy or blocking tasks off the network threads. When a task completes, the coroutine continuation is posted back to the originating `EventLoop`.\n\n```cpp\nstd::string result = co_await pool.submit(loop, [] {\n    return expensive_computation();\n});\n// Resumed on 'loop' — safe to read/write sockets here.\n```\n\n### `coro_epoll::TcpServer`\n\nNon-blocking TCP listening socket, bound to an `EventLoop`.\n\n| Method | Description |\n|--------|-------------|\n| `listen(port, backlog, reuse_port)` | Create, bind, and listen. `reuse_port` enables `SO_REUSEPORT`. |\n| `async_accept_fd()` | `co_await` a new client fd (level-triggered). |\n| `async_accept()` | `co_await` a new `TcpSocket` (level-triggered). |\n| `async_accept_et()` | Edge-triggered variant — drains accept queue on each event, returns `std::vector\u003cTcpSocket\u003e`. |\n\n### `coro_epoll::TcpSocket`\n\nNon-blocking connected TCP socket. Move-only; destructor closes the fd and deregisters from the `EventLoop`.\n\n| Method | Description |\n|--------|-------------|\n| `async_read(buffer, size)` | `co_await` until data arrives (level-triggered). Returns bytes read, or `0` on EOF. |\n| `async_write(buffer, size)` | `co_await` until all bytes are written. Partial writes are handled transparently. |\n| `async_read_et(buffer, size)` | Edge-triggered variant — drains socket buffer until `EAGAIN` on each event. |\n\n### `coro_epoll::UdpSocket`\n\nNon-blocking UDP socket.\n\n| Method | Description |\n|--------|-------------|\n| `bind(port, reuse_port)` | Create and bind. Supports `SO_REUSEPORT`. |\n| `async_recv_from(buffer, size)` | `co_await` a datagram (level-triggered). Returns `UdpReceiveResult{size, endpoint}`. |\n| `async_recv_from_et(buffer, size)` | Edge-triggered variant — drains socket buffer, returns `std::vector\u003cUdpReceiveResult\u003e`. |\n| `async_send_to(buffer, size, endpoint)` | `co_await` until the datagram is sent. |\n\n### `coro_epoll::UdpEndpoint`\n\nImmutable IPv4 address + port. Construct from `(string_view address, uint16_t port)`, or from a raw `sockaddr_in`.\n\n## Threading model\n\n```text\nworker thread N\n    EventLoop N\n        epoll_wait(eventfd + listen fd + client fds)\n            accept()  ← SO_REUSEPORT, kernel distributes connections\n                spawn client coroutine\n            resume read/write coroutines\n\nbusiness thread pool\n    run CPU-heavy / blocking tasks\n    post continuation back to the originating EventLoop via loop.post()\n```\n\nEach `EventLoop` owns its `epoll`/`kqueue` fd and runs on a dedicated thread. A client socket stays on whichever worker accepted it — all subsequent I/O for that socket is handled by the same worker, so no locking is needed for socket state.\n\n## Build\n\n```bash\ncmake -S . -B build\ncmake --build build\n```\n\n## Examples\n\n- [tcp_echo_server](examples/tcp-echo-server/README.md) — TCP echo server (level-triggered)\n- [tcp_echo_server_et](examples/tcp-echo-server-et/README.md) — TCP echo server (edge-triggered)\n- [udp_echo_server](examples/udp-echo-server/README.md) — UDP echo server (level-triggered)\n- [udp_echo_server_et](examples/udp-echo-server-et/README.md) — UDP echo server (edge-triggered)\n- [proxy_server](examples/proxy-server/README.md) — TCP proxy\n- [http_server](examples/http-server/README.md) — Minimal HTTP/1.1 server\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fejunjsh%2Fcpp-coroutine-epoll","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fejunjsh%2Fcpp-coroutine-epoll","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fejunjsh%2Fcpp-coroutine-epoll/lists"}