-- Leo's gemini proxy

-- Connecting to gemini.clehaxze.tw:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

Asynchronously read STDIN in Drogon/Trantor


I haven't blogged in quite a while. I've been busy on other stuff but they are just non blogable. At least for now. I hope one day I can publish them. Anyway, I was working on some web related code in Drogon and I need to read stdin so the user can control the application. Problem being, C++'s `std::cin` blocks. Nothing on the same thread can execute while it's waiting for the user for input. This might not be an issue for most CLI applications - it is one for mine. I need some background task running.


The solution turns out uses some fairlt unknown and low level API to interface with the poller. My solution is not fool proof (for instance, it'll block if stdin gets flushed and there's no newline in it). But good enough for my use case.


#include <drogin/utils/coroutine.h>
#include <drogin/drogon.h>
#include <trantor/net/Channel.h>

Task<std::string> asyncReadLineStdin()
{
    // CallbackAwaiter is a struct in Drogon that allows easy wrapping
    // from a callback into coroutine
    struct Awaiter : public CallbackAwaiter<std::string>
    {
        trantor::Channel channel;

        // Create a channel to notify on stdin (0, per UNIX standard)
        Awaiter() : channel(app().getLoop(), 0) {}

        void await_suspend(std::coroutine_handle<> coro)
        {
            // Tell Trantor we want to get a notification wehn stdin
            // is readable
            channel.enableReading();
            channel.setReadCallback([this, coro]() {
                // when stdin is readable. We read a line from it
                std::string line;
                std::getline(std::cin, line);
                // Read the line. Set it as the coroutine's return value
                setValue(line);
                // We are no longer interested in stdin. Remove the channel
                app().getLoop()->removeChannel(&channel);
                // resume the coroutine
                coro.resume();
            });
            channel.ownerLoop()->updateChannel(&channel);
        }
    };

    // make sure we are on the main loop, else adding the channel will fail
    co_await switchThreadCoro(app().getLoop());
    std::string res = co_await Awaiter();
    co_return res;
}

-- Response ended

-- Page fetched on Fri Jun 14 09:05:37 2024