Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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).

Slide 8

Slide 8 text

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_; };

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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()

Slide 13

Slide 13 text

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()

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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)

Slide 19

Slide 19 text

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);

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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.

Slide 22

Slide 22 text

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);

Slide 23

Slide 23 text

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_; };

Slide 24

Slide 24 text

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); }

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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!

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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);

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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);

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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; };

Slide 61

Slide 61 text

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!

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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.

Slide 64

Slide 64 text

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.

Slide 65

Slide 65 text

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>;

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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.

Slide 72

Slide 72 text

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 };     } };

Slide 73

Slide 73 text

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;

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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.

Slide 78

Slide 78 text

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 };     } };

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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; } } }

Slide 81

Slide 81 text

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.

Slide 82

Slide 82 text

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); } }

Slide 83

Slide 83 text

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); } }

Slide 84

Slide 84 text

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); } }

Slide 85

Slide 85 text

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); } }

Slide 86

Slide 86 text

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); } }

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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.

Slide 96

Slide 96 text

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); …

Slide 97

Slide 97 text

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); …

Slide 98

Slide 98 text

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; } }

Slide 99

Slide 99 text

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); …

Slide 100

Slide 100 text

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()}; } }

Slide 101

Slide 101 text

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); …

Slide 102

Slide 102 text

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; } }

Slide 103

Slide 103 text

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); …

Slide 104

Slide 104 text

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; } } }

Slide 105

Slide 105 text

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); …

Slide 106

Slide 106 text

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"; } }

Slide 107

Slide 107 text

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(); } }

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

Asynchronous I/O and coroutines – NDC{TechTown} 2021 © Björn Fahller @bjorn_fahller 111/121 Conclusions ● Lack of library support is a major headache

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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;

Slide 114

Slide 114 text

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) { …

Slide 115

Slide 115 text

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) { …

Slide 116

Slide 116 text

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) { …

Slide 117

Slide 117 text

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) { …

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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