Skip to content

BusyStudent/Ilias

Repository files navigation

Ilias

A async coroutine IO library for C++, based on C++20 stackless coroutines

Introduction

A lightweight async coroutine library aimed at minimal dependencies, implemented using C++20 stackless coroutines

  • Core has no external dependencies
  • Built-in facilities for cancellation operations
  • Structured concurrency
  • Network support (TCP, UDP, UnixSocket, Async-GetAddrinfo)
  • File I/O and pipe support
  • Cross-platform (Windows, Linux)
  • Simple single-threaded scheduler, easy to integrate with other frameworks like Qt

CI Status

CI Name Status
Windows CI for windows by xmake
Linux CI for linux by xmake
Coverage codecov

Quick Start

Adding to Your Project

For xmake projects

add_repositories("btk-repo https://github.com/Btk-Project/xmake-repo.git")
add_requires("ilias")

Or use git submodule

git submodule add https://github.com/BusyStudent/Ilias.git

For a simpler approach

Just copy all files from the include directory into your project

Basic Environment

#include <ilias/platform.hpp>
#include <ilias/task.hpp>

auto main() -> int {
    ilias::PlatformContext ctxt; // First, build a context for submitting tasks - this is thread_local, one per thread
    ctxt.install(); 
    // Available IO contexts include IocpContext, EpollContext, UringContext, QIoContext
    // Simple executors without IO include MiniExecutor
    // PlatformContext is a typedef that selects based on the current platform at compile time
    auto task = []() -> ilias::Task<int> { // This is a coroutine, return type must be Task<T>
        co_return 1;
    };
    auto result = task().wait(); // Create task and block waiting for completion
    // Task<T> represents return value of type T, so T can be used below
    assert(result == 1);
    return 0;
}

For convenience, there's an ilias_main macro equivalent to the above:

#include <ilias/platform.hpp>
#include <ilias/task.hpp>

void ilias_main() {
    co_return;
}
// or
int ilias_main() {
    co_return 0;
}
// () supports both () and (int argc, char** argv) formats
// Return value supports void and int

Socket

Simple message sending:

#include <ilias/platform.hpp>
#include <ilias/task.hpp>
#include <ilias/net.hpp>

using ilias::TcpClient;
using ilias::IPEndpoint;

void ilias_main() { // Taking a shortcut :)
    auto endpoint = IPEndpoint::fromString("127.0.0.1:8080").value();
    auto client = (co_await TcpClient::make(endpoint.family)).value();
    if (auto res = co_await client.connect(endpoint); !res) {
        co_return;
    }
    // ilias::makeBuffer converts anything that can be converted to std::span<T> into std::span<const std::byte> or std::span<std::byte>
    // read and write parameters are std::span<const std::byte> and std::span<std::byte> respectively
    // read and write return an IoTask<size_t>
    // IoTask<T, E = Error> is an alias for Task<Result<T, E>>, representing possible errors - see error handling section
    std::string_view sv = "HELLO WORLD";
    if (auto res = co_await client.write(ilias::makeBuffer(sv)); !res) {
        co_return;
    }

    // Alternative way to construct TcpClient
    auto ctxt = co_await ilias::currentIoContext();
    TcpClient client2(ctxt, AF_INET);
}

Waiting for connections:

#include <ilias/sync/scope.hpp>
#include <ilias/platform.hpp>
#include <ilias/task.hpp>
#include <ilias/net.hpp>

using ilias::TcpListener;
using ilias::IPEndpoint;
using ilias::TaskScope;

void ilias_main() { // Taking shortcuts, not handling errors for demo, using value() directly :)
    auto endpoint = IPEndpoint::fromString("127.0.0.1:8080").value();
    auto scope = co_await TaskScope::make(); // TaskScope ensures all child coroutines complete when exiting
    auto listener = (co_await TcpListener::make(endpoint.family)).value();
    while (true) {
        auto [client, _] = (co_await listener.accept()).value();
        auto handle = scope.spawn(handleClient, std::move(client)); // Create child task in scope. Could use ilias::spawn directly but less safe
        // handle can check completion or wait for completion. Losing handle is like detach
    }
}

Qt Integration

#include <ilias/platform/qt.hpp>
#include <ilias/http.hpp>
#include <QApplication>

auto main(int argc, char **argv) -> int {
    QApplication app(argc, argv);
    ilias::QIoContext ctxt; // Qt-integrated IoContext
    // Code same as above - ready to use
}

Synchronization

Supports multiple synchronization methods: Channel, Mutex, whenAny, whenAll

  • whenAny:
auto fn() -> Task<void> {
    // Wait for either task to complete, other task gets cancellation notice and result discarded
    auto [a, b] = co_await whenAny(taskA(), taskB());
    if (a) { // taskA() completed first

    }
    if (b) { // taskB() completed first

    }
}
  • whenAll:
auto fn() -> Task<void> {
    // Returns only when both complete
    auto [a, b] = co_await whenAll(taskA(), taskB());
    // use a & b
}
  • Channel:
auto fn() -> Task<void> {
    // Create channel
    auto [sender, receiver] = mpmc::channel<int>(3); // 3 is capacity, blocks if send would exceed capacity. Default size_t::max() means unlimited
    co_await sender.send(1);
    auto val = co_await receiver.recv();
}

Dependencies

  • liburing (optional, used by UringContext)

Backends

Backend Progress Description
epoll Completed
IOCP Completed
Qt Completed Qt Integration
io_uring Completed

About

C++ 20 coroutine library for networking, with minimal dependency, support tcp, udp, file, console, pipe

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages