netcan / asyncio

asyncio is a c++20 library to write concurrent code using the async/await syntax.
MIT License
812 stars 80 forks source link

OSX compilation troubles #12

Closed Be3y4uu-K0T closed 1 year ago

Be3y4uu-K0T commented 1 year ago

Hello,

nikita@MacBook-Air-Nikita ~ % sw_vers
ProductName:            macOS
ProductVersion:         13.1
BuildVersion:           22C65

while I was building asyncio I have faced with many issues:

In file included from /Users/nikita/Desktop/test/asyncio/src/event_loop.cpp:6:
/Users/nikita/Desktop/test/asyncio/include/asyncio/event_loop.h: In member function 'constexpr void asyncio::EventLoop::WaitEventAwaiter::await_suspend(std::__n4861::coroutine_handle<_Promise>)':
/Users/nikita/Desktop/test/asyncio/include/asyncio/event_loop.h:60:27: error: 'using Selector = struct asyncio::KQueueSelector' {aka 'struct asyncio::KQueueSelector'} has no member named 'register_event'
   60 |                 selector_.register_event(event_);
      |                           ^~~~~~~~~~~~~~
/Users/nikita/Desktop/test/asyncio/include/asyncio/event_loop.h: In member function 'void asyncio::EventLoop::WaitEventAwaiter::destroy()':
/Users/nikita/Desktop/test/asyncio/include/asyncio/event_loop.h:70:27: error: 'using Selector = struct asyncio::KQueueSelector' {aka 'struct asyncio::KQueueSelector'} has no member named 'remove_event'
   70 |                 selector_.remove_event(event_);
      |                           ^~~~~~~~~~~~
/Users/nikita/Desktop/test/asyncio/include/asyncio/event_loop.h: In member function 'bool asyncio::EventLoop::is_stop()':
/Users/nikita/Desktop/test/asyncio/include/asyncio/event_loop.h:93:65: error: 'using Selector = struct asyncio::KQueueSelector' {aka 'struct asyncio::KQueueSelector'} has no member named 'is_stop'
   93 |         return schedule_.empty() && ready_.empty() && selector_.is_stop();
      |                

In asyncio/include/asyncio/selector/kqueue_selector.h I finished KQueueSelector:

#ifndef ASYNCIO_KQUEUE_SELECTOR_H
#define ASYNCIO_KQUEUE_SELECTOR_H
#include <asyncio/asyncio_ns.h>
#include <asyncio/selector/event.h>
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <unistd.h>
#include <cerrno>
#include <cstdio>
#include <vector>
#include <chrono>
#include <ranges>
#include <memory>

using namespace std::chrono;

ASYNCIO_NS_BEGIN
struct KQueueSelector {
    KQueueSelector(): kq_(kqueue()) {
        if (kq_ < 0) {
            perror("kqueue create");
            throw;
        }
    }

    std::vector<Event> select(int timeout /* ms */) {
        errno = 0;
        auto nsec = duration_cast<nanoseconds>(milliseconds{ timeout });
        const auto sec = duration_cast<seconds>(nsec);
        const timespec ts {
            .tv_sec = sec.count(),
            .tv_nsec = (nsec - sec).count(),
        };
        std::vector<struct kevent> events(register_event_count_);
        int ndfs = kevent(kq_, nullptr, 0, events.data(), register_event_count_, timeout < 0 ? nullptr : &ts);
        if (ndfs < 0)
            throw std::system_error { errno, std::system_category(), "kevent::select" };

        if (!ndfs)
            return {}; // return empty std::vector<Event>

        std::vector<Event> result;
        if (ndfs >= 100) [[unlikely]] { // FIXME: Is there a needed?
            result.reserve(ndfs);
        }
        for (auto event : events | std::views::take(ndfs)) {
            auto handle_info = reinterpret_cast<HandleInfo*>(event.udata);
            if (handle_info->handle != nullptr && handle_info->handle != (Handle*)&handle_info->handle) {
                result.emplace_back(Event {
                    .handle_info = *handle_info
                });
            } else {
                // mark event ready, but has no response callback
                handle_info->handle = (Handle*)&handle_info->handle;
            }
        }
        return result;
    }

    ~KQueueSelector() {
        if (kq_ > 0) {
            close(kq_);
        }
    }

    bool is_stop() const {
        return register_event_count_ == 1;
    }

    void register_event(const Event& event) {
        struct kevent ev {
            .ident = static_cast<uintptr_t>(event.fd),
            .filter = static_cast<int16_t>(event.flags),
            .flags = EV_ADD | EV_ENABLE,
            .udata = const_cast<HandleInfo*>(&event.handle_info)
        };
        if (!kevent(kq_, &ev, 1, nullptr, 0, nullptr)) {
            ++register_event_count_;
        }
    }

    void remove_event(const Event& event) {
        struct kevent ev {
            .ident = static_cast<uintptr_t>(event.fd),
            .filter = static_cast<int16_t>(event.flags),
            .flags = EV_DELETE | EV_DISABLE,
            .udata = nullptr
        };
        if (!kevent(kq_, &ev, 1, nullptr, 0, nullptr)) {
            --register_event_count_;
        }
    }

private:
    int kq_;
    int register_event_count_ { 1 }; // FIXME: Is there a needed? With zero (0) faster than one (1).
    /* "When Fa nevents is zero, kevent ();
will return immediately even if there is a Fa timeout specified unlike [select](https://www.opennet.ru/cgi-bin/opennet/man.cgi?topic=select&category=2)(2)."
    https://www.opennet.ru/man.shtml?topic=kevent
    */
};

ASYNCIO_NS_END
#endif //ASYNCIO_KQUEUE_SELECTOR_H

I replaced this std::vector<struct kevent> events_; on int register_event_count_ { 1 }; because I don't know why this need

Then I have faced with many issues again:

In file included from /Users/nikita/Desktop/test/asyncio/include/asyncio/open_connection.h:8,
                 from /Users/nikita/Desktop/test/asyncio/test/st/echo_client.cpp:4:
/Users/nikita/Desktop/test/asyncio/include/asyncio/stream.h:98:48: error: 'EPOLLIN' was not declared in this scope; did you mean 'POLL_IN'?
   98 |     Event read_ev_ { .fd = read_fd_, .events = EPOLLIN };
      |                                                ^~~~~~~
      |                                                POLL_IN
/Users/nikita/Desktop/test/asyncio/include/asyncio/stream.h:99:50: error: 'EPOLLOUT' was not declared in this scope; did you mean 'POLL_OUT'?
   99 |     Event write_ev_ { .fd = write_fd_, .events = EPOLLOUT };
      |                                                  ^~~~~~~~
      |                                                  POLL_OUT
/Users/nikita/Desktop/test/asyncio/include/asyncio/open_connection.h: In function 'asyncio::Task<bool> asyncio::detail::connect(int, const sockaddr*, socklen_t)':
/Users/nikita/Desktop/test/asyncio/include/asyncio/open_connection.h:26:36: error: 'EPOLLOUT' was not declared in this scope; did you mean 'POLL_OUT'?
   26 |     Event ev { .fd = fd, .events = EPOLLOUT };
      |                                    ^~~~~~~~
      |                                    POLL_OUT
In file included from /Users/nikita/Desktop/test/asyncio/include/asyncio/start_server.h:9,
                 from /Users/nikita/Desktop/test/asyncio/test/ut/task_test.cpp:14:
/Users/nikita/Desktop/test/asyncio/include/asyncio/stream.h:98:48: error: 'EPOLLIN' was not declared in this scope; did you mean 'POLL_IN'?
   98 |     Event read_ev_ { .fd = read_fd_, .events = EPOLLIN };
      |                                                ^~~~~~~
      |                                                POLL_IN
/Users/nikita/Desktop/test/asyncio/include/asyncio/stream.h:99:50: error: 'EPOLLOUT' was not declared in this scope; did you mean 'POLL_OUT'?
   99 |     Event write_ev_ { .fd = write_fd_, .events = EPOLLOUT };
      |                                                  ^~~~~~~~
      |                                                  POLL_OUT
In file included from /Users/nikita/Desktop/test/asyncio/test/st/echo_server.cpp:1:
/Users/nikita/Desktop/test/asyncio/include/asyncio/stream.h:98:48: error: 'EPOLLIN' was not declared in this scope; did you mean 'POLL_IN'?
   98 |     Event read_ev_ { .fd = read_fd_, .events = EPOLLIN };
      |                                                ^~~~~~~
      |                                                POLL_IN
/Users/nikita/Desktop/test/asyncio/include/asyncio/stream.h:99:50: error: 'EPOLLOUT' was not declared in this scope; did you mean 'POLL_OUT'?
   99 |     Event write_ev_ { .fd = write_fd_, .events = EPOLLOUT };
      |                                                  ^~~~~~~~
      |                                                  POLL_OUT
/Users/nikita/Desktop/test/asyncio/include/asyncio/start_server.h: In member function 'asyncio::Task<> asyncio::Server<CONNECT_CB>::serve_forever()':
/Users/nikita/Desktop/test/asyncio/include/asyncio/start_server.h:33:41: error: 'EPOLLIN' was not declared in this scope; did you mean 'POLL_IN'?
   33 |         Event ev { .fd = fd_, .events = EPOLLIN };
      |                                         ^~~~~~~
      |                                         POLL_IN
In file included from /Users/nikita/Desktop/test/asyncio/test/st/echo_server.cpp:4:
/Users/nikita/Desktop/test/asyncio/include/asyncio/start_server.h: In member function 'asyncio::Task<> asyncio::Server<CONNECT_CB>::serve_forever()':
/Users/nikita/Desktop/test/asyncio/include/asyncio/start_server.h:33:41: error: 'EPOLLIN' was not declared in this scope; did you mean 'POLL_IN'?
   33 |         Event ev { .fd = fd_, .events = EPOLLIN };
      |                                         ^~~~~~~
      |                                         POLL_IN
In file included from /Users/nikita/Desktop/test/asyncio/test/ut/task_test.cpp:15:
/Users/nikita/Desktop/test/asyncio/include/asyncio/open_connection.h: In function 'asyncio::Task<bool> asyncio::detail::connect(int, const sockaddr*, socklen_t)':
/Users/nikita/Desktop/test/asyncio/include/asyncio/open_connection.h:26:36: error: 'EPOLLOUT' was not declared in this scope; did you mean 'POLL_OUT'?
   26 |     Event ev { .fd = fd, .events = EPOLLOUT };
      |                                    ^~~~~~~~
      |                                    POLL_OUT

For MacOS we need to use EVFILT_READ/EVFILT_WRITE instead of EPOLLIN/EPOLLOUT. In asyncio/include/asyncio/selector/event.h I added enum class Flags: int16_t and renamed events to flags:

#ifndef ASYNCIO_EVENT_H
#define ASYNCIO_EVENT_H

#include <asyncio/asyncio_ns.h>
#include <asyncio/handle.h>
#include <cstdint>

#if defined(__APPLE__)
    #include <sys/event.h>
#elif defined(__linux__)
    #include <sys/epoll.h>
#else
    #error "Support only Linux & MacOS!"
#endif

ASYNCIO_NS_BEGIN
struct Event {
    enum class Flags: int16_t {
    #if defined(__APPLE__)
        EVENT_READ = EVFILT_READ,
        EVENT_WRITE = EVFILT_WRITE
    #elif defined(__linux__)
        EVENT_READ = EPOLLIN,
        EVENT_WRITE = EPOLLOUT
    #else
        #error "Support only Linux & MacOS!"
    #endif
    };

    int fd;
    Flags flags;
    HandleInfo handle_info;
};
ASYNCIO_NS_END

#endif //ASYNCIO_EVENT_H

Then replace events to flags in:

And the last issues:

In file included from /Users/nikita/Desktop/test/asyncio/test/st/echo_client.cpp:4:
/Users/nikita/Desktop/test/asyncio/include/asyncio/open_connection.h: In function 'asyncio::Task<asyncio::Stream> asyncio::open_connection(std::string_view, uint16_t)':
/Users/nikita/Desktop/test/asyncio/include/asyncio/open_connection.h:54:62: error: 'SOCK_NONBLOCK' was not declared in this scope; did you mean 'O_NONBLOCK'?
   54 |         if ( (sockfd = socket(p->ai_family, p->ai_socktype | SOCK_NONBLOCK, p->ai_protocol)) == -1) {
      |                                                              ^~~~~~~~~~~~~
      |                                                              O_NONBLOCK
In file included from /Users/nikita/Desktop/test/asyncio/test/st/echo_server.cpp:4:
/Users/nikita/Desktop/test/asyncio/include/asyncio/start_server.h: In function 'asyncio::Task<asyncio::Server<CONNECT_CB> > asyncio::start_server(CONNECT_CB, std::string_view, uint16_t)':
/Users/nikita/Desktop/test/asyncio/include/asyncio/start_server.h:87:64: error: 'SOCK_NONBLOCK' was not declared in this scope; did you mean 'SIG_UNBLOCK'?
   87 |         if ( (serverfd = socket(p->ai_family, p->ai_socktype | SOCK_NONBLOCK, p->ai_protocol)) == -1) {
      |                                                                ^~~~~~~~~~~~~
      |                                                                SIG_UNBLOCK
In file included from /Users/nikita/Desktop/test/asyncio/test/ut/task_test.cpp:14:
/Users/nikita/Desktop/test/asyncio/include/asyncio/start_server.h: In function 'asyncio::Task<asyncio::Server<CONNECT_CB> > asyncio::start_server(CONNECT_CB, std::string_view, uint16_t)':
/Users/nikita/Desktop/test/asyncio/include/asyncio/start_server.h:87:64: error: 'SOCK_NONBLOCK' was not declared in this scope; did you mean 'O_NONBLOCK'?
   87 |         if ( (serverfd = socket(p->ai_family, p->ai_socktype | SOCK_NONBLOCK, p->ai_protocol)) == -1) {
      |                                                                ^~~~~~~~~~~~~
      |                                                                O_NONBLOCK
In file included from /Users/nikita/Desktop/test/asyncio/test/ut/task_test.cpp:15:
/Users/nikita/Desktop/test/asyncio/include/asyncio/open_connection.h: In function 'asyncio::Task<asyncio::Stream> asyncio::open_connection(std::string_view, uint16_t)':
/Users/nikita/Desktop/test/asyncio/include/asyncio/open_connection.h:54:62: error: 'SOCK_NONBLOCK' was not declared in this scope; did you mean 'O_NONBLOCK'?
   54 |         if ( (sockfd = socket(p->ai_family, p->ai_socktype | SOCK_NONBLOCK, p->ai_protocol)) == -1) {
      |                                                              ^~~~~~~~~~~~~
      |                                                              O_NONBLOCK

MacOS not supported SOCK_NONBLOCK, then I added into asyncio/include/asyncio/stream.h:

...

#if defined(__APPLE__) || defined(__CYGWIN__) || defined(__NetBSD__)
    #ifndef SOCK_NONBLOCK /* __APPLE__: Protocol not supported */
        #define SOCK_NONBLOCK 0
    #endif
    #include <sys/ioctl.h>
    #include <fcntl.h>
#endif

ASYNCIO_NS_BEGIN
namespace socket {
    // Redesign python method `socket.setblocking(bool)`:
    // https://github.com/python/cpython/blob/928752ce4c23f47d3175dd47ecacf08d86a99c9d/Modules/socketmodule.c#L683
    // https://stackoverflow.com/a/1549344/14070318
    bool set_blocking(int fd, bool blocking) {
        if (fd < 0)
            return false;
    #if SOCK_NONBLOCK != 0
        return true;
    #elif defined(_WIN32)
        unsigned long block = !blocking;
        return !ioctlsocket(fd, FIONBIO, &block);
    #elif __has_include(<sys/ioctl.h>) && defined(FIONBIO)
        unsigned int block = !blocking;
        return !ioctl(fd, FIONBIO, &block);
    #else
        int delay_flag, new_delay_flag;
        delay_flag = fcntl(fd, F_GETFL, 0);
        if (delay_flag == -1)
            return false;
        new_delay_flag = blocking ? (delay_flag & ~O_NONBLOCK) : (delay_flag | O_NONBLOCK);
        if (new_delay_flag != delay_flag)
            return !fcntl(fd, F_SETFL, new_delay_flag);
        else
            return false;
    #endif
    }
}

...
nikita@MacBook-Air-Nikita build % CXX=g++-12 CXXFLAGS=-I/opt/homebrew/Cellar/gcc/12.2.0/include/\ -I/opt/homebrew/Cellar/fmt/9.1.0/include\ -L/opt/homebrew/Cellar/fmt/9.1.0/lib\ -lfmt cmake -DCMAKE_SYSROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk/ ..
-- The CXX compiler identification is GNU 12.2.0
-- Checking whether CXX compiler has -isysroot
-- Checking whether CXX compiler has -isysroot - yes
-- Checking whether CXX compiler supports OSX deployment target flag
-- Checking whether CXX compiler supports OSX deployment target flag - yes
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /opt/homebrew/bin/g++-12 - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMAKE_BUILD_TYPE=
-- Module support is disabled.
-- Version: 8.0.1
-- Build type: 
-- CXX_STANDARD: 20
-- Performing Test has_std_20_flag
-- Performing Test has_std_20_flag - Success
-- Performing Test has_std_2a_flag
-- Performing Test has_std_2a_flag - Success
-- Performing Test SUPPORTS_USER_DEFINED_LITERALS
-- Performing Test SUPPORTS_USER_DEFINED_LITERALS - Success
-- Performing Test FMT_HAS_VARIANT
-- Performing Test FMT_HAS_VARIANT - Success
-- Required features: cxx_variadic_templates
-- Looking for strtod_l
-- Looking for strtod_l - found
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/nikita/Desktop/test/asyncio/build
nikita@MacBook-Air-Nikita build % make -j
[  2%] Built target nanobench
[  5%] Built target fmt
[  6%] Building CXX object CMakeFiles/asyncio.dir/src/event_loop.cpp.o
[ 83%] Built target Catch2
[ 85%] Built target Catch2WithMain
[ 86%] Linking CXX shared library libasyncio.dylib
[ 86%] Built target asyncio
[ 87%] Building CXX object test/pt/CMakeFiles/sched_test.dir/sched_test.cpp.o
[ 88%] Building CXX object test/st/CMakeFiles/hello_world.dir/hello_world.cpp.o
[ 89%] Building CXX object test/st/CMakeFiles/echo_client.dir/echo_client.cpp.o
[ 91%] Building CXX object test/st/CMakeFiles/echo_server.dir/echo_server.cpp.o
[ 92%] Building CXX object test/ut/CMakeFiles/asyncio_ut.dir/task_test.cpp.o
[ 93%] Building CXX object test/ut/CMakeFiles/asyncio_ut.dir/selector_test.cpp.o
[ 94%] Building CXX object test/ut/CMakeFiles/asyncio_ut.dir/result_test.cpp.o
[ 95%] Linking CXX executable hello_world
[ 96%] Linking CXX executable sched_test
[ 96%] Built target hello_world
[ 97%] Linking CXX executable echo_server
[ 97%] Built target sched_test
[ 97%] Built target echo_server
[ 98%] Linking CXX executable echo_client
[ 98%] Built target echo_client
[100%] Linking CXX executable asyncio_ut
[100%] Built target asyncio_ut
nikita@MacBook-Air-Nikita build % sudo make install
Password:
[  3%] Built target fmt
[  5%] Built target asyncio
[  7%] Built target hello_world
[ 10%] Built target echo_client
[ 12%] Built target echo_server
[ 14%] Built target nanobench
[ 91%] Built target Catch2
[ 93%] Built target Catch2WithMain
[ 95%] Built target sched_test
[100%] Built target asyncio_ut
Install the project...
-- Install configuration: ""
CMake Error at cmake_install.cmake:59 (file):
  file cannot create directory: /usr/include/asyncio.  Maybe need
  administrative privileges.

make: *** [install] Error 1

I tried to compile from the command line.

nikita@MacBook-Air-Nikita build % brew install fmt
nikita@MacBook-Air-Nikita test % g++-12 -fdiagnostics-color=always -c asyncio/src/event_loop.cpp -o asyncio/src/event_loop.o --sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk/ -I asyncio/include -I/opt/homebrew/Cellar/gcc/12.2.0/include/ -I/opt/homebrew/Cellar/fmt/9.1.0/include -L/opt/homebrew/Cellar/fmt/9.1.0/lib -lfmt --std=c++20 -O3
nikita@MacBook-Air-Nikita test % ar rvs asyncio/src/event_loop.a asyncio/src/event_loop.o
ar: creating archive asyncio/src/event_loop.a
a - asyncio/src/event_loop.o
nikita@MacBook-Air-Nikita test % g++-12 -fdiagnostics-color=always -g test1.cpp -o test1 asyncio/src/event_loop.a --sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk/ -I asyncio/include -I/opt/homebrew/Cellar/gcc/12.2.0/include/ -I/opt/homebrew/Cellar/fmt/9.1.0/include -L/opt/homebrew/Cellar/fmt/9.1.0/lib -lfmt --std=c++20 -O3
Undefined symbols for architecture arm64:
  "__ZN3fmt2v97vformatB5cxx11ENS0_17basic_string_viewIcEENS0_17basic_format_argsINS0_20basic_format_contextINS0_8appenderEcEEEE", referenced from:
      __ZNK7asyncio4TaskIiE12promise_type14dump_backtraceEm in ccM2pz6n.o
      __ZNK7asyncio4TaskIbE12promise_type14dump_backtraceEm in ccM2pz6n.o
ld: symbol(s) not found for architecture arm64
collect2: error: ld returned 1 exit status

I just added into asyncio/include/asyncio/event_loop.h (link):

#define FMT_HEADER_ONLY
#include <fmt/core.h>
#include <fmt/format.h>
nikita@MacBook-Air-Nikita test % g++-12 -fdiagnostics-color=always -c asyncio/src/event_loop.cpp -o asyncio/src/event_loop.o --sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk/ -I asyncio/include -I/opt/homebrew/Cellar/gcc/12.2.0/include/ -I/opt/homebrew/Cellar/fmt/9.1.0/include -L/opt/homebrew/Cellar/fmt/9.1.0/lib -lfmt --std=c++20 -O3
nikita@MacBook-Air-Nikita test % ar rvs asyncio/src/event_loop.a asyncio/src/event_loop.o 
r - asyncio/src/event_loop.o
nikita@MacBook-Air-Nikita test % g++-12 -fdiagnostics-color=always -g test1.cpp -o test1 asyncio/src/event_loop.a --sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk/ -I asyncio/include -I/opt/homebrew/Cellar/gcc/12.2.0/include/ -I/opt/homebrew/Cellar/fmt/9.1.0/include -L/opt/homebrew/Cellar/fmt/9.1.0/lib -lfmt --std=c++20 -O3
nikita@MacBook-Air-Nikita test % ./test1
true

All works!

Test 1

// test1.cpp
#include <iostream>
#include <asyncio/event_loop.h>
#include <asyncio/runner.h>
#include <asyncio/task.h>

using asyncio::Task;

auto main() -> int {
    auto completes_synchronously = []() -> Task<int> {
        co_return 1;
    };

    auto main = [&]() -> Task<bool> {
        int sum = 0;
        for (int i = 0; i < 1'000'000; ++i) {
            sum += co_await completes_synchronously();
        }
        co_return sum == 1'000'000;
    };

    std::cout << std::boolalpha << asyncio::run(main()) << std::endl;
    return 0;
}
Test 2 ```c++ // test2.cpp #include #include #include #include #include using asyncio::Task; auto main() -> int { auto completes_synchronously = []() -> Task { co_return 1; }; auto main = [&]() -> Task { int sum = 0; for (int i = 0; i < 1'000'000; ++i) { sum += co_await completes_synchronously(); } co_return sum == 1'000'000; }; std::cout << std::boolalpha << asyncio::run([&]() -> Task { auto&& [R1, R2, R3, R4, R5, R6, R7, R8] = co_await asyncio::gather( main(), main(), main(), main(), main(), main(), main(), main() ); std::vector result { R1, R2, R3 }; co_return std::all_of(result.begin(), result.end(), [](auto element){ return !!element; }); }()) << std::endl; return 0; } ```
Test 3 ```c++ // test3.cpp #include #include #include #include #include #include #include #include #include using namespace asyncio; Task hello() { co_return "hello"; } Task world() { co_return "world"; } Task hello_world() { co_return fmt::format("{} {}", co_await hello(), co_await world()); } int main() { fmt::print("run result: {}\n", asyncio::run(hello_world())); return 0; } ```
Test 4 ```c++ // test4_echo_server.cpp #include #include #include #include #include #include #include using asyncio::Stream; using asyncio::Task; using asyncio::get_in_port; using asyncio::get_in_addr; int add_count = 0; int rel_count = 0; Task<> handle_echo(Stream stream) { auto sockinfo = stream.get_sock_info(); char addr[INET6_ADDRSTRLEN] {}; auto sa = reinterpret_cast(&sockinfo); ++add_count; fmt::print("connections: {}/{}\n", rel_count, add_count); while (true) { try { auto data = co_await stream.read(200); if (data.empty()) { break; } fmt::print("Received: '{}' from '{}:{}'\n", data.data(), inet_ntop(sockinfo.ss_family, get_in_addr(sa), addr, sizeof addr), get_in_port(sa)); co_await stream.write(data); } catch (...) { break; } } ++rel_count; fmt::print("connections: {}/{}\n", rel_count, add_count); stream.close(); } Task<> echo_server() { auto server = co_await asyncio::start_server( handle_echo, "127.0.0.1", 8888); fmt::print("Serving on 127.0.0.1:8888\n"); co_await server.serve_forever(); } int main() { asyncio::run(echo_server()); return 0; } ``` ```c++ // test4_echo_client.cpp #include #include #include #include using asyncio::Task; using asyncio::Stream; using namespace std::chrono; Task<> tcp_echo_client(std::string_view message) { auto stream = co_await asyncio::open_connection("127.0.0.1", 8888); fmt::print("Send: '{}'\n", message); co_await stream.write(Stream::Buffer(message.begin(), message.end() + 1 /* plus '\0' */)); auto data = co_await asyncio::wait_for(stream.read(100), 300ms); fmt::print("Received: '{}'\n", data.data()); fmt::print("Close the connection\n"); stream.close(); } int main(int argc, char** argv) { asyncio::run(tcp_echo_client("hello world!")); return 0; } ```
Test 5 ```c++ // test5.cpp #include #include #include using namespace ASYNCIO_NS; using namespace std::chrono; int main() { EventLoop loop; Selector selector; auto before_wait = loop.time(); selector.select(300); // Without auto after_wait = loop.time(); std::cout << std::boolalpha << (after_wait - before_wait >= 300ms) << std::endl; return 0; } ```
Test 6 ```c++ // test6.cpp #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ASYNCIO_NS; using namespace std::chrono_literals; template struct dump; template Task<> coro_depth_n(std::vector& result) { result.push_back(N); if constexpr (N > 0) { co_await coro_depth_n(result); result.push_back(N * 10); } } auto test_Task_await() { std::vector result; { asyncio::run(coro_depth_n<0>(result)); std::vector expected { 0 }; std::cout << std::boolalpha << "simple await: " << (result == expected) << std::endl; result.clear(); } { asyncio::run(coro_depth_n<1>(result)); std::vector expected { 1, 0, 10 }; std::cout << std::boolalpha << "nest await: " << (result == expected) << std::endl; result.clear(); } { asyncio::run(coro_depth_n<2>(result)); std::vector expected { 2, 1, 0, 10, 20 }; std::cout << std::boolalpha << "2 depth await: " << (result == expected) << std::endl; result.clear(); } { asyncio::run(coro_depth_n<3>(result)); std::vector expected { 3, 2, 1, 0, 10, 20, 30 }; std::cout << std::boolalpha << "3 depth await: " << (result == expected) << std::endl; result.clear(); } { asyncio::run(coro_depth_n<4>(result)); std::vector expected { 4, 3, 2, 1, 0, 10, 20, 30, 40 }; std::cout << std::boolalpha << "4 depth await: " << (result == expected) << std::endl; result.clear(); } } Task square(int64_t x) { co_return x * x; } auto Task_test() { { bool called {false}; asyncio::run([&]() -> Task<> { auto t = square(5); auto tt = std::move(t); std::cout << std::boolalpha << "co_await empty task<>: " << !t.valid() << std::endl; std::cout << std::boolalpha << "co_await empty task<>: " << tt.valid() << std::endl; try { co_await t; std::cout << "co_await empty task<>: " << "no exc (bad)" << std::endl; } catch(InvalidFuture const&) { std::cout << "co_await empty task<>: " << "good exc" << std::endl; } catch(...) { std::cout << "co_await empty task<>: " << "bad exc" << std:: endl; } called = true; }()); std::cout << std::boolalpha << "co_await empty task<>: " << called << std::endl; } } auto test_Task_await_result_value() { { auto square_sum = [&](int x, int y) -> Task { auto tx = square(x); auto x2 = co_await tx; auto y2 = co_await square(y); co_return x2 + y2; }; std::cout << std::boolalpha << "square_sum 3, 4: " << (asyncio::run(square_sum(3, 4)) == 25) << std::endl; } { std::function Task> fibo = [&](size_t n) -> Task { if (n <= 1) { co_return n; } co_return co_await fibo(n - 1) + co_await fibo(n - 2); }; std::cout << std::boolalpha << "fibonacci: " << (asyncio::run(fibo(0)) == 0) << std::endl; std::cout << std::boolalpha << "fibonacci: " << (asyncio::run(fibo(1)) == 1) << std::endl; std::cout << std::boolalpha << "fibonacci: " << (asyncio::run(fibo(2)) == 1) << std::endl; std::cout << std::boolalpha << "fibonacci: " << (asyncio::run(fibo(12)) == 144) << std::endl; } } auto test_Task_for_loop() { auto sequense = [](int64_t n) -> Task { int64_t result = 1; int64_t sign = -1; for (size_t i = 2; i <= n; ++i) { result += (co_await square(i)) * sign; sign *= -1; } co_return result; }; std::cout << std::boolalpha << "test_Task_for_loop: " << (asyncio::run(sequense(1)) == 1) << std::endl; std::cout << std::boolalpha << "test_Task_for_loop: " << (asyncio::run(sequense(10)) == -55) << std::endl; std::cout << std::boolalpha << "test_Task_for_loop: " << (asyncio::run(sequense(100)) == -5050) << std::endl; std::cout << std::boolalpha << "test_Task_for_loop: " << (asyncio::run(sequense(100000)) == -5000050000) << std::endl; } auto test_schedule_task() { bool called{false}; auto f = [&]() -> Task { called = true; co_return 0xababcaab; }; { asyncio::run([&]() -> Task<> { auto handle = asyncio::schedule_task(f()); co_return; }()); std::cout << std::boolalpha << "run and detach created task: " << !called << std::endl; } { asyncio::run([&]() -> Task<> { auto handle = asyncio::schedule_task(f()); std::cout << std::boolalpha << "run and await created task: " << (co_await handle == 0xababcaab) << std::endl; std::cout << std::boolalpha << "run and await created task: " << (co_await handle == 0xababcaab) << std::endl; }()); std::cout << std::boolalpha << "run and await created task: " << called << std::endl; } { asyncio::run([&]() -> Task<> { auto handle = asyncio::schedule_task(f()); handle.cancel(); try { co_await handle; std::cout << "cancel and await created task: " << "no exc (bad)" << std::endl; } catch(InvalidFuture const&) { std::cout << "cancel and await created task: " << "good exc" << std::endl; } catch(...) { std::cout << "cancel and await created task: " << "bad exc" << std:: endl; } }()); } } auto int_div(int a, int b) -> Task { if (b == 0) { throw std::overflow_error("b is 0!"); } co_return a / b; }; auto test_exception() { std::cout << std::boolalpha << "test_exception: " << ( std::abs(asyncio::run(int_div(4, 2)) - 2.0) <= std::numeric_limits::epsilon() ) << std::endl; try { asyncio::run(int_div(4, 0)); std::cout << "test_exception: " << "no exc (bad)" << std::endl; } catch(std::overflow_error const&) { std::cout << "test_exception: " << "good exc" << std::endl; } catch(...) { std::cout << "test_exception: " << "bad exc" << std:: endl; } } auto test_gather() { bool is_called = false; auto factorial = [&](std::string_view name, int number) -> Task { int r = 1; for (int i = 2; i <= number; ++i) { fmt::print("Task {}: Compute factorial({}), currently i={}...\n", name, number, i); co_await asyncio::sleep(0.1s); r *= i; } fmt::print("Task {}: factorial({}) = {}\n", name, number, r); co_return r; }; auto test_void_func = []() -> Task<> { fmt::print("this is a void value\n"); co_return; }; { std::cout << std::boolalpha << "test lvalue & rvalue gather: " << !is_called << std::endl; asyncio::run([&]() -> Task<> { auto fac_lvalue = factorial("A", 2); auto fac_xvalue = factorial("B", 3); auto&& fac_rvalue = factorial("C", 4); { auto&& [a, b, c, _void] = co_await asyncio::gather( fac_lvalue, static_cast&&>(fac_xvalue), std::move(fac_rvalue), test_void_func() ); std::cout << std::boolalpha << "test lvalue & rvalue gather: " << (a == 2) << std::endl; std::cout << std::boolalpha << "test lvalue & rvalue gather: " << (b == 6) << std::endl; std::cout << std::boolalpha << "test lvalue & rvalue gather: " << (c == 24) << std::endl; } std::cout << std::boolalpha << "test lvalue & rvalue gather: " << ((co_await fac_lvalue) == 2) << std::endl; std::cout << std::boolalpha << "test lvalue & rvalue gather: " << !fac_xvalue.valid() << std::endl; // be moved std::cout << std::boolalpha << "test lvalue & rvalue gather: " << !fac_rvalue.valid() << std::endl; // be moved is_called = true; }()); std::cout << std::boolalpha << "test lvalue & rvalue gather: " << is_called << std::endl; is_called = false; } { std::cout << std::boolalpha << "test gather of gather: " << !is_called << std::endl; asyncio::run([&]() -> Task<> { auto&& [ab, c, _void] = co_await asyncio::gather( gather(factorial("A", 2), factorial("B", 3)), factorial("C", 4), test_void_func() ); auto&& [a, b] = ab; std::cout << std::boolalpha << "test gather of gather: " << (a == 2) << std::endl; std::cout << std::boolalpha << "test gather of gather: " << (b == 6) << std::endl; std::cout << std::boolalpha << "test gather of gather: " << (c == 24) << std::endl; is_called = true; }()); std::cout << std::boolalpha << "test gather of gather: " << is_called << std::endl; is_called = false; } { std::cout << std::boolalpha << "test detach gather: " << !is_called << std::endl; auto res = asyncio::gather( factorial("A", 2), factorial("B", 3) ); asyncio::run([&]() -> Task<> { auto&& [a, b] = co_await std::move(res); std::cout << std::boolalpha << "test detach gather: " << (a == 2) << std::endl; std::cout << std::boolalpha << "test detach gather: " << (b == 6) << std::endl; is_called = true; }()); std::cout << std::boolalpha << "test detach gather: " << is_called << std::endl; is_called = false; } { std::cout << std::boolalpha << "test exception gather: " << !is_called << std::endl; try { asyncio::run([&]() -> Task> { is_called = true; co_return co_await asyncio::gather( int_div(4, 0), factorial("B", 3) ); }()); std::cout << "test exception gather: " << "no exc (bad)" << std::endl; } catch(std::overflow_error const&) { std::cout << "test exception gather: " << "good exc" << std::endl; } catch(...) { std::cout << "test exception gather: " << "bad exc" << std:: endl; } std::cout << std::boolalpha << "test exception gather: " << is_called << std::endl; is_called = false; } } auto test_sleep() { size_t call_time = 0; auto say_after = [&](auto delay, std::string_view what) -> Task<> { co_await asyncio::sleep(delay); fmt::print("{}\n", what); ++call_time; }; { call_time = 0; auto async_main = [&]() -> Task<> { auto task1 = schedule_task(say_after(100ms, "hello")); auto task2 = schedule_task(say_after(200ms, "world")); co_await task1; co_await task2; }; auto before_wait = get_event_loop().time(); asyncio::run(async_main()); auto after_wait = get_event_loop().time(); auto diff = after_wait - before_wait; std::cout << std::boolalpha << "schedule sleep and await: " << (diff >= 200ms) << std::endl; std::cout << std::boolalpha << "schedule sleep and await: " << (diff < 300ms) << std::endl; std::cout << std::boolalpha << "schedule sleep and await: " << (call_time == 2) << std::endl; } { call_time = 0; auto async_main = [&]() -> Task<> { auto task1 = schedule_task(say_after(100ms, "hello")); auto task2 = schedule_task(say_after(200ms, "world")); co_await task1; task2.cancel(); }; auto before_wait = get_event_loop().time(); asyncio::run(async_main()); auto after_wait = get_event_loop().time(); auto diff = after_wait - before_wait; std::cout << std::boolalpha << "schedule sleep and cancel: " << (diff >= 100ms) << std::endl; std::cout << std::boolalpha << "schedule sleep and cancel: " << (diff < 200ms) << std::endl; std::cout << std::boolalpha << "schedule sleep and cancel: " << (call_time == 1) << std::endl; } { call_time = 0; auto async_main = [&]() -> Task<> { auto task1 = schedule_task(say_after(100ms, "hello")); auto task2 = schedule_task(say_after(200ms, "world")); co_await task1; task2.cancel(); // delay 300ms to exit co_await asyncio::sleep(200ms); }; auto before_wait = get_event_loop().time(); asyncio::run(async_main()); auto after_wait = get_event_loop().time(); auto diff = after_wait - before_wait; std::cout << std::boolalpha << "schedule sleep and cancel, delay exit: " << (diff >= 300ms) << std::endl; std::cout << std::boolalpha << "schedule sleep and cancel, delay exit: " << (diff < 400ms) << std::endl; std::cout << std::boolalpha << "schedule sleep and cancel, delay exit: " << (call_time == 1) << std::endl; } } auto cancel_a_infinite_loop_coroutine() { int count = 0; asyncio::run([&]() -> Task<>{ auto inf_loop = [&]() -> Task<> { while (true) { ++count; co_await asyncio::sleep(1ms); } }; auto task = schedule_task(inf_loop()); co_await asyncio::sleep(10ms); task.cancel(); }()); std::cout << std::boolalpha << "cancel_a_infinite_loop_coroutine: " << (count > 0) << std::endl; std::cout << std::boolalpha << "cancel_a_infinite_loop_coroutine: " << (count < 10) << std::endl; } auto test_timeout() { bool is_called = false; auto wait_duration = [&](auto duration) -> Task { co_await sleep(duration); fmt::print("wait_duration finished\n"); is_called = true; co_return 0xbabababc; }; auto wait_for_test = [&](auto duration, auto timeout) -> Task { co_return co_await wait_for(wait_duration(duration), timeout); }; { std::cout << std::boolalpha << "no timeout: " << !is_called << std::endl; std::cout << std::boolalpha << "no timeout: " << (asyncio::run(wait_for_test(12ms, 120ms)) == 0xbabababc) << std::endl; std::cout << std::boolalpha << "no timeout: " << is_called << std::endl; is_called = false; } { std::cout << std::boolalpha << "wait_for with sleep: " << !is_called << std::endl; auto wait_for_rvalue = wait_for(sleep(30ms), 50ms); asyncio::run([&]() -> Task<> { try { co_await std::move(wait_for_rvalue); std::cout << "wait_for with sleep: " << "no exc (good)" << std::endl; } catch (...) { std::cout << "wait_for with sleep: " << "bad exc" << std:: endl; } try { co_await wait_for(sleep(50ms), 30ms); std::cout << "wait_for with sleep: " << "no exc (bad)" << std::endl; } catch(TimeoutError const&) { std::cout << "wait_for with sleep: " << "good exc" << std::endl; } catch(...) { std::cout << "wait_for with sleep: " << "bad exc" << std:: endl; } is_called = true; }()); std::cout << std::boolalpha << "wait_for with sleep: " << is_called << std::endl; is_called = false; } { std::cout << std::boolalpha << "wait_for with gather: " << !is_called << std::endl; asyncio::run([&]() -> Task<> { try { co_await wait_for(gather(sleep(10ms), sleep(20ms), sleep(30ms)), 50ms); std::cout << "wait_for with gather: " << "no exc (good)" << std::endl; } catch (...) { std::cout << "wait_for with gather: " << "bad exc" << std:: endl; } try { co_await wait_for(gather(sleep(10ms), sleep(80ms), sleep(30ms)), 50ms); std::cout << "wait_for with gather: " << "no exc (bad)" << std::endl; } catch(TimeoutError const&) { std::cout << "wait_for with gather: " << "good exc" << std::endl; } catch(...) { std::cout << "wait_for with gather: " << "bad exc" << std:: endl; } is_called = true; }()); std::cout << std::boolalpha << "wait_for with gather: " << is_called << std::endl; is_called = false; } { try { asyncio::run([]() -> Task<> { auto v = co_await wait_for(int_div(5, 0), 100ms); }()); std::cout << "notime out with exception: " << "no exc (bad)" << std::endl; } catch(std::overflow_error const&) { std::cout << "notime out with exception: " << "good exc" << std::endl; } catch(...) { std::cout << "notime out with exception: " << "bad exc" << std:: endl; } } { std::cout << std::boolalpha << "timeout error: " << !is_called << std::endl; try { asyncio::run(wait_for_test(200ms, 100ms)); std::cout << "timeout error: " << "no exc (bad)" << std::endl; } catch(TimeoutError const&) { std::cout << "timeout error: " << "good exc" << std::endl; } catch(...) { std::cout << "timeout error: " << "bad exc" << std:: endl; } std::cout << std::boolalpha << "timeout error: " << !is_called << std::endl; } { asyncio::run([]() -> Task<> { co_await wait_for(std::suspend_always{}, 1s); co_await wait_for(std::suspend_never{}, 1s); }()); std::cout << std::boolalpha << "wait for awaitable: " << true << std::endl; } } auto echo_server__client() { bool is_called = false; constexpr std::string_view message = "hello world!"; asyncio::run([&]() -> Task<> { auto handle_echo = [&](Stream stream) -> Task<> { auto& sockinfo = stream.get_sock_info(); auto data = co_await stream.read(100); std::cout << std::boolalpha << "echo_server__client(handle_echo): " << (std::string_view{data.data()} == message) << std::endl; co_await stream.write(data); }; auto echo_server = [&]() -> Task<> { auto server = co_await asyncio::start_server( handle_echo, "127.0.0.1", 8888); co_await server.serve_forever(); }; auto echo_client = [&]() -> Task<> { auto stream = co_await asyncio::open_connection("127.0.0.1", 8888); co_await stream.write(Stream::Buffer(message.begin(), message.end())); auto data = co_await stream.read(100); std::cout << std::boolalpha << "echo_server__client(echo_client): " << (std::string_view{data.data()} == message) << std::endl; is_called = true; }; auto srv = schedule_task(echo_server()); co_await echo_client(); srv.cancel(); }()); std::cout << std::boolalpha << "echo_server__client: " << is_called << std::endl; } int main() { test_Task_await(); Task_test(); test_Task_await_result_value(); test_Task_for_loop(); test_schedule_task(); test_exception(); test_gather(); test_sleep(); cancel_a_infinite_loop_coroutine(); test_timeout(); echo_server__client(); return 0; } ```

I'm very unsure if I did everything right, but it compiles, and that was the most important thing for me! Thank you for your time.

Links:

netcan commented 1 year ago

thank you for your try! I developed with macOS originally, but later I changed to docker using a Linux container, so the macOS version was broken.

int register_event_count_ { 1 }; // FIXME: Is there a needed? With zero (0) faster than one (1).

in macOS that may be true, but in Linux the epoll_wait needs the maxevents argument must be greater than zero.

I'm very unsure if I did everything right, but it compiles, and that was the most important thing for me! Thank you for your time.

if ./test/ut/asyncio_ut runs success, it may be right, and your test cases are plenitude.

could you package your modifications with a pull request?