$30 off During Our Annual Pro Sale. View Details »

Asynchronous I/O and coroutines for smooth data streaming

Asynchronous I/O and coroutines for smooth data streaming

Linux kernel 5.1 introduced io_uring, which is a mechanism to do asynchronous I/O, primarily for network and disk operations. With asynchronous I/O, the responsiveness of your program is enhanced, but it can easily lead to "callback hell", where you register callbacks that processes arrived data, which feeds information to other callbacks, and so on. C++20 brings us language level coroutines. Coroutines are a generalization of functions, that can be suspended in the middle to allow other computations, and then resumed again, all in the same thread. One such suspension point can be to wait for the arrival of data. In this presentation I will bring a brief introduction to both topics, and then show how to use io_uring and coroutines to write code that reads asynchronous data in seeral short loops, seemingly running in parallel, without having to worry about threading issues.

Björn Fahller

October 20, 2021
Tweet

More Decks by Björn Fahller

Other Decks in Programming

Transcript

  1. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 1/121
    Asynchronous I/O and coroutines for smooth data streaming
    Björn Fahller
    #include
    ...
    x = co_await source;
    co_yield computation(x);
    io_uring

    View Slide

  2. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 2/121
    Asynchronous I/O and coroutines for smooth data streaming

    View Slide

  3. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 3/121
    Asynchronous I/O and coroutines for smooth data streaming

    View Slide

  4. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 4/121
    Asynchronous I/O and coroutines for smooth data streaming

    View Slide

  5. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 5/121
    Asynchronous I/O and coroutines for smooth data streaming

    View Slide

  6. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 6/121
    Asynchronous I/O and coroutines for smooth data streaming
    Björn Fahller
    #include
    ...
    x = co_await source;
    co_yield computation(x);
    io_uring

    View Slide

  7. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 7/121
    Linux networking

    Traditionally we use select/poll/epoll to
    register file descriptors we want to react to

    And read/recv/recvmsg/recvmmsg to read
    the data (and corresponding to send).

    View Slide

  8. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 8/121
    class poller {
    public:
    using worker = std::function data)>;
    void add(int fd, worker w) {
    fds_.push_back({fd, POLLIN, 0});
    cbs_.emplace(fd, std::move(w));
    }
    void wait() {
    auto r = poll(fds_.data(), fds_.size(), -1);
    for (auto& e : fds_) {
    if (e.revents & POLLIN) {
    char buffer[1500];
    auto len = ::read(e.fd, buffer, sizeof(buffer));
    cbs_[e.fd](std::span(buffer).first(len));
    }
    }
    }
    private:
    std::vector fds_;
    std::map cbs_;
    };

    View Slide

  9. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 9/121
    Synchronous I/O with poll/read

    View Slide

  10. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 10/121
    Synchronous I/O with poll/read
    poll()

    View Slide

  11. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 11/121
    Synchronous I/O with poll/read
    poll()
    fill from
    kernel

    View Slide

  12. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 12/121
    Synchronous I/O with poll/read
    fill from
    kernel
    read()

    View Slide

  13. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 13/121
    Synchronous I/O with poll/read
    fill from
    kernel
    read()

    View Slide

  14. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 14/121
    Synchronous I/O with poll/read
    fill from
    kernel
    read()
    fill from
    kernel

    View Slide

  15. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 15/121
    Synchronous I/O with poll/read
    fill from
    kernel
    read() process
    fill from
    kernel

    View Slide

  16. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 16/121
    Synchronous I/O with poll/read
    fill from
    kernel
    read() process poll()
    fill from
    kernel

    View Slide

  17. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 17/121
    Live Demo!

    View Slide

  18. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 18/121
    io_uring
    userspace
    kernel
    submission
    queue (SQ)
    IP stack Filesystem
    completion
    queue (CQ)

    View Slide

  19. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 19/121
    io_uring
    userspace
    kernel
    submission
    queue (SQ)
    IP stack Filesystem
    completion
    queue (CQ)
    #include
    io_uring uring;
    io_uring_queue_init(8, &uring, 0);
    ...
    auto entry = io_uring_get_sqe(&uring);
    io_uring_prep_read(entry, fd, ptr, size, 0);
    io_uring_sqe_set_data(entry, work);
    ...
    io_uring_cqe* entry;
    auto e = io_uring_wait_cqe(&uring, &entry);

    View Slide

  20. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 20/121
    io_uring
    userspace
    kernel
    submission
    queue (SQ)
    IP stack Filesystem
    completion
    queue (CQ)
    #include
    io_uring uring;
    io_uring_queue_init(8, &uring, 0);
    ...
    auto entry = io_uring_get_sqe(&uring);
    io_uring_prep_read(entry, fd, ptr, size, 0);
    io_uring_sqe_set_data(entry, work);
    ...
    io_uring_cqe* entry;
    auto e = io_uring_wait_cqe(&uring, &entry);
    Size of queue, i.e. max
    number of pending entries

    View Slide

  21. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 21/121
    io_uring
    userspace
    kernel
    submission
    queue (SQ)
    IP stack Filesystem
    completion
    queue (CQ)
    #include
    io_uring uring;
    io_uring_queue_init(8, &uring, 0);
    ...
    auto entry = io_uring_get_sqe(&uring);
    io_uring_prep_read(entry, fd, ptr, size, 0);
    io_uring_sqe_set_data(entry, work);
    ...
    io_uring_cqe* entry;
    auto e = io_uring_wait_cqe(&uring, &entry);
    Annoyingly only one word,
    64-bits, to express the
    operation and the data, so
    an indirection is almost
    always needed.

    View Slide

  22. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 22/121
    io_uring
    userspace
    kernel
    submission
    queue (SQ)
    IP stack Filesystem
    completion
    queue (CQ)
    #include
    io_uring uring;
    io_uring_queue_init(8, &uring, 0);
    ...
    auto entry = io_uring_get_sqe(&uring);
    io_uring_prep_read(entry, fd, ptr, size, 0);
    io_uring_sqe_set_data(entry, work);
    ...
    io_uring_cqe* entry;
    auto e = io_uring_wait_cqe(&uring, &entry);

    View Slide

  23. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 23/121
    uring
    #include
    class ring
    {
    public:
    using work = std::function)>;
    ring();
    ring& operator=(ring&&) = delete;
    ~ring();
    void add(int fd, work);
    void wait();
    private:
    struct read_work;
    std::list pending_;
    io_uring uring_;
    };

    View Slide

  24. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 24/121
    uring
    #include
    class ring
    {
    public:
    using work = std::function)>;
    ring();
    ring& operator=(ring&&) = delete;
    ~ring();
    void add(int fd, work);
    void wait();
    private:
    struct read_work;
    std::list pending_;
    io_uring uring_;
    };
    struct ring::read_work {
        work cb_;
        int fd_;
        std::array buffer_;
    };
    void ring::add(int fd, work w)
    {
    auto& work = pending_.emplace_back();
    work.cb_ = std::move(w);
    work.fd_ = fd;
    auto entry = io_uring_get_sqe(&uring_);
    io_uring_prep_read(entry, fd,
    work.buffer_.data(),
    work.buffer_.size(),
    0);
    io_uring_sqe_set_data(entry, &work);
    }

    View Slide

  25. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 25/121
    uring
    #include
    class ring
    {
    public:
    using work = std::function)>;
    ring();
    ring& operator=(ring&&) = delete;
    ~ring();
    void add(int fd, work);
    void wait();
    private:
    struct read_work;
    std::list pending_;
    io_uring uring_;
    };
    struct ring::read_work {
        work cb_;
        int fd_;
        std::array buffer_;
    };
    void ring::add(int fd, work w)
    {
    auto& work = pending_.emplace_back();
    work.cb_ = std::move(w);
    work.fd_ = fd;
    auto entry = io_uring_get_sqe(&uring_);
    io_uring_prep_read(entry, fd,
    work.buffer_.data(),
    work.buffer_.size(),
    0);
    io_uring_sqe_set_data(entry, &work);
    }
    The data area to read
    into needs to be
    available when
    preparing the read
    operation

    View Slide

  26. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 26/121
    uring
    #include
    class ring
    {
    public:
    using work = std::function)>;
    ring();
    ring& operator=(ring&&) = delete;
    ~ring();
    void add(int fd, work);
    void wait();
    private:
    struct read_work;
    std::list pending_;
    io_uring uring_;
    };
    struct ring::read_work {
        work cb_;
        int fd_;
        std::array buffer_;
    };
    void ring::add(int fd, work w)
    {
    auto& work = pending_.emplace_back();
    work.cb_ = std::move(w);
    work.fd_ = fd;
    auto entry = io_uring_get_sqe(&uring_);
    io_uring_prep_read(entry, fd,
    work.buffer_.data(),
    work.buffer_.size(),
    0);
    io_uring_sqe_set_data(entry, &work);
    }
    Get a submission queue
    entry and prepare a
    read to the buffer from
    the file descriptor

    View Slide

  27. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 27/121
    uring
    #include
    class ring
    {
    public:
    using work = std::function)>;
    ring();
    ring& operator=(ring&&) = delete;
    ~ring();
    void add(int fd, work);
    void wait();
    private:
    struct read_work;
    std::list pending_;
    io_uring uring_;
    };
    struct ring::read_work {
        work cb_;
        int fd_;
        std::array buffer_;
    };
    void ring::add(int fd, work w)
    {
    auto& work = pending_.emplace_back();
    work.cb_ = std::move(w);
    work.fd_ = fd;
    auto entry = io_uring_get_sqe(&uring_);
    io_uring_prep_read(entry, fd,
    work.buffer_.data(),
    work.buffer_.size(),
    0);
    io_uring_sqe_set_data(entry, &work);
    }
    And associate the work
    struct with the data area
    and callback with the
    submission queue entry

    View Slide

  28. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 28/121
    data
    buffers
    ready

    View Slide

  29. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 29/121
    data
    buffers
    ready
    fill from
    kernel

    View Slide

  30. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 30/121
    data
    buffers
    ready
    fill from
    kernel
    wait

    View Slide

  31. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 31/121
    data
    buffers
    ready
    fill from
    kernel
    wait
    fill from
    kernel

    View Slide

  32. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 32/121
    data
    buffers
    ready
    fill from
    kernel
    wait
    fill from
    kernel
    process

    View Slide

  33. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 33/121
    data
    buffers
    ready
    fill from
    kernel
    wait
    fill from
    kernel
    process
    fill from
    kernel

    View Slide

  34. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 34/121
    data
    buffers
    ready
    fill from
    kernel
    wait
    fill from
    kernel
    process process
    fill from
    kernel

    View Slide

  35. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 35/121
    data
    buffers
    ready
    fill from
    kernel
    wait
    fill from
    kernel
    process process
    fill from
    kernel

    View Slide

  36. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 36/121
    data
    buffers
    ready
    fill from
    kernel
    wait
    fill from
    kernel
    process process
    fill from
    kernel
    Fewer
    system calls
    too!

    View Slide

  37. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 37/121
    Live Demo!

    View Slide

  38. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 38/121
    coroutines

    View Slide

  39. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 39/121
    coroutines
    Offers a way for you to write asynchronous
    code as if they were continuous loops

    View Slide

  40. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 40/121
    coroutines
    Offers a way for you to write asynchronous
    code as if they were continuous loops
    Language support from C++20

    View Slide

  41. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 41/121
    coroutines
    Offers a way for you to write asynchronous
    code as if they were continuous loops
    Language support from C++20
    Compiler magic converts it to something else,
    via types that you must write

    View Slide

  42. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 42/121
    coroutines
    Offers a way for you to write asynchronous
    code as if they were continuous loops
    Language support from C++20
    Compiler magic converts it to something else,
    via types that you must write
    – And they’re mindbogglingly hard to understand

    View Slide

  43. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 43/121
    coroutines
    Offers a way for you to write asynchronous code as if
    they were continuous loops
    Language support from C++20
    Compiler magic converts it to something else, via
    types that you must write
    – And they’re mindbogglingly hard to understand
    – And the standard library doesn’t help

    View Slide

  44. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 44/121
    coroutines
    for (;;) {

    }
    for (;;) {

    }

    View Slide

  45. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 45/121
    coroutines
    for (;;) {

    }
    for (;;) {

    }
    Suspend execution

    View Slide

  46. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 46/121
    coroutines
    for (;;) {

    }
    for (;;) {

    }
    Compute x
    Suspend execution

    View Slide

  47. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 47/121
    coroutines
    for (;;) {

    }
    for (;;) {

    }
    Compute x
    Suspend execution
    Resume execution with x
    Suspend execution

    View Slide

  48. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 48/121
    coroutines
    for (;;) {

    }
    for (;;) {

    }
    Compute x
    Suspend execution
    Resume execution with x
    Work with x
    Suspend execution

    View Slide

  49. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 49/121
    coroutines
    for (;;) {

    }
    for (;;) {

    }
    Compute x
    Suspend execution
    Resume execution with x
    Work with x
    Suspend execution
    Suspend execution
    Resume execution

    View Slide

  50. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 50/121
    coroutines
    for (;;) {

    }
    for (;;) {

    }
    Compute x
    Suspend execution
    Resume execution with x
    Work with x
    Suspend execution
    Suspend execution
    Resume execution
    Compute x

    View Slide

  51. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 51/121
    coroutines
    for (;;) {

    }
    for (;;) {

    }
    Compute x
    Suspend execution
    Resume execution with x
    Work with x
    Suspend execution
    Suspend execution
    Resume execution
    Compute x
    Suspend execution
    Resume execution with x

    View Slide

  52. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 52/121
    coroutines
    for (;;) {

    }
    for (;;) {

    }
    Compute x
    Suspend execution
    Resume execution with x
    Work with x
    Suspend execution
    Suspend execution
    Resume execution
    Compute x
    Suspend execution
    Resume execution with x
    Work with x

    View Slide

  53. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 53/121
    coroutines
    for (;;) {

    }
    for (;;) {

    }
    Compute x
    Suspend execution
    Resume execution with x
    Work with x
    Suspend execution
    Suspend execution
    Resume execution
    Compute x
    Suspend execution
    Resume execution with x
    Work with x
    Suspend execution
    Resume execution

    View Slide

  54. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 54/121
    coroutines
    coroutine_type
    my_coro(int x, int y, coro_src& src)
    {
        int result = 0;
        while (int a = co_await src)
        {
            result += work(a,x,y);
        }
        co_return result;
    }
    auto coro_obj = my_coro(3, 8, source);

    View Slide

  55. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 55/121
    coroutines
    coroutine_type
    my_coro(int x, int y, coro_src& src)
    {
        int result = 0;
        while (int a = co_await src)
        {
            result += work(a,x,y);
        }
        co_return result;
    }
    auto coro_obj = my_coro(3, 8, source);
    class my_coro {
    public:
        my_coro(int x_, int y_) : x(x_), y(y_) {}
        int get_result() const { return result; }
        void operator()(int a)
        {
        result += work(a, x, y);
        }
    private:
        int x;
        int y;
        int result = 0;
    };
    auto coro_obj = impl(new my_coro(3, 8), source);
    Compiler
    rewrites
    like

    View Slide

  56. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 56/121
    coroutines
    coroutine_type
    my_coro(int x, int y, coro_src& src)
    {
        int result = 0;
        while (int a = co_await src)
        {
            result += work(a,x,y);
        }
        co_return result;
    }
    auto coro_obj = my_coro(3, 8, source);
    class my_coro {
    public:
        my_coro(int x_, int y_) : x(x_), y(y_) {}
        int get_result() const { return result; }
        void operator()(int a)
        {
        result += work(a, x, y);
        }
    private:
        int x;
        int y;
        int result = 0;
    };
    auto coro_obj = impl(new my_coro(3, 8), source);
    It’s not this
    simple!
    Compiler
    rewrites
    like

    View Slide

  57. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 57/121
    coroutines
    coroutine_type
    my_coro(int x, int y, coro_src& src)
    {
        int result = 0;
        while (int a = co_await src)
        {
            result += work(a,x,y);
        }
        co_return result;
    }
    auto coro_obj = my_coro(3, 8, source);

    View Slide

  58. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 58/121
    coroutines
    coroutine_type
    my_coro(int x, int y, coro_src& src)
    {
        int result = 0;
        while (int a = co_await src)
        {
            result += work(a,x,y);
        }
        co_return result;
    }
    auto coro_obj = my_coro(3, 8, source);
    We must write
    this type

    View Slide

  59. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 59/121
    coroutines
    coroutine_type
    my_coro(int x, int y, coro_src& src)
    {
        int result = 0;
        while (int a = co_await src)
        {
            result += work(a,x,y);
        }
        co_return result;
    }
    auto coro_obj = my_coro(3, 8, source);
    We must write
    this type
    and this type

    View Slide

  60. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 60/121
    coroutine return object (task)
    template
    struct task
    {
    using promise_type = promise;
    auto operator co_await() const noexcept;
    private:
    task(promise* p) : m_promise(p) { }
    friend class promise;
    promise_ptr m_promise;
    };

    View Slide

  61. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 61/121
    coroutine return object (task)
    template
    struct task
    {
    using promise_type = promise;
    auto operator co_await() const noexcept;
    private:
    task(promise* p) : m_promise(p) { }
    friend class promise;
    promise_ptr m_promise;
    };
    NOT std::promise!

    View Slide

  62. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 62/121
    coroutine return object (task)
    template
    struct task
    {
    using promise_type = promise;
    auto operator co_await() const noexcept;
    private:
    task(promise* p) : m_promise(p) { }
    friend class promise;
    promise_ptr m_promise;
    };
    NOT std::promise!
    And we have
    to write it
    ourselves

    View Slide

  63. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 63/121
    coroutine return object (task)
    template
    struct task
    {
    using promise_type = promise;
    auto operator co_await() const noexcept;
    private:
    task(promise* p) : m_promise(p) { }
    friend class promise;
    promise_ptr m_promise;
    };
    We will be given a
    promise, allocated
    by compiler magic.

    View Slide

  64. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 64/121
    coroutine return object (task)
    template
    struct task
    {
    using promise_type = promise;
    auto operator co_await() const noexcept;
    private:
    task(promise* p) : m_promise(p) { }
    friend class promise;
    promise_ptr m_promise;
    };
    And we must destroy it
    the right way. A smart
    pointer makes this easy.

    View Slide

  65. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 65/121
    coroutine return object (task)
    template
    struct task
    {
    using promise_type = promise;
    auto operator co_await() const noexcept;
    private:
    task(promise* p) : m_promise(p) { }
    friend class promise;
    promise_ptr m_promise;
    };
    struct coro_deleter
    {
    template
    void operator()(promise* p) const noexcept {
    using handle = std::coroutine_handle;
    handle::from_promise(*p).destroy();
    }
    };
    template
    using promise_ptr = std::unique_ptr,
    coro_deleter>;

    View Slide

  66. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 66/121
    template
    struct promise
    {
    task get_return_object() noexcept;
    std::suspend_never initial_suspend() noexcept;
    std::suspend_always final_suspend() noexcept;

    bool is_ready() const noexcept;
    T get();
    void unhandled_exception();
    template
    std::suspend_always yield_value(U&& u);
    void return_void();
    std::coroutine_handle<> m_continuation;
    std::optional m_value;
    };
    coroutines promise

    View Slide

  67. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 67/121
    template
    struct promise
    {
    task get_return_object() noexcept;
    std::suspend_never initial_suspend() noexcept;
    std::suspend_always final_suspend() noexcept;

    bool is_ready() const noexcept;
    T get();
    void unhandled_exception();
    template
    std::suspend_always yield_value(U&& u);
    void return_void();
    std::coroutine_handle<> m_continuation;
    std::optional m_value;
    };
    coroutines promise
    The function that creates
    the task object

    View Slide

  68. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 68/121
    template
    struct promise
    {
    task get_return_object() noexcept;
    std::suspend_never initial_suspend() noexcept;
    std::suspend_always final_suspend() noexcept;

    bool is_ready() const noexcept;
    T get();
    void unhandled_exception();
    template
    std::suspend_always yield_value(U&& u);
    void return_void();
    std::coroutine_handle<> m_continuation;
    std::optional m_value;
    };
    coroutines promise
    Behaviour at the
    beginning and end of the
    life of the coroutine

    View Slide

  69. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 69/121
    template
    struct promise
    {
    task get_return_object() noexcept;
    std::suspend_never initial_suspend() noexcept;
    std::suspend_always final_suspend() noexcept;

    bool is_ready() const noexcept;
    T get();
    void unhandled_exception();
    template
    std::suspend_always yield_value(U&& u);
    void return_void();
    std::coroutine_handle<> m_continuation;
    std::optional m_value;
    };
    coroutines promise
    Utility functions for our
    own implementation

    View Slide

  70. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 70/121
    template
    struct promise
    {
    task get_return_object() noexcept;
    std::suspend_never initial_suspend() noexcept;
    std::suspend_always final_suspend() noexcept;

    bool is_ready() const noexcept;
    T get();
    void unhandled_exception();
    template
    std::suspend_always yield_value(U&& u);
    void return_void();
    std::coroutine_handle<> m_continuation;
    std::optional m_value;
    };
    coroutines promise
    Handle to
    suspended
    coroutine

    View Slide

  71. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 71/121
    template
    struct promise
    {
    task get_return_object() noexcept;
    std::suspend_never initial_suspend() noexcept;
    std::suspend_always final_suspend() noexcept;

    bool is_ready() const noexcept;
    T get();
    void unhandled_exception();
    template
    std::suspend_always yield_value(U&& u);
    void return_void();
    std::coroutine_handle<> m_continuation;
    std::optional m_value;
    };
    template
    struct promise
    {
    task get_return_object() noexcept;
    std::suspend_never initial_suspend() noexcept;
    std::suspend_always final_suspend() noexcept;

    bool is_ready() const noexcept;
    T get();
    void unhandled_exception();
    template
    std::suspend_always yield_value(U&& u){
    m_value.emplace(std::forward(u));
    m_continuation.resume();
    return {};
    }
    };
    coroutines promise
    Resume execution of
    the coroutine that is
    suspended waiting for a
    value.

    View Slide

  72. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 72/121
    coroutine return object (task) cont.
    template
    struct task
    {
        auto operator co_await() const noexcept {
            struct awaitable {
                bool await_ready() const noexcept {
                    return m_promise.is_ready();
                }
                void await_suspend(std::coroutine_handle<> next) const noexcept {
                    m_promise.m_continuation = next;
                }
                T await_resume() const {
                    return m_promise.get();
                }
                promise& m_promise;
            };
            return awaitable{ *m_promise };
        }
    };

    View Slide

  73. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 73/121
    coroutine return object (task) cont.
    template
    struct task
    {
        auto operator co_await() const noexcept {
            struct awaitable {
                bool await_ready() const noexcept {
                    return m_promise.is_ready();
                }
                void await_suspend(std::coroutine_handle<> next) const noexcept {
                    m_promise.m_continuation = next;
                }
                T await_resume() const {
                    return m_promise.get();
                }
                promise& m_promise;
            };
            return awaitable{ *m_promise };
        }
    };
    Operator is called when
    code calls:
    co_await task;

    View Slide

  74. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 74/121
    coroutine return object (task) cont.
    template
    struct task
    {
        auto operator co_await() const noexcept {
            struct awaitable {
                bool await_ready() const noexcept {
                    return m_promise.is_ready();
                }
                void await_suspend(std::coroutine_handle<> next) const noexcept {
                    m_promise.m_continuation = next;
                }
                T await_resume() const {
                    return m_promise.get();
                }
                promise& m_promise;
            };
            return awaitable{ *m_promise };
        }
    };
    It returns an awaitable
    object that communicates
    with the promise

    View Slide

  75. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 75/121
    coroutine return object (task) cont.
    template
    struct task
    {
        auto operator co_await() const noexcept {
            struct awaitable {
                bool await_ready() const noexcept {
                    return m_promise.is_ready();
                }
                void await_suspend(std::coroutine_handle<> next) const noexcept {
                    m_promise.m_continuation = next;
                }
                T await_resume() const {
                    return m_promise.get();
                }
                promise& m_promise;
            };
            return awaitable{ *m_promise };
        }
    };
    Check if the promise
    holds a value

    View Slide

  76. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 76/121
    coroutine return object (task) cont.
    template
    struct task
    {
        auto operator co_await() const noexcept {
            struct awaitable {
                bool await_ready() const noexcept {
                    return m_promise.is_ready();
                }
                void await_suspend(std::coroutine_handle<> next) const noexcept {
                    m_promise.m_continuation = next;
                }
                T await_resume() const {
                    return m_promise.get();
                }
                promise& m_promise;
            };
            return awaitable{ *m_promise };
        }
    };
    Store the calling coroutine as
    the one to continue when the
    promise gets a value

    View Slide

  77. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 77/121
    coroutine return object (task) cont.
    template
    struct task
    {
        auto operator co_await() const noexcept {
            struct awaitable {
                bool await_ready() const noexcept {
                    return m_promise.is_ready();
                }
                void await_suspend(std::coroutine_handle<> next) const noexcept {
                    m_promise.m_continuation = next;
                }
                T await_resume() const {
                    return m_promise.get();
                }
                promise& m_promise;
            };
            return awaitable{ *m_promise };
        }
    };
    And finally, on resume, get the
    value from the promise,
    making it empty again.

    View Slide

  78. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 78/121
    coroutine return object (task) cont.
    template
    struct task
    {
        auto operator co_await() const noexcept {
            struct awaitable {
                bool await_ready() const noexcept {
                    return m_promise.is_ready();
                }
                void await_suspend(std::coroutine_handle<> next) const noexcept {
                    m_promise.m_continuation = next;
                }
                T await_resume() const {
                    return m_promise.get();
                }
                promise& m_promise;
            };
            return awaitable{ *m_promise };
        }
    };

    View Slide

  79. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 79/121
    Live Demo!

    View Slide

  80. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 80/121
    Making a computation pipeline
    template
    task filter_in(P predicate, task& in)
    {
    for (;;) {
    auto v = co_await in;
    if (predicate(v)) {
    co_yield v;
    }
    }
    }

    View Slide

  81. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 81/121
    Making a computation pipeline
    template
    task filter_in(P predicate, task& in)
    {
    for (;;) {
    auto v = co_await in;
    if (predicate(v)) {
    co_yield v;
    }
    }
    }
    Calls the yield_value()
    member function on the
    promise of the return type
    for the coroutine.

    View Slide

  82. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 82/121
    Making a computation pipeline
    template
    task filter_in(P predicate, task& in)
    {
    for (;;) {
    auto v = co_await in;
    if (predicate(v)) {
    co_yield v;
    }
    }
    }
    int main()
    {
    auto is_odd = [](auto v) { return v & 1;};
    auto incoming = task::make();
    auto odd_values = filter_in(is_odd, incoming);
    auto printer = print_all(odd_values);
    for (int i = 0; i < 10; ++i) {
    incoming.get_promise().yield_value(i);
    }
    }

    View Slide

  83. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 83/121
    Making a computation pipeline
    template
    task filter_in(P predicate, task& in)
    {
    for (;;) {
    auto v = co_await in;
    if (predicate(v)) {
    co_yield v;
    }
    }
    }
    int main()
    {
    auto is_odd = [](auto v) { return v & 1;};
    auto incoming = task::make();
    auto odd_values = filter_in(is_odd, incoming);
    auto printer = print_all(odd_values);
    for (int i = 0; i < 10; ++i) {
    incoming.get_promise().yield_value(i);
    }
    }

    View Slide

  84. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 84/121
    Making a computation pipeline
    template
    task filter_in(P predicate, task& in)
    {
    for (;;) {
    auto v = co_await in;
    if (predicate(v)) {
    co_yield v;
    }
    }
    }
    int main()
    {
    auto is_odd = [](auto v) { return v & 1;};
    auto incoming = task::make();
    auto odd_values = filter_in(is_odd, incoming);
    auto printer = print_all(odd_values);
    for (int i = 0; i < 10; ++i) {
    incoming.get_promise().yield_value(i);
    }
    }

    View Slide

  85. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 85/121
    Making a computation pipeline
    template
    task filter_in(P predicate, task& in)
    {
    for (;;) {
    auto v = co_await in;
    if (predicate(v)) {
    co_yield v;
    }
    }
    }
    int main()
    {
    auto is_odd = [](auto v) { return v & 1;};
    auto incoming = task::make();
    auto odd_values = filter_in(is_odd, incoming);
    auto printer = print_all(odd_values);
    for (int i = 0; i < 10; ++i) {
    incoming.get_promise().yield_value(i);
    }
    }

    View Slide

  86. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 86/121
    Making a computation pipeline
    template
    task filter_in(P predicate, task& in)
    {
    for (;;) {
    auto v = co_await in;
    if (predicate(v)) {
    co_yield v;
    }
    }
    }
    int main()
    {
    auto is_odd = [](auto v) { return v & 1;};
    auto incoming = task::make();
    auto odd_values = filter_in(is_odd, incoming);
    auto printer = print_all(odd_values);
    for (int i = 0; i < 10; ++i) {
    incoming.get_promise().yield_value(i);
    }
    }

    View Slide

  87. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 87/121
    Live Demo!

    View Slide

  88. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 88/121
    io_uring + coroutines = 💖

    View Slide

  89. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 89/121
    io_uring + coroutines = 💖
    We’ve seen how:

    View Slide

  90. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 90/121
    io_uring + coroutines = 💖
    We’ve seen how:

    io_uring offers asynchronous data

    View Slide

  91. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 91/121
    io_uring + coroutines = 💖
    We’ve seen how:

    io_uring offers asynchronous data

    Calling yield_value() on a coroutine promise
    pushes data through the coroutine pipeline

    View Slide

  92. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 92/121
    io_uring + coroutines = 💖
    We’ve seen how:

    io_uring offers asynchronous data

    Calling yield_value() on a coroutine promise
    pushes data through the coroutine pipeline

    How to read values from an upstream coroutine
    with co_await

    View Slide

  93. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 93/121
    io_uring + coroutines = 💖
    We’ve seen how:

    io_uring offers asynchronous data

    Calling yield_value() on a coroutine promise
    pushes data through the coroutine pipeline

    How to read values from an upstream coroutine
    with co_await

    How to forward values downstream with
    co_yield

    View Slide

  94. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 94/121
    io_uring + coroutines = 💖
    We’ve seen how:

    io_uring offers asynchronous data

    Calling yield_value() on a coroutine promise
    pushes data through the coroutine pipeline

    How to read values from an upstream coroutine
    with co_await

    How to forward values downstream with
    co_yield
    Let’s put the
    pieces together

    View Slide

  95. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 95/121
    uring + coroutines
    auto to_promise = [](auto& promise) {
    return [&](auto packet) {
    promise.yield_value(packet);
    return true;
    };
    };
    Convenient tool to generate
    a callback that writes to a
    promise, and thus drives a
    coroutine pipeline.

    View Slide

  96. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 96/121
    uring + coroutines
    int main()
    {
    uint64_t num_bytes = 0;
    uint64_t num_packets = 0;
    bool done = false;
    auto in_4000 = task>::make();
    auto counted_packets = count_packet_data(num_packets, num_bytes, in_4000);
    auto strings = to_string(counted_packets);
    auto stripped_strings = strip_trailing_newline(strings);
    auto lines = concatenate_to<40>(stripped_strings);
    auto print = print_lines(std::cout, lines);

    View Slide

  97. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 97/121
    uring + coroutines
    int main()
    {
    uint64_t num_bytes = 0;
    uint64_t num_packets = 0;
    bool done = false;
    auto in_4000 = task>::make();
    auto counted_packets = count_packet_data(num_packets, num_bytes, in_4000);
    auto strings = to_string(counted_packets);
    auto stripped_strings = strip_trailing_newline(strings);
    auto lines = concatenate_to<40>(stripped_strings);
    auto print = print_lines(std::cout, lines);

    View Slide

  98. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 98/121
    uring + coroutines
    int main()
    {
    uint64_t num_bytes = 0;
    uint64_t num_packets = 0;
    bool done = false;
    auto in_4000 = task>::make();
    auto counted_packets = count_packet_data(num_packets, num_bytes, in_4000);
    auto strings = to_string(counted_packets);
    auto stripped_strings = strip_trailing_newline(strings);
    auto lines = concatenate_to<40>(stripped_strings);
    auto print = print_lines(std::cout, lines);

    task>
    count_packet_data(uint64_t& packets,
    uint64_t& bytes,
    task>& in)
    {
    for (;;) {
    auto packet = co_await in;
    ++packets;
    bytes += packet.size();
    co_yield packet;
    }
    }

    View Slide

  99. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 99/121
    uring + coroutines
    int main()
    {
    uint64_t num_bytes = 0;
    uint64_t num_packets = 0;
    bool done = false;
    auto in_4000 = task>::make();
    auto counted_packets = count_packet_data(num_packets, num_bytes, in_4000);
    auto strings = to_string(counted_packets);
    auto stripped_strings = strip_trailing_newline(strings);
    auto lines = concatenate_to<40>(stripped_strings);
    auto print = print_lines(std::cout, lines);

    View Slide

  100. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 100/121
    uring + coroutines
    int main()
    {
    uint64_t num_bytes = 0;
    uint64_t num_packets = 0;
    bool done = false;
    auto in_4000 = task>::make();
    auto counted_packets = count_packet_data(num_packets, num_bytes, in_4000);
    auto strings = to_string(counted_packets);
    auto stripped_strings = strip_trailing_newline(strings);
    auto lines = concatenate_to<40>(stripped_strings);
    auto print = print_lines(std::cout, lines);

    task to_string(task>& in)
    {
    for (;;) {
    auto packet = co_await in;
    co_yield std::string{packet.begin(), packet.end()};
    }
    }

    View Slide

  101. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 101/121
    uring + coroutines
    int main()
    {
    uint64_t num_bytes = 0;
    uint64_t num_packets = 0;
    bool done = false;
    auto in_4000 = task>::make();
    auto counted_packets = count_packet_data(num_packets, num_bytes, in_4000);
    auto strings = to_string(counted_packets);
    auto stripped_strings = strip_trailing_newline(strings);
    auto lines = concatenate_to<40>(stripped_strings);
    auto print = print_lines(std::cout, lines);

    View Slide

  102. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 102/121
    uring + coroutines
    int main()
    {
    uint64_t num_bytes = 0;
    uint64_t num_packets = 0;
    bool done = false;
    auto in_4000 = task>::make();
    auto counted_packets = count_packet_data(num_packets, num_bytes, in_4000);
    auto strings = to_string(counted_packets);
    auto stripped_strings = strip_trailing_newline(strings);
    auto lines = concatenate_to<40>(stripped_strings);
    auto print = print_lines(std::cout, lines);

    task
    strip_trailing_newline(task& in)
    {
    for (;;) {
    auto s = co_await in;
    while (s.ends_with("\n")) {
    s.resize(s.length() - 1);
    }
    co_yield s;
    }
    }

    View Slide

  103. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 103/121
    uring + coroutines
    int main()
    {
    uint64_t num_bytes = 0;
    uint64_t num_packets = 0;
    bool done = false;
    auto in_4000 = task>::make();
    auto counted_packets = count_packet_data(num_packets, num_bytes, in_4000);
    auto strings = to_string(counted_packets);
    auto stripped_strings = strip_trailing_newline(strings);
    auto lines = concatenate_to<40>(stripped_strings);
    auto print = print_lines(std::cout, lines);

    View Slide

  104. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 104/121
    uring + coroutines
    int main()
    {
    uint64_t num_bytes = 0;
    uint64_t num_packets = 0;
    bool done = false;
    auto in_4000 = task>::make();
    auto counted_packets = count_packet_data(num_packets, num_bytes, in_4000);
    auto strings = to_string(counted_packets);
    auto stripped_strings = strip_trailing_newline(strings);
    auto lines = concatenate_to<40>(stripped_strings);
    auto print = print_lines(std::cout, lines);

    template
    task concatenate_to(task& in)
    {
    std::string current_line;
    for (;;) {
    auto next_piece = co_await in;
    if (current_line.length() + next_piece.length() + 1 > line_length) {
    co_yield std::exchange(current_line, next_piece);
    } else if (current_line.empty()) {
    current_line = next_piece;
    } else {
    current_line += " " + next_piece;
    }
    }
    }

    View Slide

  105. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 105/121
    uring + coroutines
    int main()
    {
    uint64_t num_bytes = 0;
    uint64_t num_packets = 0;
    bool done = false;
    auto in_4000 = task>::make();
    auto counted_packets = count_packet_data(num_packets, num_bytes, in_4000);
    auto strings = to_string(counted_packets);
    auto stripped_strings = strip_trailing_newline(strings);
    auto lines = concatenate_to<40>(stripped_strings);
    auto print = print_lines(std::cout, lines);

    View Slide

  106. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 106/121
    uring + coroutines
    int main()
    {
    uint64_t num_bytes = 0;
    uint64_t num_packets = 0;
    bool done = false;
    auto in_4000 = task>::make();
    auto counted_packets = count_packet_data(num_packets, num_bytes, in_4000);
    auto strings = to_string(counted_packets);
    auto stripped_strings = strip_trailing_newline(strings);
    auto lines = concatenate_to<40>(stripped_strings);
    auto print = print_lines(std::cout, lines);

    task print_lines(std::ostream& os, task& in)
    {
    for (;;) {
    auto line = co_await in;
    os << ':' << line << ":\n";
    }
    }

    View Slide

  107. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 107/121
    uring + coroutines
    ...
    auto print_and_exit = [&](auto&&) {
    std::cout << "packets=" << num_packets << " bytes=" << num_bytes << '\n';
    done = true;
    return false;
    };
    ring r;
    auto port4000 = udp_socket("127.0.0.1", 4000);
    auto port4001 = udp_socket("127.0.0.1", 4001);
    r.add(port4000.fd(), to_promise(in_4000.get_promise()));
    r.add(port4001.fd(), print_and_exit);
    while (!done) {
    r.wait();
    }
    }

    View Slide

  108. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 108/121
    Live Demo!

    View Slide

  109. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 109/121
    Some omissions

    Error handling is non-trivial

    Major omission is how to cancel, both coroutines
    and operations from io_uring

    There are so many ways you can tweak
    coroutine behaviour, this was but one simple
    (simplistic?) example

    View Slide

  110. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 110/121
    Conclusions

    View Slide

  111. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 111/121
    Conclusions

    Lack of library support is a major headache

    View Slide

  112. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 112/121
    Conclusions

    Lack of library support is a major headache

    Testing and debugging is terrible, especially if
    you get the future<>/task<>/awaitable<> types
    wrong

    View Slide

  113. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 113/121
    Conclusions

    Lack of library support is a major headache

    Testing and debugging is terrible, especially if
    you get the future<>/task<>/awaitable<> types
    wrong
    template
    struct str {
    constexpr str(const char* p) noexcept {
    std::copy_n(p, N, cstr);
    }
    friend std::ostream& operator<<(std::ostream& os, const str& s) {
    return os << s.cstr;
    }
    char cstr[N];
    };
    template
    str(const char (&)[N]) -> str;

    View Slide

  114. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 114/121
    Conclusions

    Lack of library support is a major headache

    Testing and debugging is terrible, especially if
    you get the future<>/task<>/awaitable<> types
    wrong
    template
    struct str {
    constexpr str(const char* p) noexcept {
    std::copy_n(p, N, cstr);
    }
    friend std::ostream& operator<<(std::ostream& os, const str& s) {
    return os << s.cstr;
    }
    char cstr[N];
    };
    template
    str(const char (&)[N]) -> str;
    template
    struct promise
    {
    task get_return_object() noexcept {
    std::cerr << "task::get_return_object()\n";
    return {this};
    }

    };
    template
    task print_lines(std::ostream& os, task& in)
    {

    View Slide

  115. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 115/121
    Conclusions

    Lack of library support is a major headache

    Testing and debugging is terrible, especially if
    you get the future<>/task<>/awaitable<> types
    wrong
    template
    struct str {
    constexpr str(const char* p) noexcept {
    std::copy_n(p, N, cstr);
    }
    friend std::ostream& operator<<(std::ostream& os, const str& s) {
    return os << s.cstr;
    }
    char cstr[N];
    };
    template
    str(const char (&)[N]) -> str;
    template
    struct promise
    {
    task get_return_object() noexcept {
    std::cerr << "task::get_return_object()\n";
    return {this};
    }

    };
    template
    task print_lines(std::ostream& os, task& in)
    {

    View Slide

  116. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 116/121
    Conclusions

    Lack of library support is a major headache

    Testing and debugging is terrible, especially if
    you get the future<>/task<>/awaitable<> types
    wrong
    template
    struct str {
    constexpr str(const char* p) noexcept {
    std::copy_n(p, N, cstr);
    }
    friend std::ostream& operator<<(std::ostream& os, const str& s) {
    return os << s.cstr;
    }
    char cstr[N];
    };
    template
    str(const char (&)[N]) -> str;
    template
    struct promise
    {
    task get_return_object() noexcept {
    std::cerr << "task::get_return_object()\n";
    return {this};
    }

    };
    template
    task print_lines(std::ostream& os, task& in)
    {

    View Slide

  117. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 117/121
    Conclusions

    Lack of library support is a major headache

    Testing and debugging is terrible, especially if
    you get the future<>/task<>/awaitable<> types
    wrong
    template
    struct str {
    constexpr str(const char* p) noexcept {
    std::copy_n(p, N, cstr);
    }
    friend std::ostream& operator<<(std::ostream& os, const str& s) {
    return os << s.cstr;
    }
    char cstr[N];
    };
    template
    str(const char (&)[N]) -> str;
    template
    struct promise
    {
    task get_return_object() noexcept {
    std::cerr << "task::get_return_object()\n";
    return {this};
    }

    };
    template
    task print_lines(std::ostream& os, task& in)
    {

    View Slide

  118. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 118/121
    Conclusions

    Lack of library support is a major headache

    Testing and debugging is terrible, especially if
    you get the future<>/task<>/awaitable<> types
    wrong

    Writing asynchronous code as if they were local
    loops is very convenient

    View Slide

  119. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 119/121
    Conclusions

    Lack of library support is a major headache

    Testing and debugging is terrible, especially if you
    get the future<>/task<>/awaitable<> types wrong

    Writing asynchronous code as if they were local
    loops is very convenient

    Connecting “pipelines” offers great support for
    generic utilities

    View Slide

  120. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 120/121
    Resources
    Shuveb Hussain – “Lord of the io_uring”
    https://unixism.net/loti/
    Pavel Novikov – “Understanding coroutines by example”, C++London, Feb 2021
    https://www.youtube.com/watch?v=7sKUAyWXNHA
    Lewis Baker – “CppCoro”
    https://github.com/lewissbaker/cppcoro
    Mikhail Svetkin – “ecoro”
    https://github.com/msvetkin/ecoro
    Facebook experimental – “libunifex”
    https://github.com/facebookexperimental/libunifex

    View Slide

  121. Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 121/121
    [email protected]
    @bjorn_fahller
    @rollbear
    Björn Fahller
    Asynchronous I/O and coroutines for smooth data streaming

    View Slide