Skip to content

Getting Started

github-actions[bot] edited this page Jul 18, 2026 · 19 revisions

Getting Started

This guide will help you get Elio up and running in your project.

Installation

Prerequisites

# Ubuntu/Debian (22.04+) default build requirements
sudo apt install build-essential cmake ninja-build g++-12 libssl-dev
# Optional io_uring backend
sudo apt install liburing-dev

# Fedora default build requirements
sudo dnf install gcc-c++ cmake ninja-build openssl-devel
# Optional io_uring backend
sudo dnf install liburing-devel

# Arch Linux default build requirements
sudo pacman -S base-devel cmake ninja openssl
# Optional io_uring backend
sudo pacman -S liburing

Building from Source

git clone https://github.com/Coldwings/Elio.git
cd Elio
cmake -B build -G Ninja
cmake --build build

Running Tests

cd build
ctest --output-on-failure

# With AddressSanitizer (memory safety)
./tests/elio_tests_asan

# With ThreadSanitizer (thread safety)
./tests/elio_tests_tsan

CMake Integration

Elio is header-only, but consumers should still link one of Elio's CMake interface targets. Those targets propagate required dependencies such as fmt::fmt, Threads::Threads, optional backend libraries like liburing, and the protocol/RDMA dependencies for enabled feature targets. If you integrate Elio without CMake, provide the equivalent include paths, compile definitions, and link libraries manually.

Using FetchContent

include(FetchContent)

FetchContent_Declare(elio
    GIT_REPOSITORY https://github.com/Coldwings/Elio.git
    GIT_TAG main
)
FetchContent_MakeAvailable(elio)

target_link_libraries(your_target PRIVATE elio)

Using add_subdirectory

add_subdirectory(path/to/elio)
target_link_libraries(your_target PRIVATE elio)

Using an Installed Package

find_package(Elio REQUIRED CONFIG)
target_link_libraries(your_target PRIVATE Elio::elio)

Installed-package consumers must make Elio's exported dependencies discoverable to CMake as well. fmt and CMake's Threads::Threads target are always required by the installed package config; install a system fmt CMake package or pass its location with CMAKE_PREFIX_PATH/fmt_DIR. Feature targets add their own dependencies, such as liburing for io_uring-enabled builds, OpenSSL for TLS/HTTP targets, nghttp2 for HTTP/2, and the RDMA/CUDA libraries listed below.

Optional Features

Elio has several optional feature flags, each controlled by a CMake option. The primary feature flags are:

CMake option Dependency What it enables
ELIO_ENABLE_TLS OpenSSL TLS/SSL streams, HTTPS support
ELIO_ENABLE_HTTP OpenSSL via the current elio_tls target HTTP/1.1 client and server, WebSocket, SSE; plaintext and TLS endpoints at runtime
ELIO_ENABLE_HTTP2 nghttp2 plus enabled HTTP/TLS targets HTTP/2 multiplexed connections over TLS
ELIO_ENABLE_RDMA None beyond core Elio dependencies Header-only RDMA-Core abstraction
ELIO_ENABLE_RDMA_CM librdmacm plus enabled RDMA core target RDMA Connection Manager helpers
ELIO_ENABLE_RDMA_IBVERBS libibverbs plus enabled RDMA core target Reference libibverbs backend and endpoint wrapper
ELIO_ENABLE_RDMA_CUDA CUDA Toolkit plus enabled ibverbs target CUDA GPUDirect RDMA helpers

Link the target for the feature your code includes:

Feature FetchContent / add_subdirectory Installed package
Core elio Elio::elio
TLS elio_tls Elio::elio_tls
HTTP/1.1, WebSocket, SSE elio_http Elio::elio_http
HTTP/2 elio_http2 Elio::elio_http2
RDMA core elio_rdma Elio::elio_rdma
RDMA CM helper elio_rdma_cm Elio::elio_rdma_cm
RDMA ibverbs backend elio_rdma_ibverbs Elio::elio_rdma_ibverbs
RDMA CUDA helpers elio_rdma_cuda Elio::elio_rdma_cuda

Feature targets exist only when their options are enabled. For FetchContent or add_subdirectory, set the options before adding Elio:

set(ELIO_ENABLE_TLS ON CACHE BOOL "" FORCE)
set(ELIO_ENABLE_HTTP ON CACHE BOOL "" FORCE)
set(ELIO_ENABLE_HTTP2 ON CACHE BOOL "" FORCE)

# Then call FetchContent_MakeAvailable(elio) or add_subdirectory(path/to/elio).
target_link_libraries(your_target PRIVATE elio_http2)

An installed package exports only the feature targets that were enabled when that package was built. Elio::elio_http2 therefore requires an Elio install configured with TLS, HTTP, and HTTP/2 enabled.

The same rule applies to installed RDMA targets. Elio::elio_rdma is the header-only RDMA core abstraction. Elio::elio_rdma_cm restores librdmacm, Elio::elio_rdma_ibverbs restores libibverbs, and Elio::elio_rdma_cuda restores CUDAToolkit when find_package(Elio) loads the installed package. Link the most specific RDMA target whose headers your code includes.

elio_http currently depends on the TLS target at build/link time, so OpenSSL must be available even if your application only uses plaintext http://, ws://, or plain HTTP server traffic. This is a package target dependency, not a requirement that every HTTP/1.1 connection use TLS.

Enable them at configure time:

cmake -B build -DELIO_ENABLE_TLS=ON -DELIO_ENABLE_HTTP=ON -DELIO_ENABLE_HTTP2=ON

When building Elio from source with ELIO_ENABLE_HTTP2 set, Elio can fetch nghttp2 automatically via CMake's FetchContent. Installed-package consumers must make nghttp2 discoverable to CMake as described above. OpenSSL must be installed separately for the default TLS/HTTP/HTTP2 targets. liburing is optional; install it only when you want the io_uring backend, otherwise Elio builds with the epoll fallback.

Additional options include RDMA support (ELIO_ENABLE_RDMA, ELIO_ENABLE_RDMA_CM, ELIO_ENABLE_RDMA_IBVERBS, ELIO_ENABLE_RDMA_IBVERBS_TESTS, ELIO_ENABLE_RDMA_CUDA), build controls (ELIO_BUILD_TESTS, ELIO_BUILD_EXAMPLES, ELIO_BUILD_TCP_BENCHMARKS), developer warnings (ELIO_ENABLE_DEVELOPER_WARNINGS, ELIO_WARNINGS_AS_ERRORS), and debug metadata (ELIO_ENABLE_DEBUG_METADATA). See the top-level CMakeLists.txt for the authoritative option list.

Your First Program

Create a simple "Hello World" program:

#include <elio/elio.hpp>
#include <iostream>

using namespace elio;

coro::task<std::string> get_greeting() {
    co_return "Hello from Elio!";
}

coro::task<int> async_main(int argc, char* argv[]) {
    std::cout << "Program: " << argv[0] << std::endl;
    if (argc > 1) {
        std::cout << "First argument: " << argv[1] << std::endl;
    }
    
    std::string greeting = co_await get_greeting();
    std::cout << greeting << std::endl;
    co_return 0;
}

ELIO_ASYNC_MAIN(async_main)

The ELIO_ASYNC_MAIN macro handles scheduler setup, I/O context initialization, and cleanup automatically. It passes argc and argv to your async_main function.

Using I/O Context

The scheduler automatically creates and manages worker I/O contexts. Access the current worker's context via io::current_io_context():

coro::task<int> async_main(int argc, char* argv[]) {
    // Get the current scheduler worker's I/O context
    auto& ctx = io::current_io_context();
    
    // Use ctx for networking, timers, etc.
    co_await time::sleep_for(std::chrono::seconds(1));
    
    co_return 0;
}

ELIO_ASYNC_MAIN(async_main)

Outside a scheduler worker, io::current_io_context() falls back to the global io::default_io_context(). That fallback is a standalone context: code using it must serialize submissions, drive polling, and keep all request storage alive. Inside scheduler coroutines, use the current worker's context. Elio's standard I/O awaitables reject a standalone context or another worker's context because the scheduler cannot safely poll or resume those operations as worker-local I/O. Mutating io_context operations enforce the same exact-owner rule; notify() is the designated cross-thread wakeup operation. Directly constructed backend objects remain standalone and caller-serialized.

Alternative: Using elio::run()

For more control, use elio::run() directly:

int main(int argc, char* argv[]) {
    return elio::run(async_main, argc, argv);
}

Or configure via run_config:

int main(int argc, char* argv[]) {
    elio::run_config config;
    config.num_threads = 4;  // Use 4 worker threads
    
    return elio::run(config, async_main, argc, argv);
}

Macros Reference

Macro Supported signatures Description
ELIO_ASYNC_MAIN task<int>(int, char**), task<void>(int, char**), task<int>(), task<void>() Unified entry point macro; detects signature at compile time

Design Philosophy

A few deliberate choices shaped how Elio is built.

Header-only

Elio ships as a header-only library. Coroutine-heavy code is inherently template-heavy -- the compiler needs to see full definitions to instantiate coroutine frames, promise types, and awaitables. A separate compilation model would add link-time complexity for very little gain. Header-only also avoids Elio-specific ABI compatibility concerns, because there is no compiled Elio library. It does not remove normal consumer dependencies: prefer the CMake targets, or mirror their include paths, compile definitions, and link libraries manually.

C++20 coroutines

C++20 stackless coroutines give us suspension and resumption without allocating a full thread stack per concurrent operation. The compiler manages coroutine frame layout and lifetime, which means the optimizer can see through co_await boundaries. When you are not using coroutines, you pay nothing -- no background threads, no hidden allocations. The trade-off is that C++20 coroutine support requires a reasonably modern compiler (GCC 12+, Clang 15+), but that is a reasonable baseline in 2024.

Linux-only

Elio targets Linux exclusively. This is not an arbitrary limitation -- it enables deep integration with Linux-specific facilities like io_uring for high-throughput async I/O and signalfd for clean signal handling inside the event loop. Epoll is supported as a fallback for older kernels, but both backends assume Linux semantics. Targeting a single platform means the library can provide consistent behavior guarantees without #ifdef complexity or lowest-common-denominator abstractions.

Running the Examples

cd build

# Hello World
./examples/hello_world

# TCP Echo Server (run in one terminal)
./examples/tcp_echo_server 8080

# TCP Echo Client (run in another terminal)
./examples/tcp_echo_client localhost 8080

# HTTP Server
./examples/http_server 8080

# HTTP Client
./examples/http_client https://httpbin.org/get

Project Structure

Elio is a header-only library. Everything lives under include/elio/:

include/elio/
├── elio.hpp              # Main include
├── coro/                 # Coroutine primitives
│   ├── task.hpp          # task<T>
│   ├── promise_base.hpp  # Virtual stack and runtime-state anchor
│   ├── task_execution_context.hpp # Shared task runtime policy
│   ├── frame.hpp         # Stack introspection
│   ├── generator.hpp     # Async generator
│   ├── cancel_token.hpp  # Cooperative cancellation
│   ├── awaitable_base.hpp # Awaitable interface
│   ├── task_handle.hpp   # Task handle utilities
│   ├── traits.hpp        # Type traits for coroutines
│   ├── when_all.hpp      # Wait for all tasks
│   ├── when_any.hpp      # Wait for any task
│   └── with_timeout.hpp  # Timeout wrapper
├── runtime/              # Scheduler and threading
│   ├── scheduler.hpp     # Work-stealing scheduler
│   ├── worker_thread.hpp # Worker implementation
│   ├── chase_lev_deque.hpp # Lock-free deque
│   ├── mpsc_queue.hpp    # Cross-thread queue
│   ├── wait_strategy.hpp # Idle wait policies
│   ├── affinity.hpp      # Thread affinity
│   ├── async_main.hpp    # Entry point macros
│   ├── serve.hpp         # Server lifecycle
│   ├── autoscaler.hpp    # Dynamic thread scaling
│   ├── autoscaler_actions.hpp # Scale up/down actions
│   ├── autoscaler_config.hpp  # Autoscaler configuration
│   ├── autoscaler_triggers.hpp # Overload/idle triggers
│   ├── blocking_pool.hpp # Blocking thread pool
│   ├── spawn.hpp         # go() and spawn() free functions
│   └── spawn_blocking.hpp # Spawn blocking operations
├── io/                   # I/O backends
│   ├── io_context.hpp
│   ├── io_backend.hpp
│   ├── io_uring_backend.hpp
│   └── epoll_backend.hpp
├── net/                  # Networking
│   ├── tcp.hpp
│   └── uds.hpp
├── sync/                 # Synchronization
│   ├── primitives.hpp    # Convenience umbrella header
│   ├── mutex.hpp
│   ├── shared_mutex.hpp
│   ├── semaphore.hpp
│   ├── event.hpp
│   ├── channel.hpp
│   ├── condition_variable.hpp
│   ├── spinlock.hpp
│   ├── lockfree_ring.hpp
│   └── object_cache.hpp
├── time/                 # Timers
│   └── timer.hpp
├── signal/               # Signal handling
│   └── signalfd.hpp
├── http/                 # HTTP stack
│   ├── http_client.hpp
│   ├── http_server.hpp
│   ├── http2.hpp
│   ├── websocket.hpp
│   └── sse.hpp
├── tls/                  # TLS/SSL
│   ├── tls_context.hpp
│   └── tls_stream.hpp
├── rpc/                  # RPC framework
│   ├── rpc.hpp
│   ├── rpc_types.hpp
│   ├── rpc_buffer.hpp
│   ├── rpc_client.hpp
│   └── rpc_server.hpp
├── hash/                 # Hash functions
│   ├── crc32.hpp
│   ├── sha1.hpp
│   └── sha256.hpp
└── log/                  # Logging
    └── logger.hpp

The repository also contains examples/ with runnable programs, tests/ with Catch2 tests, and tools/ with debugging utilities (elio-pstack, GDB/LLDB extensions).

Next Steps

Clone this wiki locally