2013-10-18

Go-style coroutines in C++ - this time with (non)blocking IO

Go-style coroutines in C++ - this time with (non) blocking I/O

I've managed to write epoll-based IO scheduler, witch works on top of the coroutine scheduler described in my previous post.

The IO stuff comes in form of a small library. It provides io_scheduler and set of tcp socket wrappers (tcp_socket, tcp_acceptor). The principle is pretty simple: The io_scheduler creates coroutine with epoll loop, which receives commands via channel.

The socket classes try to perform each operation in non-blocking manner. When this is impossible, request is sent to io_scheduler, socket added to epoll, and then notification is received via another channel. At this moment coroutine may yields, and another one may be executed.

These gory details are obviously hidden from the user, for whom the code looks as if the operations were blocking.

Show me the code!

Here ya go. Just the main part, imagine the rest is your standard library. All the HTTP stuff is implemented using POCO

Everything is here: https://github.com/maciekgajewski/coroutines/tree/0.2, help yourself.

Smal HTTP server - C++

#include "coroutines/globals.hpp"
#include "coroutines/scheduler.hpp"

#include "coroutines_io/globals.hpp"
#include "coroutines_io/io_scheduler.hpp"
#include "coroutines_io/tcp_acceptor.hpp"

#include "client_connection.hpp"

#include <iostream>

using namespace coroutines;
using namespace boost::asio::ip;

void handler(http_request const& req, http_response& res)
{
    res.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
    res.add("Content-Length", "14");
    res.add("Content-Type", "text/plain");

    res.stream() << "hello, world!\n";
}

void start_client_connection(tcp_socket& sock)
{
    client_connection c(std::move(sock), handler);
    c.start();
}

void server()
{
    try
    {
        tcp_acceptor acc;
        acc.listen(tcp::endpoint(address_v4::any(), 8080));

        std::cout << "Server accepting connections" << std::endl;
        for(;;)
        {
            tcp_socket sock = acc.accept();
            go("client connection", start_client_connection, std::move(sock));
        }
    }
    catch(const std::exception& e)
    {
        std::cerr << "server error: " << e.what() << std::endl;
    }
}

int main(int argc, char** argv)
{
    scheduler sched(4);
    io_scheduler io_sched(sched);
    set_scheduler(&sched);
    set_io_scheduler(&io_sched);

    io_sched.start();

    go("acceptor", server);

    sched.wait();
}

Smal HTTP server - Go

And this is the equivalent code in Go. It's a bit smaller, but mostly because it hides the accept/serve loop behind library call. I wanted to keep this part visible. And Go is still ~20% faster. But I'm getting there :)

package main

import (
    "net/http"
    "io"
    "runtime"
)

func HelloServer(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    w.Header().Set("Connection", "keep-alive")
    w.Header().Set("Content-Length", "14")
    io.WriteString(w, "hello, world!\n")
}

func main() {
    runtime.GOMAXPROCS(4)
    http.HandleFunc("/", HelloServer)
    http.ListenAndServe(":8081", nil)
}

No comments:

Post a Comment