From a23975f06bc156e29a79bb336db6f3c834230681 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 08:51:02 -0500 Subject: [PATCH 001/309] New socket code --- include/glaze/socket/socket.hpp | 231 ++++++++++++++++++++++++++++ include/glaze/thread/threadpool.hpp | 1 + tests/CMakeLists.txt | 1 + tests/socket_test/CMakeLists.txt | 9 ++ tests/socket_test/socket_test.cpp | 66 ++++++++ 5 files changed, 308 insertions(+) create mode 100644 include/glaze/socket/socket.hpp create mode 100644 tests/socket_test/CMakeLists.txt create mode 100644 tests/socket_test/socket_test.cpp diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp new file mode 100644 index 0000000000..93f53d7027 --- /dev/null +++ b/include/glaze/socket/socket.hpp @@ -0,0 +1,231 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +#ifdef _WIN32 + #include + #include + #pragma comment(lib, "Ws2_32.lib") + #define CLOSESOCKET closesocket + #define SOCKET_ERROR_CODE WSAGetLastError() +#else + #include + #include +#if __has_include() + #include +#endif + #include + #include + #include + #include + #define CLOSESOCKET close + #define SOCKET_ERROR_CODE errno + #define SOCKET int +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "glaze/thread/threadpool.hpp" + +namespace glz +{ + namespace net + { + struct opts + { + + }; + } + + inline void wsa_startup() + { + static std::once_flag flag{}; + std::call_once(flag, []{ +#ifdef _WIN32 + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); +#endif + }); + } + + struct socket { + using Callback = std::function; + + SOCKET socket_fd{-1}; + + void set_non_blocking(SOCKET fd) { + #ifdef _WIN32 + u_long mode = 1; + ioctlsocket(fd, FIONBIO, &mode); + #else + int flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + #endif + } + + socket() { + wsa_startup(); + } + + socket(SOCKET fd) : socket_fd(fd) { + wsa_startup(); + set_non_blocking(socket_fd); + } + + ~socket() { + if (socket_fd != -1) { + CLOSESOCKET(socket_fd); + } + #ifdef _WIN32 + WSACleanup(); + #endif + } + + bool connect(const std::string& address, int port) { + socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd == -1) { + return false; + } + + sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); + + if (::connect(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { + return false; + } + + set_non_blocking(socket_fd); + + return true; + } + + bool bind_and_listen(int port) { + socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd == -1) { + return false; + } + + sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(port); + + if (::bind(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { + return false; + } + + if (::listen(socket_fd, SOMAXCONN) == -1) { + return false; + } + + set_non_blocking(socket_fd); + + return true; + } + + void async_read(Callback callback) { + std::thread([this, callback]() { + std::string buffer('\0', 1024); + while (true) { + ssize_t bytes_read = ::recv(int(socket_fd), buffer.data(), buffer.size(), 0); + if (bytes_read > 0) { + callback(buffer, bytes_read); + } else if (bytes_read == 0) { + break; + } else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + }).detach(); + } + + void async_write(const std::string& data, Callback callback) { + std::thread([this, data, callback]() { + size_t total_bytes_sent = 0; + while (total_bytes_sent < data.size()) { + ssize_t bytes_sent = ::send(int(socket_fd), data.data() + total_bytes_sent, data.size() - total_bytes_sent, 0); + if (bytes_sent > 0) { + total_bytes_sent += bytes_sent; + } else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + callback(data, total_bytes_sent); + }).detach(); + } + }; + + struct destructor final { + std::function destroy{}; + + ~destructor() { + if (destroy) { + destroy(); + } + } + + template + destructor(Func&& f) : destroy(std::forward(f)) {} + }; + + struct server final + { + int port{}; + glz::pool threads{1}; + + std::atomic active = true; + + destructor on_destruct{[this]{ + active = false; + }}; + + using AcceptCallback = std::function; + + void async_accept(AcceptCallback callback) { + std::thread([this, callback = std::move(callback)]() { + glz::socket accept_socket{}; + + if (accept_socket.bind_and_listen(port)) + { + std::cout << std::format("Server started on port {}\n", port); + + while (active) { + sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + // As long as we're not calling accept on the same port we are safe + SOCKET client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); + if (client_fd != -1) { + threads.emplace_back([callback = std::move(callback), client_fd]{ + callback(socket(client_fd)); + }); + } else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + else { + std::cerr << "Failed to start server.\n"; + } + }).detach(); + } + }; +} + diff --git a/include/glaze/thread/threadpool.hpp b/include/glaze/thread/threadpool.hpp index e4b70e25e1..2b0e93e408 100644 --- a/include/glaze/thread/threadpool.hpp +++ b/include/glaze/thread/threadpool.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d25a6275d1..81eab1981d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -68,6 +68,7 @@ add_subdirectory(lib_test) add_subdirectory(mock_json_test) add_subdirectory(reflection_test) add_subdirectory(repe_test) +add_subdirectory(socket_test) # We don't run find_package_test or glaze-install_test with MSVC/Windows, because the Github action runner often chokes if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") diff --git a/tests/socket_test/CMakeLists.txt b/tests/socket_test/CMakeLists.txt new file mode 100644 index 0000000000..8685725812 --- /dev/null +++ b/tests/socket_test/CMakeLists.txt @@ -0,0 +1,9 @@ +project(socket_test) + +add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp) + +target_link_libraries(${PROJECT_NAME} PRIVATE glz_test_common) + +add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) + +target_code_coverage(${PROJECT_NAME} AUTO ALL) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp new file mode 100644 index 0000000000..a690fb58d7 --- /dev/null +++ b/tests/socket_test/socket_test.cpp @@ -0,0 +1,66 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#define UT_RUN_TIME_ONLY + +#include +#include + +#include "ut/ut.hpp" + +#include "glaze/socket/socket.hpp" + +#include + +using namespace ut; + +std::future server_thread{}; + +suite make_server = [] { + server_thread = std::async([]{ + glz::server server{8080}; + + server.async_accept([](glz::socket&& client) { + std::cout << "New client connected!\n"; + + client.async_read([](const std::string& data, int bytes_read) { + std::string received(data.begin(), data.begin() + bytes_read); + std::cout << "Received from client: " << received << std::endl; + }); + + std::string message = "Welcome!"; + client.async_write(message, [](const std::string& data, int bytes_sent) { + std::cout << "Sent to client: " << std::string(data.begin(), data.begin() + bytes_sent) << std::endl; + }); + }); + + // Keep thread alive + std::this_thread::sleep_for(std::chrono::hours(1)); + }); + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); +}; + +suite socket_test = [] { + glz::socket socket{}; + + if (socket.connect("127.0.0.1", 8080)) { + std::cout << "Connected to server!" << std::endl; + + socket.async_read([](const std::string& data, int bytes_read) { + std::string received(data.begin(), data.begin() + bytes_read); + std::cout << "Received: " << received << std::endl; + }); + + std::string message = "Hello World"; + socket.async_write(message, [](const std::string& data, int bytes_sent) { + std::cout << "Sent: " << std::string(data.begin(), data.begin() + bytes_sent) << std::endl; + }); + } else { + std::cerr << "Failed to connect to server." << std::endl; + } + + std::this_thread::sleep_for(std::chrono::seconds(10)); +}; + +int main() { return 0; } From e358af07ad958b4166723f7924e0b7a355c8338d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 09:17:22 -0500 Subject: [PATCH 002/309] updates --- include/glaze/socket/socket.hpp | 99 ++++++++++++++++++++----------- tests/socket_test/socket_test.cpp | 7 ++- 2 files changed, 71 insertions(+), 35 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 93f53d7027..61d0433b1a 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -36,7 +36,7 @@ namespace glz { - namespace net + namespace ip { struct opts { @@ -55,6 +55,35 @@ namespace glz }); } + enum ip_error + { + socket_connect_failed = 1001, + server_bind_failed = 1002 + }; + + struct ip_error_category : public std::error_category { + static const ip_error_category& instance() { + static ip_error_category instance{}; + return instance; + } + + const char* name() const noexcept override { + return "ip_error_category"; + } + + std::string message(int ec) const override { + using enum ip_error; + switch (static_cast(ec)) { + case socket_connect_failed: + return "socket_connect_failed"; + case server_bind_failed: + return "server_bind_failed"; + default: + return "unknown_error"; + } + } + }; + struct socket { using Callback = std::function; @@ -88,10 +117,10 @@ namespace glz #endif } - bool connect(const std::string& address, int port) { + std::error_code connect(const std::string& address, int port) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (socket_fd == -1) { - return false; + return {ip_error::socket_connect_failed, ip_error_category::instance()}; } sockaddr_in server_addr; @@ -100,12 +129,12 @@ namespace glz inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); if (::connect(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { - return false; + return {ip_error::socket_connect_failed, ip_error_category::instance()}; } set_non_blocking(socket_fd); - return true; + return {}; } bool bind_and_listen(int port) { @@ -196,35 +225,37 @@ namespace glz using AcceptCallback = std::function; - void async_accept(AcceptCallback callback) { - std::thread([this, callback = std::move(callback)]() { - glz::socket accept_socket{}; - - if (accept_socket.bind_and_listen(port)) - { - std::cout << std::format("Server started on port {}\n", port); - - while (active) { - sockaddr_in client_addr; - socklen_t client_len = sizeof(client_addr); - // As long as we're not calling accept on the same port we are safe - SOCKET client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); - if (client_fd != -1) { - threads.emplace_back([callback = std::move(callback), client_fd]{ - callback(socket(client_fd)); - }); - } else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; - } - } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - } - else { - std::cerr << "Failed to start server.\n"; - } - }).detach(); + std::error_code async_accept(AcceptCallback callback) { + std::unique_ptr accept_socket = std::make_unique(); + + if (accept_socket->bind_and_listen(port)) + { + std::thread([this, accept_socket = std::move(accept_socket), callback = std::move(callback)]() { + std::cout << std::format("Server started on port {}\n", port); + + while (active) { + sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + // As long as we're not calling accept on the same port we are safe + SOCKET client_fd = ::accept(accept_socket->socket_fd, (sockaddr*)&client_addr, &client_len); + if (client_fd != -1) { + threads.emplace_back([callback = std::move(callback), client_fd]{ + callback(socket(client_fd)); + }); + } else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + }).detach(); + } + else { + return {ip_error::server_bind_failed, ip_error_category::instance()}; + } + + return {}; } }; } diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index a690fb58d7..471887d670 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -20,7 +20,7 @@ suite make_server = [] { server_thread = std::async([]{ glz::server server{8080}; - server.async_accept([](glz::socket&& client) { + const auto ec = server.async_accept([](glz::socket&& client) { std::cout << "New client connected!\n"; client.async_read([](const std::string& data, int bytes_read) { @@ -34,10 +34,15 @@ suite make_server = [] { }); }); + if (ec) { + std::cout << ec.message() << '\n'; + } + // Keep thread alive std::this_thread::sleep_for(std::chrono::hours(1)); }); + // Allow the socket to get started std::this_thread::sleep_for(std::chrono::milliseconds(10)); }; From b464a3509853a8db66670023c8c9f6ecec342442 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 09:22:21 -0500 Subject: [PATCH 003/309] updates --- include/glaze/socket/socket.hpp | 3 ++- tests/socket_test/socket_test.cpp | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 61d0433b1a..579a9aad97 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -57,6 +57,7 @@ namespace glz enum ip_error { + none = 0, socket_connect_failed = 1001, server_bind_failed = 1002 }; @@ -134,7 +135,7 @@ namespace glz set_non_blocking(socket_fd); - return {}; + return {}; } bool bind_and_listen(int port) { diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 471887d670..9a302618a4 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -35,7 +35,7 @@ suite make_server = [] { }); if (ec) { - std::cout << ec.message() << '\n'; + std::cerr << ec.message() << '\n'; } // Keep thread alive @@ -50,19 +50,19 @@ suite socket_test = [] { glz::socket socket{}; if (socket.connect("127.0.0.1", 8080)) { - std::cout << "Connected to server!" << std::endl; + std::cerr << "Failed to connect to server.\n"; + } else { + std::cout << "Connected to server!\n"; - socket.async_read([](const std::string& data, int bytes_read) { - std::string received(data.begin(), data.begin() + bytes_read); - std::cout << "Received: " << received << std::endl; - }); + socket.async_read([](const std::string& data, int bytes_read) { + std::string received(data.begin(), data.begin() + bytes_read); + std::cout << "Received: " << received << std::endl; + }); - std::string message = "Hello World"; - socket.async_write(message, [](const std::string& data, int bytes_sent) { - std::cout << "Sent: " << std::string(data.begin(), data.begin() + bytes_sent) << std::endl; - }); - } else { - std::cerr << "Failed to connect to server." << std::endl; + std::string message = "Hello World"; + socket.async_write(message, [](const std::string& data, int bytes_sent) { + std::cout << "Sent: " << std::string(data.begin(), data.begin() + bytes_sent) << std::endl; + }); } std::this_thread::sleep_for(std::chrono::seconds(10)); From 4c66bc2764a640a8fe8da91a716d455683da3fa2 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 09:29:47 -0500 Subject: [PATCH 004/309] Update socket_test.cpp --- tests/socket_test/socket_test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 9a302618a4..c48e27faa0 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -29,8 +29,8 @@ suite make_server = [] { }); std::string message = "Welcome!"; - client.async_write(message, [](const std::string& data, int bytes_sent) { - std::cout << "Sent to client: " << std::string(data.begin(), data.begin() + bytes_sent) << std::endl; + client.async_write(message, [](const std::string& data, int /*bytes_sent*/) { + std::cout << std::format("Sent to client: {}\n", data); }); }); @@ -61,7 +61,7 @@ suite socket_test = [] { std::string message = "Hello World"; socket.async_write(message, [](const std::string& data, int bytes_sent) { - std::cout << "Sent: " << std::string(data.begin(), data.begin() + bytes_sent) << std::endl; + std::cout << "Sent from client: " << std::string(data.begin(), data.begin() + bytes_sent) << std::endl; }); } From 754a15f964fb4faa37475f5494d8f65efb8d0655 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 09:35:51 -0500 Subject: [PATCH 005/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 579a9aad97..22ed6a0675 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -226,12 +226,13 @@ namespace glz using AcceptCallback = std::function; - std::error_code async_accept(AcceptCallback callback) { + template + std::error_code async_accept(AcceptCallback&& callback) { std::unique_ptr accept_socket = std::make_unique(); if (accept_socket->bind_and_listen(port)) { - std::thread([this, accept_socket = std::move(accept_socket), callback = std::move(callback)]() { + std::thread([this, accept_socket = std::move(accept_socket), callback = std::forward(callback)]() { std::cout << std::format("Server started on port {}\n", port); while (active) { From b44533e5e92332bc207f228ea5b364ce49067757 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 09:36:58 -0500 Subject: [PATCH 006/309] updates --- include/glaze/socket/socket.hpp | 2 -- tests/socket_test/socket_test.cpp | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 22ed6a0675..02009593e7 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -233,8 +233,6 @@ namespace glz if (accept_socket->bind_and_listen(port)) { std::thread([this, accept_socket = std::move(accept_socket), callback = std::forward(callback)]() { - std::cout << std::format("Server started on port {}\n", port); - while (active) { sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index c48e27faa0..5bee27fb0d 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -37,6 +37,9 @@ suite make_server = [] { if (ec) { std::cerr << ec.message() << '\n'; } + else { + std::cout << std::format("Server started on port: {}\n", server.port); + } // Keep thread alive std::this_thread::sleep_for(std::chrono::hours(1)); From 28743470adf7a55a1ccdda4ddd19f8c6337afdb4 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 09:48:45 -0500 Subject: [PATCH 007/309] no_delay and synchronous --- include/glaze/socket/socket.hpp | 70 ++++++++++++++++--------------- tests/socket_test/socket_test.cpp | 8 ++-- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 02009593e7..8803e8c3cd 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -15,6 +15,7 @@ #if __has_include() #include #endif +#include #include #include #include @@ -138,6 +139,12 @@ namespace glz return {}; } + bool no_delay() { + int flag = 1; + int result = setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int)); + return result == 0; + } + bool bind_and_listen(int port) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (socket_fd == -1) { @@ -158,45 +165,42 @@ namespace glz } set_non_blocking(socket_fd); + no_delay(); return true; } - void async_read(Callback callback) { - std::thread([this, callback]() { - std::string buffer('\0', 1024); - while (true) { - ssize_t bytes_read = ::recv(int(socket_fd), buffer.data(), buffer.size(), 0); - if (bytes_read > 0) { - callback(buffer, bytes_read); - } else if (bytes_read == 0) { - break; - } else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; - } - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - }).detach(); + void read(Callback callback) { + std::string buffer('\0', 1024); + while (true) { + ssize_t bytes_read = ::recv(int(socket_fd), buffer.data(), buffer.size(), 0); + if (bytes_read > 0) { + callback(buffer, bytes_read); + } else if (bytes_read == 0) { + break; // connection has been closed + } else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } } - void async_write(const std::string& data, Callback callback) { - std::thread([this, data, callback]() { - size_t total_bytes_sent = 0; - while (total_bytes_sent < data.size()) { - ssize_t bytes_sent = ::send(int(socket_fd), data.data() + total_bytes_sent, data.size() - total_bytes_sent, 0); - if (bytes_sent > 0) { - total_bytes_sent += bytes_sent; - } else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; - } - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - callback(data, total_bytes_sent); - }).detach(); + void write(const std::string& data, Callback callback) { + size_t total_bytes_sent = 0; + while (total_bytes_sent < data.size()) { + ssize_t bytes_sent = ::send(int(socket_fd), data.data() + total_bytes_sent, data.size() - total_bytes_sent, 0); + if (bytes_sent > 0) { + total_bytes_sent += bytes_sent; + } else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + callback(data, total_bytes_sent); } }; diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 5bee27fb0d..b1ceff616d 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -23,13 +23,13 @@ suite make_server = [] { const auto ec = server.async_accept([](glz::socket&& client) { std::cout << "New client connected!\n"; - client.async_read([](const std::string& data, int bytes_read) { + client.read([](const std::string& data, int bytes_read) { std::string received(data.begin(), data.begin() + bytes_read); std::cout << "Received from client: " << received << std::endl; }); std::string message = "Welcome!"; - client.async_write(message, [](const std::string& data, int /*bytes_sent*/) { + client.write(message, [](const std::string& data, int /*bytes_sent*/) { std::cout << std::format("Sent to client: {}\n", data); }); }); @@ -57,13 +57,13 @@ suite socket_test = [] { } else { std::cout << "Connected to server!\n"; - socket.async_read([](const std::string& data, int bytes_read) { + socket.read([](const std::string& data, int bytes_read) { std::string received(data.begin(), data.begin() + bytes_read); std::cout << "Received: " << received << std::endl; }); std::string message = "Hello World"; - socket.async_write(message, [](const std::string& data, int bytes_sent) { + socket.write(message, [](const std::string& data, int bytes_sent) { std::cout << "Sent from client: " << std::string(data.begin(), data.begin() + bytes_sent) << std::endl; }); } From 1e0e760c7a205d44a6d1914c769f09f7dbfd3df4 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 09:49:47 -0500 Subject: [PATCH 008/309] formatting --- include/glaze/socket/socket.hpp | 412 +++++++++++++++--------------- tests/socket_test/socket_test.cpp | 64 ++--- 2 files changed, 241 insertions(+), 235 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 8803e8c3cd..78463a98ff 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -4,34 +4,35 @@ #pragma once #ifdef _WIN32 - #include - #include - #pragma comment(lib, "Ws2_32.lib") - #define CLOSESOCKET closesocket - #define SOCKET_ERROR_CODE WSAGetLastError() +#include +#include +#pragma comment(lib, "Ws2_32.lib") +#define CLOSESOCKET closesocket +#define SOCKET_ERROR_CODE WSAGetLastError() #else - #include - #include +#include +#include #if __has_include() - #include +#include #endif +#include +#include #include - #include - #include - #include - #include - #define CLOSESOCKET close - #define SOCKET_ERROR_CODE errno - #define SOCKET int +#include + +#include +#define CLOSESOCKET close +#define SOCKET_ERROR_CODE errno +#define SOCKET int #endif -#include -#include -#include +#include #include +#include #include #include -#include +#include +#include #include "glaze/thread/threadpool.hpp" @@ -40,227 +41,232 @@ namespace glz namespace ip { struct opts - { - - }; + {}; } - + inline void wsa_startup() { static std::once_flag flag{}; - std::call_once(flag, []{ + std::call_once(flag, [] { #ifdef _WIN32 WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); #endif }); } - - enum ip_error + + enum ip_error { none = 0, socket_connect_failed = 1001, server_bind_failed = 1002 }; + + struct ip_error_category : public std::error_category { - none = 0, - socket_connect_failed = 1001, - server_bind_failed = 1002 - }; - - struct ip_error_category : public std::error_category { - static const ip_error_category& instance() { - static ip_error_category instance{}; - return instance; - } - - const char* name() const noexcept override { - return "ip_error_category"; - } - - std::string message(int ec) const override { - using enum ip_error; - switch (static_cast(ec)) { - case socket_connect_failed: - return "socket_connect_failed"; - case server_bind_failed: - return "server_bind_failed"; - default: - return "unknown_error"; - } - } + static const ip_error_category& instance() + { + static ip_error_category instance{}; + return instance; + } + + const char* name() const noexcept override { return "ip_error_category"; } + + std::string message(int ec) const override + { + using enum ip_error; + switch (static_cast(ec)) { + case socket_connect_failed: + return "socket_connect_failed"; + case server_bind_failed: + return "server_bind_failed"; + default: + return "unknown_error"; + } + } }; - - struct socket { - using Callback = std::function; - + + struct socket + { + using Callback = std::function; + SOCKET socket_fd{-1}; - void set_non_blocking(SOCKET fd) { - #ifdef _WIN32 - u_long mode = 1; - ioctlsocket(fd, FIONBIO, &mode); - #else - int flags = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, flags | O_NONBLOCK); - #endif - } - - socket() { - wsa_startup(); + void set_non_blocking(SOCKET fd) + { +#ifdef _WIN32 + u_long mode = 1; + ioctlsocket(fd, FIONBIO, &mode); +#else + int flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); +#endif } - - socket(SOCKET fd) : socket_fd(fd) { + + socket() { wsa_startup(); } + + socket(SOCKET fd) : socket_fd(fd) + { wsa_startup(); set_non_blocking(socket_fd); } - ~socket() { - if (socket_fd != -1) { - CLOSESOCKET(socket_fd); - } - #ifdef _WIN32 - WSACleanup(); - #endif - } - - std::error_code connect(const std::string& address, int port) { - socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd == -1) { - return {ip_error::socket_connect_failed, ip_error_category::instance()}; - } - - sockaddr_in server_addr; - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(port); - inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); - - if (::connect(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { - return {ip_error::socket_connect_failed, ip_error_category::instance()}; - } - - set_non_blocking(socket_fd); - - return {}; - } - - bool no_delay() { - int flag = 1; - int result = setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int)); - return result == 0; - } - - bool bind_and_listen(int port) { - socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd == -1) { - return false; - } - - sockaddr_in server_addr; - server_addr.sin_family = AF_INET; - server_addr.sin_addr.s_addr = INADDR_ANY; - server_addr.sin_port = htons(port); - - if (::bind(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { - return false; - } - - if (::listen(socket_fd, SOMAXCONN) == -1) { - return false; - } - - set_non_blocking(socket_fd); - no_delay(); - - return true; - } - - void read(Callback callback) { - std::string buffer('\0', 1024); - while (true) { - ssize_t bytes_read = ::recv(int(socket_fd), buffer.data(), buffer.size(), 0); - if (bytes_read > 0) { - callback(buffer, bytes_read); - } else if (bytes_read == 0) { - break; // connection has been closed - } else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; - } - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - } - - void write(const std::string& data, Callback callback) { - size_t total_bytes_sent = 0; - while (total_bytes_sent < data.size()) { - ssize_t bytes_sent = ::send(int(socket_fd), data.data() + total_bytes_sent, data.size() - total_bytes_sent, 0); - if (bytes_sent > 0) { - total_bytes_sent += bytes_sent; - } else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; - } - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - callback(data, total_bytes_sent); - } + ~socket() + { + if (socket_fd != -1) { + CLOSESOCKET(socket_fd); + } +#ifdef _WIN32 + WSACleanup(); +#endif + } + + std::error_code connect(const std::string& address, int port) + { + socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd == -1) { + return {ip_error::socket_connect_failed, ip_error_category::instance()}; + } + + sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); + + if (::connect(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { + return {ip_error::socket_connect_failed, ip_error_category::instance()}; + } + + set_non_blocking(socket_fd); + + return {}; + } + + bool no_delay() + { + int flag = 1; + int result = setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int)); + return result == 0; + } + + bool bind_and_listen(int port) + { + socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd == -1) { + return false; + } + + sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(port); + + if (::bind(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { + return false; + } + + if (::listen(socket_fd, SOMAXCONN) == -1) { + return false; + } + + set_non_blocking(socket_fd); + no_delay(); + + return true; + } + + void read(Callback callback) + { + std::string buffer('\0', 1024); + while (true) { + ssize_t bytes_read = ::recv(int(socket_fd), buffer.data(), buffer.size(), 0); + if (bytes_read > 0) { + callback(buffer, bytes_read); + } + else if (bytes_read == 0) { + break; // connection has been closed + } + else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + + void write(const std::string& data, Callback callback) + { + size_t total_bytes_sent = 0; + while (total_bytes_sent < data.size()) { + ssize_t bytes_sent = + ::send(int(socket_fd), data.data() + total_bytes_sent, data.size() - total_bytes_sent, 0); + if (bytes_sent > 0) { + total_bytes_sent += bytes_sent; + } + else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + callback(data, total_bytes_sent); + } }; - - struct destructor final { - std::function destroy{}; - - ~destructor() { - if (destroy) { - destroy(); - } - } - - template - destructor(Func&& f) : destroy(std::forward(f)) {} + + struct destructor final + { + std::function destroy{}; + + ~destructor() + { + if (destroy) { + destroy(); + } + } + + template + destructor(Func&& f) : destroy(std::forward(f)) + {} }; - + struct server final { int port{}; glz::pool threads{1}; - + std::atomic active = true; - - destructor on_destruct{[this]{ - active = false; - }}; - + + destructor on_destruct{[this] { active = false; }}; + using AcceptCallback = std::function; - + template - std::error_code async_accept(AcceptCallback&& callback) { + std::error_code async_accept(AcceptCallback&& callback) + { std::unique_ptr accept_socket = std::make_unique(); - - if (accept_socket->bind_and_listen(port)) - { - std::thread([this, accept_socket = std::move(accept_socket), callback = std::forward(callback)]() { + + if (accept_socket->bind_and_listen(port)) { + std::thread([this, accept_socket = std::move(accept_socket), + callback = std::forward(callback)]() { while (active) { - sockaddr_in client_addr; - socklen_t client_len = sizeof(client_addr); + sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); // As long as we're not calling accept on the same port we are safe - SOCKET client_fd = ::accept(accept_socket->socket_fd, (sockaddr*)&client_addr, &client_len); - if (client_fd != -1) { - threads.emplace_back([callback = std::move(callback), client_fd]{ - callback(socket(client_fd)); - }); - } else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; - } - } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + SOCKET client_fd = ::accept(accept_socket->socket_fd, (sockaddr*)&client_addr, &client_len); + if (client_fd != -1) { + threads.emplace_back([callback = std::move(callback), client_fd] { callback(socket(client_fd)); }); + } + else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } }).detach(); } else { return {ip_error::server_bind_failed, ip_error_category::instance()}; } - + return {}; - } + } }; } - diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index b1ceff616d..9cca325647 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -3,48 +3,47 @@ #define UT_RUN_TIME_ONLY +#include "glaze/socket/socket.hpp" + +#include #include #include #include "ut/ut.hpp" -#include "glaze/socket/socket.hpp" - -#include - using namespace ut; std::future server_thread{}; suite make_server = [] { - server_thread = std::async([]{ + server_thread = std::async([] { glz::server server{8080}; - const auto ec = server.async_accept([](glz::socket&& client) { - std::cout << "New client connected!\n"; + const auto ec = server.async_accept([](glz::socket&& client) { + std::cout << "New client connected!\n"; - client.read([](const std::string& data, int bytes_read) { - std::string received(data.begin(), data.begin() + bytes_read); - std::cout << "Received from client: " << received << std::endl; - }); + client.read([](const std::string& data, int bytes_read) { + std::string received(data.begin(), data.begin() + bytes_read); + std::cout << "Received from client: " << received << std::endl; + }); std::string message = "Welcome!"; - client.write(message, [](const std::string& data, int /*bytes_sent*/) { - std::cout << std::format("Sent to client: {}\n", data); - }); + client.write(message, [](const std::string& data, int /*bytes_sent*/) { + std::cout << std::format("Sent to client: {}\n", data); + }); }); - + if (ec) { std::cerr << ec.message() << '\n'; } else { std::cout << std::format("Server started on port: {}\n", server.port); } - - // Keep thread alive - std::this_thread::sleep_for(std::chrono::hours(1)); + + // Keep thread alive + std::this_thread::sleep_for(std::chrono::hours(1)); }); - + // Allow the socket to get started std::this_thread::sleep_for(std::chrono::milliseconds(10)); }; @@ -52,23 +51,24 @@ suite make_server = [] { suite socket_test = [] { glz::socket socket{}; - if (socket.connect("127.0.0.1", 8080)) { - std::cerr << "Failed to connect to server.\n"; - } else { - std::cout << "Connected to server!\n"; + if (socket.connect("127.0.0.1", 8080)) { + std::cerr << "Failed to connect to server.\n"; + } + else { + std::cout << "Connected to server!\n"; - socket.read([](const std::string& data, int bytes_read) { - std::string received(data.begin(), data.begin() + bytes_read); - std::cout << "Received: " << received << std::endl; - }); + socket.read([](const std::string& data, int bytes_read) { + std::string received(data.begin(), data.begin() + bytes_read); + std::cout << "Received: " << received << std::endl; + }); std::string message = "Hello World"; - socket.write(message, [](const std::string& data, int bytes_sent) { - std::cout << "Sent from client: " << std::string(data.begin(), data.begin() + bytes_sent) << std::endl; - }); - } + socket.write(message, [](const std::string& data, int bytes_sent) { + std::cout << "Sent from client: " << std::string(data.begin(), data.begin() + bytes_sent) << std::endl; + }); + } - std::this_thread::sleep_for(std::chrono::seconds(10)); + std::this_thread::sleep_for(std::chrono::seconds(10)); }; int main() { return 0; } From bca78987da22f145959051dbe92da4e166b78d69 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 09:54:01 -0500 Subject: [PATCH 009/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 78463a98ff..5b45a6f9dd 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -55,7 +55,7 @@ namespace glz }); } - enum ip_error { none = 0, socket_connect_failed = 1001, server_bind_failed = 1002 }; + enum ip_error { none = 0, socket_connect_failed = 1001, socket_bind_failed = 1002 }; struct ip_error_category : public std::error_category { @@ -73,8 +73,8 @@ namespace glz switch (static_cast(ec)) { case socket_connect_failed: return "socket_connect_failed"; - case server_bind_failed: - return "server_bind_failed"; + case socket_bind_failed: + return "socket_bind_failed"; default: return "unknown_error"; } @@ -144,11 +144,11 @@ namespace glz return result == 0; } - bool bind_and_listen(int port) + std::error_code bind_and_listen(int port) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (socket_fd == -1) { - return false; + return {ip_error::socket_bind_failed, ip_error_category::instance()}; } sockaddr_in server_addr; @@ -157,17 +157,17 @@ namespace glz server_addr.sin_port = htons(port); if (::bind(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { - return false; + return {ip_error::socket_bind_failed, ip_error_category::instance()}; } if (::listen(socket_fd, SOMAXCONN) == -1) { - return false; + return {ip_error::socket_bind_failed, ip_error_category::instance()}; } set_non_blocking(socket_fd); no_delay(); - return true; + return {}; } void read(Callback callback) @@ -242,7 +242,11 @@ namespace glz { std::unique_ptr accept_socket = std::make_unique(); - if (accept_socket->bind_and_listen(port)) { + const auto ec = accept_socket->bind_and_listen(port); + if (ec) { + return {ip_error::socket_bind_failed, ip_error_category::instance()}; + } + else { std::thread([this, accept_socket = std::move(accept_socket), callback = std::forward(callback)]() { while (active) { @@ -262,9 +266,6 @@ namespace glz } }).detach(); } - else { - return {ip_error::server_bind_failed, ip_error_category::instance()}; - } return {}; } From d1e15457d662dbba683322d2c70fee95b4bdbd2f Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 10:12:02 -0500 Subject: [PATCH 010/309] updates --- include/glaze/socket/socket.hpp | 16 +++++++++------- tests/socket_test/socket_test.cpp | 14 ++++---------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 5b45a6f9dd..e6076cbfcd 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -80,11 +80,12 @@ namespace glz } } }; + + template + concept is_callback = std::invocable || std::invocable; struct socket { - using Callback = std::function; - SOCKET socket_fd{-1}; void set_non_blocking(SOCKET fd) @@ -170,13 +171,14 @@ namespace glz return {}; } - void read(Callback callback) + template + void read(Callback&& callback) { std::string buffer('\0', 1024); while (true) { ssize_t bytes_read = ::recv(int(socket_fd), buffer.data(), buffer.size(), 0); if (bytes_read > 0) { - callback(buffer, bytes_read); + continue; } else if (bytes_read == 0) { break; // connection has been closed @@ -186,11 +188,12 @@ namespace glz break; } } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } + callback(buffer); } - void write(const std::string& data, Callback callback) + void write(const std::string& data) { size_t total_bytes_sent = 0; while (total_bytes_sent < data.size()) { @@ -206,7 +209,6 @@ namespace glz } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } - callback(data, total_bytes_sent); } }; diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 9cca325647..07d9bee7fe 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -22,15 +22,12 @@ suite make_server = [] { const auto ec = server.async_accept([](glz::socket&& client) { std::cout << "New client connected!\n"; - client.read([](const std::string& data, int bytes_read) { - std::string received(data.begin(), data.begin() + bytes_read); + client.read([](const std::string& received) { std::cout << "Received from client: " << received << std::endl; }); std::string message = "Welcome!"; - client.write(message, [](const std::string& data, int /*bytes_sent*/) { - std::cout << std::format("Sent to client: {}\n", data); - }); + client.write(message); }); if (ec) { @@ -57,15 +54,12 @@ suite socket_test = [] { else { std::cout << "Connected to server!\n"; - socket.read([](const std::string& data, int bytes_read) { - std::string received(data.begin(), data.begin() + bytes_read); + socket.read([](const std::string& received) { std::cout << "Received: " << received << std::endl; }); std::string message = "Hello World"; - socket.write(message, [](const std::string& data, int bytes_sent) { - std::cout << "Sent from client: " << std::string(data.begin(), data.begin() + bytes_sent) << std::endl; - }); + socket.write(message); } std::this_thread::sleep_for(std::chrono::seconds(10)); From 78597ba598f2096ebf63095fc283513b97b81aa9 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 10:21:45 -0500 Subject: [PATCH 011/309] updates --- include/glaze/socket/socket.hpp | 4 ++-- tests/socket_test/socket_test.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index e6076cbfcd..20fe951a65 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -181,11 +181,11 @@ namespace glz continue; } else if (bytes_read == 0) { - break; // connection has been closed + return; // connection has been closed } else { if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; + return; } } std::this_thread::sleep_for(std::chrono::milliseconds(10)); diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 07d9bee7fe..309014e53b 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -42,7 +42,7 @@ suite make_server = [] { }); // Allow the socket to get started - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); }; suite socket_test = [] { From 080d7b9233bdd21b50468f616e122ba4faaaa3ae Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 10:27:00 -0500 Subject: [PATCH 012/309] Update socket_test.cpp --- tests/socket_test/socket_test.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 309014e53b..b61f75c79f 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -22,12 +22,12 @@ suite make_server = [] { const auto ec = server.async_accept([](glz::socket&& client) { std::cout << "New client connected!\n"; - client.read([](const std::string& received) { - std::cout << "Received from client: " << received << std::endl; - }); - std::string message = "Welcome!"; client.write(message); + + /*client.read([](const std::string& received) { + std::cout << "Received from client: " << received << std::endl; + });*/ }); if (ec) { @@ -58,8 +58,8 @@ suite socket_test = [] { std::cout << "Received: " << received << std::endl; }); - std::string message = "Hello World"; - socket.write(message); + //std::string message = "Hello World"; + //socket.write(message); } std::this_thread::sleep_for(std::chrono::seconds(10)); From 2477c85d2f89a73d1760d30628a27ac136d1efd5 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 10:38:48 -0500 Subject: [PATCH 013/309] updates --- include/glaze/socket/socket.hpp | 4 +++- tests/socket_test/socket_test.cpp | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 20fe951a65..249744d33e 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -257,7 +257,9 @@ namespace glz // As long as we're not calling accept on the same port we are safe SOCKET client_fd = ::accept(accept_socket->socket_fd, (sockaddr*)&client_addr, &client_len); if (client_fd != -1) { - threads.emplace_back([callback = std::move(callback), client_fd] { callback(socket(client_fd)); }); + threads.emplace_back([callback = std::move(callback), client_fd] { + callback(socket{client_fd}); + }); } else { if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index b61f75c79f..e53400e7df 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -19,7 +19,7 @@ suite make_server = [] { server_thread = std::async([] { glz::server server{8080}; - const auto ec = server.async_accept([](glz::socket&& client) { + const auto ec = server.async_accept([&](glz::socket&& client) { std::cout << "New client connected!\n"; std::string message = "Welcome!"; @@ -28,6 +28,11 @@ suite make_server = [] { /*client.read([](const std::string& received) { std::cout << "Received from client: " << received << std::endl; });*/ + + // Change this to a std::condition_variable + while (server.active) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } }); if (ec) { @@ -62,7 +67,7 @@ suite socket_test = [] { //socket.write(message); } - std::this_thread::sleep_for(std::chrono::seconds(10)); + std::this_thread::sleep_for(std::chrono::seconds(60)); }; int main() { return 0; } From e60e58ad8623218c978e33f3252b252fa305bcdb Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 10:59:00 -0500 Subject: [PATCH 014/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 56 +++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 249744d33e..cce466ef72 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -175,32 +175,58 @@ namespace glz void read(Callback&& callback) { std::string buffer('\0', 1024); - while (true) { - ssize_t bytes_read = ::recv(int(socket_fd), buffer.data(), buffer.size(), 0); - if (bytes_read > 0) { + + uint64_t size{}; + ssize_t size_bytes{}; + while (size_bytes < 8) { + size_bytes += ::recv(int(socket_fd), (char*)(&size) + size_bytes, sizeof(uint64_t) - size_bytes, 0); + } + + if (size_bytes > 8) { + return; // error + } + + size_t total_bytes{}; + while (total_bytes < size) { + const ssize_t bytes = ::recv(int(socket_fd), buffer.data() + total_bytes, buffer.size() - total_bytes, 0); + if (bytes > 0) { + total_bytes += bytes; continue; } - else if (bytes_read == 0) { + else if (bytes == 0) { return; // connection has been closed } else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - return; + if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + else { + return; // error } } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); } callback(buffer); } void write(const std::string& data) { - size_t total_bytes_sent = 0; - while (total_bytes_sent < data.size()) { - ssize_t bytes_sent = - ::send(int(socket_fd), data.data() + total_bytes_sent, data.size() - total_bytes_sent, 0); - if (bytes_sent > 0) { - total_bytes_sent += bytes_sent; + const uint64_t size = data.size(); + + ssize_t size_bytes{}; + while (size_bytes < 8) { + size_bytes += ::send(int(socket_fd), (char*)(&size) + size_bytes, sizeof(uint64_t) - size_bytes, 0); + } + + if (size_bytes > 8) { + return; // error + } + + size_t total_bytes = 0; + while (total_bytes < size) { + const ssize_t bytes = + ::send(int(socket_fd), data.data() + total_bytes, data.size() - total_bytes, 0); + if (bytes > 0) { + total_bytes += bytes; } else { if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { @@ -209,6 +235,10 @@ namespace glz } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } + + if (total_bytes != size) { + // error + } } }; From 5108fba3e3042b07554b877bffb733a0e8cddcd7 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 11:49:31 -0500 Subject: [PATCH 015/309] updates --- include/glaze/socket/socket.hpp | 175 +++++++++++++++++++----------- tests/socket_test/socket_test.cpp | 24 ++-- 2 files changed, 123 insertions(+), 76 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index cce466ef72..20986a6c27 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -88,15 +88,15 @@ namespace glz { SOCKET socket_fd{-1}; - void set_non_blocking(SOCKET fd) + void set_non_blocking(SOCKET /*fd*/) { -#ifdef _WIN32 +/*#ifdef _WIN32 u_long mode = 1; ioctlsocket(fd, FIONBIO, &mode); #else int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); -#endif +#endif*/ } socket() { wsa_startup(); } @@ -171,74 +171,21 @@ namespace glz return {}; } - template - void read(Callback&& callback) + ssize_t read(std::string& buffer) { - std::string buffer('\0', 1024); - - uint64_t size{}; - ssize_t size_bytes{}; - while (size_bytes < 8) { - size_bytes += ::recv(int(socket_fd), (char*)(&size) + size_bytes, sizeof(uint64_t) - size_bytes, 0); - } - - if (size_bytes > 8) { - return; // error - } - - size_t total_bytes{}; - while (total_bytes < size) { - const ssize_t bytes = ::recv(int(socket_fd), buffer.data() + total_bytes, buffer.size() - total_bytes, 0); - if (bytes > 0) { - total_bytes += bytes; - continue; - } - else if (bytes == 0) { - return; // connection has been closed - } - else { - if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - else { - return; // error - } - } + buffer.resize(4096); + ssize_t bytes = ::recv(socket_fd, buffer.data(), buffer.size(), 0); + if (bytes == -1) { + buffer.clear(); + } else { + buffer.resize(bytes); } - callback(buffer); + return bytes; } - void write(const std::string& data) + ssize_t write(const std::string& buffer) { - const uint64_t size = data.size(); - - ssize_t size_bytes{}; - while (size_bytes < 8) { - size_bytes += ::send(int(socket_fd), (char*)(&size) + size_bytes, sizeof(uint64_t) - size_bytes, 0); - } - - if (size_bytes > 8) { - return; // error - } - - size_t total_bytes = 0; - while (total_bytes < size) { - const ssize_t bytes = - ::send(int(socket_fd), data.data() + total_bytes, data.size() - total_bytes, 0); - if (bytes > 0) { - total_bytes += bytes; - } - else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; - } - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - if (total_bytes != size) { - // error - } + return ::send(socket_fd, buffer.c_str(), buffer.size(), 0); } }; @@ -305,3 +252,99 @@ namespace glz } }; } + + +/*template +void read(Callback&& callback) +{ + std::string buffer('\0', 1024); + + uint64_t size{}; + ssize_t size_bytes{}; + while (size_bytes < 8) { + const ssize_t bytes = ::recv(int(socket_fd), (char*)(&size) + size_bytes, sizeof(uint64_t) - size_bytes, 0); + if (bytes > 0) { + size_bytes += bytes; + continue; + } + else if (bytes == 0) { + continue; // connection has been closed + } + else { + if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + else { + return; // error + } + } + } + + if (size_bytes > 8) { + return; // error + } + + size_t total_bytes{}; + while (total_bytes < size) { + const ssize_t bytes = ::recv(int(socket_fd), buffer.data() + total_bytes, buffer.size() - total_bytes, 0); + if (bytes > 0) { + total_bytes += bytes; + continue; + } + else if (bytes == 0) { + continue; // connection has been closed + } + else { + if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + else { + return; // error + } + } + } + callback(buffer); +} + +void write(const std::string& data) +{ + const uint64_t size = data.size(); + + ssize_t size_bytes{}; + while (size_bytes < 8) { + const ssize_t bytes = ::send(int(socket_fd), (char*)(&size) + size_bytes, sizeof(uint64_t) - size_bytes, 0); + if (bytes > 0) { + size_bytes += bytes; + } + else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + if (size_bytes > 8) { + return; // error + } + + size_t total_bytes = 0; + while (total_bytes < size) { + const ssize_t bytes = + ::send(int(socket_fd), data.data() + total_bytes, data.size() - total_bytes, 0); + if (bytes > 0) { + total_bytes += bytes; + } + else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + if (total_bytes != size) { + // error + } +} +*/ diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index e53400e7df..63df9449e9 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -22,15 +22,19 @@ suite make_server = [] { const auto ec = server.async_accept([&](glz::socket&& client) { std::cout << "New client connected!\n"; - std::string message = "Welcome!"; - client.write(message); - - /*client.read([](const std::string& received) { - std::cout << "Received from client: " << received << std::endl; - });*/ + //std::string message = "Welcome!"; + //client.write(message); // Change this to a std::condition_variable while (server.active) { + /*client.read([](const std::string& received) { + std::cout << "Received from client: " << received << std::endl; + });*/ + + std::string received{}; + client.read(received); + std::cout << "Received from client: " << received << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } }); @@ -59,12 +63,12 @@ suite socket_test = [] { else { std::cout << "Connected to server!\n"; - socket.read([](const std::string& received) { + /*socket.read([](const std::string& received) { std::cout << "Received: " << received << std::endl; - }); + });*/ - //std::string message = "Hello World"; - //socket.write(message); + std::string message = "Hello World"; + socket.write(message); } std::this_thread::sleep_for(std::chrono::seconds(60)); From 50bbf64c71c2b54ee1f96199dd18256f446094f6 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 12:04:58 -0500 Subject: [PATCH 016/309] updates --- include/glaze/socket/socket.hpp | 150 +++++++++--------------------- tests/socket_test/socket_test.cpp | 8 +- 2 files changed, 52 insertions(+), 106 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 20986a6c27..f2599f33d4 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -173,19 +173,57 @@ namespace glz ssize_t read(std::string& buffer) { - buffer.resize(4096); - ssize_t bytes = ::recv(socket_fd, buffer.data(), buffer.size(), 0); - if (bytes == -1) { - buffer.clear(); - } else { - buffer.resize(bytes); + uint64_t size{}; + size_t size_bytes{}; + while (size_bytes < 8) { + ssize_t bytes = ::recv(socket_fd, (char*)(&size) + size_bytes, sizeof(uint64_t) - size_bytes, 0); + if (bytes == -1) { + buffer.clear(); + return bytes; + } + else { + size_bytes += bytes; + } } - return bytes; + + buffer.resize(size); + size_t total_bytes{}; + while (total_bytes < size) { + ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, buffer.size() - total_bytes, 0); + if (bytes == -1) { + buffer.clear(); + return bytes; + } else { + total_bytes += bytes; + } + } + return buffer.size(); } ssize_t write(const std::string& buffer) { - return ::send(socket_fd, buffer.c_str(), buffer.size(), 0); + uint64_t size = buffer.size(); + size_t size_bytes{}; + while (size_bytes < 8) { + ssize_t bytes = ::send(socket_fd, (char*)(&size) + size_bytes, sizeof(uint64_t) - size_bytes, 0); + if (bytes == -1) { + return bytes; + } + else { + size_bytes += bytes; + } + } + + size_t total_bytes{}; + while (total_bytes < size) { + ssize_t bytes = ::send(socket_fd, buffer.c_str() + total_bytes, buffer.size() - total_bytes, 0); + if (bytes == -1) { + return bytes; + } else { + total_bytes += bytes; + } + } + return buffer.size(); } }; @@ -252,99 +290,3 @@ namespace glz } }; } - - -/*template -void read(Callback&& callback) -{ - std::string buffer('\0', 1024); - - uint64_t size{}; - ssize_t size_bytes{}; - while (size_bytes < 8) { - const ssize_t bytes = ::recv(int(socket_fd), (char*)(&size) + size_bytes, sizeof(uint64_t) - size_bytes, 0); - if (bytes > 0) { - size_bytes += bytes; - continue; - } - else if (bytes == 0) { - continue; // connection has been closed - } - else { - if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - else { - return; // error - } - } - } - - if (size_bytes > 8) { - return; // error - } - - size_t total_bytes{}; - while (total_bytes < size) { - const ssize_t bytes = ::recv(int(socket_fd), buffer.data() + total_bytes, buffer.size() - total_bytes, 0); - if (bytes > 0) { - total_bytes += bytes; - continue; - } - else if (bytes == 0) { - continue; // connection has been closed - } - else { - if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - else { - return; // error - } - } - } - callback(buffer); -} - -void write(const std::string& data) -{ - const uint64_t size = data.size(); - - ssize_t size_bytes{}; - while (size_bytes < 8) { - const ssize_t bytes = ::send(int(socket_fd), (char*)(&size) + size_bytes, sizeof(uint64_t) - size_bytes, 0); - if (bytes > 0) { - size_bytes += bytes; - } - else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; - } - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - if (size_bytes > 8) { - return; // error - } - - size_t total_bytes = 0; - while (total_bytes < size) { - const ssize_t bytes = - ::send(int(socket_fd), data.data() + total_bytes, data.size() - total_bytes, 0); - if (bytes > 0) { - total_bytes += bytes; - } - else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; - } - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - if (total_bytes != size) { - // error - } -} -*/ diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 63df9449e9..d7293b54ac 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -22,8 +22,8 @@ suite make_server = [] { const auto ec = server.async_accept([&](glz::socket&& client) { std::cout << "New client connected!\n"; - //std::string message = "Welcome!"; - //client.write(message); + std::string message = "Welcome!"; + client.write(message); // Change this to a std::condition_variable while (server.active) { @@ -66,6 +66,10 @@ suite socket_test = [] { /*socket.read([](const std::string& received) { std::cout << "Received: " << received << std::endl; });*/ + + std::string received{}; + socket.read(received); + std::cout << "Received: " << received << std::endl; std::string message = "Hello World"; socket.write(message); From 02527155b0582cbc2e8ab02b492596a4bd9a6164 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 12:27:06 -0500 Subject: [PATCH 017/309] updates --- include/glaze/socket/socket.hpp | 21 ++++++++++++--------- tests/socket_test/socket_test.cpp | 14 ++++++++------ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index f2599f33d4..b7fafe4b06 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -26,6 +26,7 @@ #define SOCKET int #endif +#include #include #include #include @@ -44,7 +45,7 @@ namespace glz {}; } - inline void wsa_startup() + inline void windows_startup() { static std::once_flag flag{}; std::call_once(flag, [] { @@ -54,6 +55,12 @@ namespace glz #endif }); } + + inline void windows_cleanup() { +#ifdef _WIN32 + WSACleanup(); +#endif + } enum ip_error { none = 0, socket_connect_failed = 1001, socket_bind_failed = 1002 }; @@ -99,11 +106,10 @@ namespace glz #endif*/ } - socket() { wsa_startup(); } + socket() = default; socket(SOCKET fd) : socket_fd(fd) { - wsa_startup(); set_non_blocking(socket_fd); } @@ -112,9 +118,6 @@ namespace glz if (socket_fd != -1) { CLOSESOCKET(socket_fd); } -#ifdef _WIN32 - WSACleanup(); -#endif } std::error_code connect(const std::string& address, int port) @@ -242,15 +245,15 @@ namespace glz destructor(Func&& f) : destroy(std::forward(f)) {} }; + + inline static std::atomic active = true; struct server final { int port{}; glz::pool threads{1}; - std::atomic active = true; - - destructor on_destruct{[this] { active = false; }}; + destructor on_destruct{[] { active = false; }}; using AcceptCallback = std::function; diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index d7293b54ac..1f5752c146 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -26,11 +26,7 @@ suite make_server = [] { client.write(message); // Change this to a std::condition_variable - while (server.active) { - /*client.read([](const std::string& received) { - std::cout << "Received from client: " << received << std::endl; - });*/ - + while (glz::active) { std::string received{}; client.read(received); std::cout << "Received from client: " << received << std::endl; @@ -78,4 +74,10 @@ suite socket_test = [] { std::this_thread::sleep_for(std::chrono::seconds(60)); }; -int main() { return 0; } +int main() { + std::signal(SIGINT, [](int){ + glz::active = false; + std::exit(0); + }); + return 0; +} From 98daed63083cfed14eb595e0a6593d33029f55d7 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 12:28:38 -0500 Subject: [PATCH 018/309] Update socket_test.cpp --- tests/socket_test/socket_test.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 1f5752c146..c38225a840 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -43,7 +43,9 @@ suite make_server = [] { } // Keep thread alive - std::this_thread::sleep_for(std::chrono::hours(1)); + while (glz::active) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } }); // Allow the socket to get started @@ -71,7 +73,9 @@ suite socket_test = [] { socket.write(message); } - std::this_thread::sleep_for(std::chrono::seconds(60)); + while (glz::active) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } }; int main() { From a3780423c84a7c4ba014b7ec08a42aa459433ac9 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 12:35:00 -0500 Subject: [PATCH 019/309] Update socket_test.cpp --- tests/socket_test/socket_test.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index c38225a840..63fa06b42a 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -73,9 +73,8 @@ suite socket_test = [] { socket.write(message); } - while (glz::active) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } + std::cin.get(); + glz::active = false; }; int main() { From f306822120299cdafa58ce354709b4fba6e7f848 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 14:14:48 -0500 Subject: [PATCH 020/309] using size in REPE --- include/glaze/rpc/repe.hpp | 3 +- include/glaze/socket/socket.hpp | 82 +++++++++++++++++++++---------- tests/socket_test/socket_test.cpp | 8 +-- 3 files changed, 61 insertions(+), 32 deletions(-) diff --git a/include/glaze/rpc/repe.hpp b/include/glaze/rpc/repe.hpp index 2081aa980a..014e3b7316 100644 --- a/include/glaze/rpc/repe.hpp +++ b/include/glaze/rpc/repe.hpp @@ -22,6 +22,7 @@ namespace glz::repe std::variant id{}; // an identifier static constexpr uint8_t version = 0; // the REPE version uint8_t error = 0; // 0 denotes no error (boolean: 0 or 1) + int64_t size = -1; // -1 denotes no size provided // Action: These booleans are packed into the a uint8_t action in the REPE header bool notify{}; // no response returned @@ -48,7 +49,7 @@ struct glz::meta return action; }; static constexpr auto value = - glz::array(&T::version, &T::error, custom, &T::method, &T::id); + glz::array(&T::version, &T::error, &T::size, custom, &T::method, &T::id); }; namespace glz::repe diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index b7fafe4b06..39ab2033eb 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -36,6 +36,7 @@ #include #include "glaze/thread/threadpool.hpp" +#include "glaze/rpc/repe.hpp" namespace glz { @@ -176,21 +177,10 @@ namespace glz ssize_t read(std::string& buffer) { - uint64_t size{}; - size_t size_bytes{}; - while (size_bytes < 8) { - ssize_t bytes = ::recv(socket_fd, (char*)(&size) + size_bytes, sizeof(uint64_t) - size_bytes, 0); - if (bytes == -1) { - buffer.clear(); - return bytes; - } - else { - size_bytes += bytes; - } - } - - buffer.resize(size); + buffer.resize(4096); // allocate enough bytes for reading size from header + uint64_t size = (std::numeric_limits::max)(); size_t total_bytes{}; + bool size_obtained = false; while (total_bytes < size) { ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, buffer.size() - total_bytes, 0); if (bytes == -1) { @@ -199,33 +189,71 @@ namespace glz } else { total_bytes += bytes; } + + if (total_bytes > 17 && not size_obtained) { + //std::string json{}; + //std::ignore = glz::beve_to_json(std::string_view{buffer.data(), 34}, json); + //std::cout << std::format("JSON: {}\n", json); + std::tuple> header{}; + const auto ec = glz::read(header, buffer); + if (ec) { + // error + } + size = std::get<2>(std::get<0>(header)); // reading size from header + buffer.resize(size); + size_obtained = true; + } } return buffer.size(); } ssize_t write(const std::string& buffer) { - uint64_t size = buffer.size(); - size_t size_bytes{}; - while (size_bytes < 8) { - ssize_t bytes = ::send(socket_fd, (char*)(&size) + size_bytes, sizeof(uint64_t) - size_bytes, 0); + const size_t size = buffer.size(); + size_t total_bytes{}; + while (total_bytes < size) { + ssize_t bytes = ::send(socket_fd, buffer.data() + total_bytes, buffer.size() - total_bytes, 0); if (bytes == -1) { return bytes; } else { - size_bytes += bytes; + total_bytes += bytes; } } + return buffer.size(); + } + + template + ssize_t read_value(T&& value) + { + static thread_local std::string buffer{}; // TODO: use a buffer pool - size_t total_bytes{}; - while (total_bytes < size) { - ssize_t bytes = ::send(socket_fd, buffer.c_str() + total_bytes, buffer.size() - total_bytes, 0); - if (bytes == -1) { - return bytes; - } else { - total_bytes += bytes; - } + read(buffer); + + const auto ec = glz::read_binary(std::forward_as_tuple(repe::header{}, std::forward(value)), buffer); + if (ec) { + // error + } + + return buffer.size(); + } + + template + ssize_t write_value(T&& value) + { + static thread_local std::string buffer{}; // TODO: use a buffer pool + + const auto ec = glz::write_binary(std::forward_as_tuple(repe::header{}, std::forward(value)), buffer); + if (ec) { + // error } + + // write into location of size + const uint64_t size = buffer.size(); + std::memcpy(buffer.data() + 9, &size, sizeof(uint64_t)); + + write(buffer); + return buffer.size(); } }; diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 63fa06b42a..8ccfa077af 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -23,12 +23,12 @@ suite make_server = [] { std::cout << "New client connected!\n"; std::string message = "Welcome!"; - client.write(message); + client.write_value(message); // Change this to a std::condition_variable while (glz::active) { std::string received{}; - client.read(received); + client.read_value(received); std::cout << "Received from client: " << received << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -66,11 +66,11 @@ suite socket_test = [] { });*/ std::string received{}; - socket.read(received); + socket.read_value(received); std::cout << "Received: " << received << std::endl; std::string message = "Hello World"; - socket.write(message); + socket.write_value(message); } std::cin.get(); From be9c4f959c7472f7b34b432310180463e5197df9 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 14:40:08 -0500 Subject: [PATCH 021/309] updates --- include/glaze/socket/socket.hpp | 10 ++++++++-- tests/socket_test/socket_test.cpp | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 39ab2033eb..fa82f3d612 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -35,7 +35,6 @@ #include #include -#include "glaze/thread/threadpool.hpp" #include "glaze/rpc/repe.hpp" namespace glz @@ -279,7 +278,7 @@ namespace glz struct server final { int port{}; - glz::pool threads{1}; + std::vector threads{}; // TODO: Remove dead clients destructor on_destruct{[] { active = false; }}; @@ -313,6 +312,13 @@ namespace glz } } std::this_thread::sleep_for(std::chrono::milliseconds(10)); + threads.erase(std::partition(threads.begin(), threads.end(), [](auto& thread) { + if (thread.joinable()) { + thread.join(); + return false; + } + return true; + }), threads.end()); } }).detach(); } diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 8ccfa077af..857399ad38 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -25,11 +25,11 @@ suite make_server = [] { std::string message = "Welcome!"; client.write_value(message); - // Change this to a std::condition_variable + // TODO: Change this to a std::condition_variable while (glz::active) { std::string received{}; client.read_value(received); - std::cout << "Received from client: " << received << std::endl; + std::cout << std::format("Received from client: {}\n", received); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } From ac2600a4518dc01865c5a5e6b4abe8141fc951fc Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 14:52:20 -0500 Subject: [PATCH 022/309] multiple clients --- include/glaze/socket/socket.hpp | 3 ++ tests/socket_test/socket_test.cpp | 49 ++++++++++++++++++------------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index fa82f3d612..e71470aa44 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -279,6 +279,8 @@ namespace glz { int port{}; std::vector threads{}; // TODO: Remove dead clients + + std::mutex mtx{}; destructor on_destruct{[] { active = false; }}; @@ -300,6 +302,7 @@ namespace glz sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); // As long as we're not calling accept on the same port we are safe + std::unique_lock lock{mtx}; SOCKET client_fd = ::accept(accept_socket->socket_fd, (sockaddr*)&client_addr, &client_len); if (client_fd != -1) { threads.emplace_back([callback = std::move(callback), client_fd] { diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 857399ad38..179ee75ca6 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -22,8 +22,7 @@ suite make_server = [] { const auto ec = server.async_accept([&](glz::socket&& client) { std::cout << "New client connected!\n"; - std::string message = "Welcome!"; - client.write_value(message); + client.write_value("Welcome!"); // TODO: Change this to a std::condition_variable while (glz::active) { @@ -53,24 +52,34 @@ suite make_server = [] { }; suite socket_test = [] { - glz::socket socket{}; - - if (socket.connect("127.0.0.1", 8080)) { - std::cerr << "Failed to connect to server.\n"; - } - else { - std::cout << "Connected to server!\n"; - - /*socket.read([](const std::string& received) { - std::cout << "Received: " << received << std::endl; - });*/ - - std::string received{}; - socket.read_value(received); - std::cout << "Received: " << received << std::endl; - - std::string message = "Hello World"; - socket.write_value(message); + + constexpr auto n_clients = 10; + std::vector sockets(n_clients); + std::vector> threads(n_clients); + + for (size_t id{}; id < n_clients; ++id) + { + threads.emplace_back(std::async([id, &sockets]{ + glz::socket& socket = sockets[id]; + + if (socket.connect("127.0.0.1", 8080)) { + std::cerr << "Failed to connect to server.\n"; + } + else { + std::cout << "Connected to server!\n"; + + std::string received{}; + socket.read_value(received); + std::cout << "Received: " << received << std::endl; + + size_t tick{}; + while (glz::active) { + socket.write_value(std::format("Client {}, {}", id, tick)); + std::this_thread::sleep_for(std::chrono::seconds(2)); + ++tick; + } + } + })); } std::cin.get(); From eb82b6b40f711d6bd79e8e264d015c5c0c12d43c Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 15:09:19 -0500 Subject: [PATCH 023/309] thread erasing --- include/glaze/socket/socket.hpp | 15 ++++++--------- tests/socket_test/socket_test.cpp | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index e71470aa44..25ce024411 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -34,6 +34,7 @@ #include #include #include +#include #include "glaze/rpc/repe.hpp" @@ -278,9 +279,7 @@ namespace glz struct server final { int port{}; - std::vector threads{}; // TODO: Remove dead clients - - std::mutex mtx{}; + std::vector> threads{}; // TODO: Remove dead clients destructor on_destruct{[] { active = false; }}; @@ -302,12 +301,11 @@ namespace glz sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); // As long as we're not calling accept on the same port we are safe - std::unique_lock lock{mtx}; SOCKET client_fd = ::accept(accept_socket->socket_fd, (sockaddr*)&client_addr, &client_len); if (client_fd != -1) { - threads.emplace_back([callback = std::move(callback), client_fd] { + threads.emplace_back(std::async([callback = std::move(callback), client_fd] { callback(socket{client_fd}); - }); + })); } else { if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { @@ -315,9 +313,8 @@ namespace glz } } std::this_thread::sleep_for(std::chrono::milliseconds(10)); - threads.erase(std::partition(threads.begin(), threads.end(), [](auto& thread) { - if (thread.joinable()) { - thread.join(); + threads.erase(std::partition(threads.begin(), threads.end(), [](auto& future) { + if (auto status = future.wait_for(std::chrono::milliseconds(0)); status == std::future_status::ready) { return false; } return true; diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 179ee75ca6..f0809595cb 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -70,7 +70,7 @@ suite socket_test = [] { std::string received{}; socket.read_value(received); - std::cout << "Received: " << received << std::endl; + std::cout << std::format("Received: {}\n", received); size_t tick{}; while (glz::active) { From f34dd2a7094669656005078e6a37b34356edcb76 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 15:15:08 -0500 Subject: [PATCH 024/309] Update socket_test.cpp --- tests/socket_test/socket_test.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index f0809595cb..b6d089f08b 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -19,7 +19,7 @@ suite make_server = [] { server_thread = std::async([] { glz::server server{8080}; - const auto ec = server.async_accept([&](glz::socket&& client) { + const auto ec = server.async_accept([](glz::socket&& client) { std::cout << "New client connected!\n"; client.write_value("Welcome!"); @@ -28,7 +28,7 @@ suite make_server = [] { while (glz::active) { std::string received{}; client.read_value(received); - std::cout << std::format("Received from client: {}\n", received); + std::cout << std::format("Server: {}\n", received); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } @@ -46,9 +46,6 @@ suite make_server = [] { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } }); - - // Allow the socket to get started - std::this_thread::sleep_for(std::chrono::milliseconds(100)); }; suite socket_test = [] { From c4c919cb89697d40048d1d0207e88617e1c8d225 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 15:24:28 -0500 Subject: [PATCH 025/309] disconnect --- include/glaze/socket/socket.hpp | 1 + tests/socket_test/socket_test.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 25ce024411..35ca887aa3 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -117,6 +117,7 @@ namespace glz ~socket() { if (socket_fd != -1) { + write_value("disconnect"); CLOSESOCKET(socket_fd); } } diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index b6d089f08b..2dd246a900 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -28,6 +28,12 @@ suite make_server = [] { while (glz::active) { std::string received{}; client.read_value(received); + + if (received == "disconnect") { + std::cout << std::format("Client Disconnecting\n"); + break; + } + std::cout << std::format("Server: {}\n", received); std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -70,7 +76,7 @@ suite socket_test = [] { std::cout << std::format("Received: {}\n", received); size_t tick{}; - while (glz::active) { + while (glz::active && tick < 3) { socket.write_value(std::format("Client {}, {}", id, tick)); std::this_thread::sleep_for(std::chrono::seconds(2)); ++tick; From 2021f31676d517b52d0a4f98e8a756e4c0bc8730 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 15:33:50 -0500 Subject: [PATCH 026/309] updates --- include/glaze/socket/socket.hpp | 26 ++++++++++++++------------ tests/socket_test/socket_test.cpp | 4 +--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 35ca887aa3..7565a71b7f 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -96,22 +96,22 @@ namespace glz { SOCKET socket_fd{-1}; - void set_non_blocking(SOCKET /*fd*/) + void set_non_blocking() { -/*#ifdef _WIN32 +#ifdef _WIN32 u_long mode = 1; - ioctlsocket(fd, FIONBIO, &mode); + ioctlsocket(socket_fd, FIONBIO, &mode); #else - int flags = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, flags | O_NONBLOCK); -#endif*/ + int flags = fcntl(socket_fd, F_GETFL, 0); + fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); +#endif } socket() = default; socket(SOCKET fd) : socket_fd(fd) { - set_non_blocking(socket_fd); + //set_non_blocking(); } ~socket() @@ -138,7 +138,7 @@ namespace glz return {ip_error::socket_connect_failed, ip_error_category::instance()}; } - set_non_blocking(socket_fd); + //set_non_blocking(); return {}; } @@ -170,7 +170,7 @@ namespace glz return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - set_non_blocking(socket_fd); + //set_non_blocking(); no_delay(); return {}; @@ -281,6 +281,7 @@ namespace glz { int port{}; std::vector> threads{}; // TODO: Remove dead clients + std::future acceptor_thread{}; destructor on_destruct{[] { active = false; }}; @@ -296,8 +297,9 @@ namespace glz return {ip_error::socket_bind_failed, ip_error_category::instance()}; } else { - std::thread([this, accept_socket = std::move(accept_socket), - callback = std::forward(callback)]() { + acceptor_thread = std::async([this, accept_socket = std::move(accept_socket), + callback = std::forward(callback)] { + //accept_socket->set_non_blocking(); while (active) { sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); @@ -321,7 +323,7 @@ namespace glz return true; }), threads.end()); } - }).detach(); + }); } return {}; diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 2dd246a900..1f8580d770 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -69,11 +69,9 @@ suite socket_test = [] { std::cerr << "Failed to connect to server.\n"; } else { - std::cout << "Connected to server!\n"; - std::string received{}; socket.read_value(received); - std::cout << std::format("Received: {}\n", received); + std::cout << std::format("Received from server: {}\n", received); size_t tick{}; while (glz::active && tick < 3) { From 71cc925ca5540616c44875b3ac0bf00891f7ec8b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 15:53:25 -0500 Subject: [PATCH 027/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 7565a71b7f..44c9334b35 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -283,7 +283,12 @@ namespace glz std::vector> threads{}; // TODO: Remove dead clients std::future acceptor_thread{}; - destructor on_destruct{[] { active = false; }}; + destructor on_destruct{[this] { + active = false; + // connect to self to trigger accept return -1 so that we can close + glz::socket local{}; + local.connect("127.0.0.1", port); + }}; using AcceptCallback = std::function; @@ -304,7 +309,7 @@ namespace glz sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); // As long as we're not calling accept on the same port we are safe - SOCKET client_fd = ::accept(accept_socket->socket_fd, (sockaddr*)&client_addr, &client_len); + auto client_fd = ::accept(accept_socket->socket_fd, (sockaddr*)&client_addr, &client_len); if (client_fd != -1) { threads.emplace_back(std::async([callback = std::move(callback), client_fd] { callback(socket{client_fd}); From 7337f2efaac2079eb0cec83aaad25c3a3c324098 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 16:00:42 -0500 Subject: [PATCH 028/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 44c9334b35..369182d963 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -88,9 +88,6 @@ namespace glz } } }; - - template - concept is_callback = std::invocable || std::invocable; struct socket { From df69e72ead9f657df79621f95adf0c5f099351dc Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 16:03:59 -0500 Subject: [PATCH 029/309] async_accept -> accept --- include/glaze/socket/socket.hpp | 50 ++++++++++++++----------------- tests/socket_test/socket_test.cpp | 14 +++------ 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 369182d963..3957f03323 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -278,7 +278,6 @@ namespace glz { int port{}; std::vector> threads{}; // TODO: Remove dead clients - std::future acceptor_thread{}; destructor on_destruct{[this] { active = false; @@ -290,7 +289,7 @@ namespace glz using AcceptCallback = std::function; template - std::error_code async_accept(AcceptCallback&& callback) + std::error_code accept(AcceptCallback&& callback) { std::unique_ptr accept_socket = std::make_unique(); @@ -299,33 +298,30 @@ namespace glz return {ip_error::socket_bind_failed, ip_error_category::instance()}; } else { - acceptor_thread = std::async([this, accept_socket = std::move(accept_socket), - callback = std::forward(callback)] { - //accept_socket->set_non_blocking(); - while (active) { - sockaddr_in client_addr; - socklen_t client_len = sizeof(client_addr); - // As long as we're not calling accept on the same port we are safe - auto client_fd = ::accept(accept_socket->socket_fd, (sockaddr*)&client_addr, &client_len); - if (client_fd != -1) { - threads.emplace_back(std::async([callback = std::move(callback), client_fd] { - callback(socket{client_fd}); - })); - } - else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; - } + //accept_socket->set_non_blocking(); + while (active) { + sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + // As long as we're not calling accept on the same port we are safe + auto client_fd = ::accept(accept_socket->socket_fd, (sockaddr*)&client_addr, &client_len); + if (client_fd != -1) { + threads.emplace_back(std::async([callback = std::move(callback), client_fd] { + callback(socket{client_fd}); + })); + } + else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - threads.erase(std::partition(threads.begin(), threads.end(), [](auto& future) { - if (auto status = future.wait_for(std::chrono::milliseconds(0)); status == std::future_status::ready) { - return false; - } - return true; - }), threads.end()); } - }); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + threads.erase(std::partition(threads.begin(), threads.end(), [](auto& future) { + if (auto status = future.wait_for(std::chrono::milliseconds(0)); status == std::future_status::ready) { + return false; + } + return true; + }), threads.end()); + } } return {}; diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 1f8580d770..a23eb582b4 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -19,12 +19,14 @@ suite make_server = [] { server_thread = std::async([] { glz::server server{8080}; - const auto ec = server.async_accept([](glz::socket&& client) { + std::cout << std::format("Server started on port: {}\n", server.port); + + const auto ec = server.accept([](glz::socket&& client) { std::cout << "New client connected!\n"; client.write_value("Welcome!"); - // TODO: Change this to a std::condition_variable + // TODO: Change this to a std::condition_variable??? while (glz::active) { std::string received{}; client.read_value(received); @@ -43,14 +45,6 @@ suite make_server = [] { if (ec) { std::cerr << ec.message() << '\n'; } - else { - std::cout << std::format("Server started on port: {}\n", server.port); - } - - // Keep thread alive - while (glz::active) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } }); }; From a18f3173d6fcf7ae907c7ad487c679bad7a53e0a Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 16:06:08 -0500 Subject: [PATCH 030/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 49 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 3957f03323..bc6d6f9039 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -291,37 +291,36 @@ namespace glz template std::error_code accept(AcceptCallback&& callback) { - std::unique_ptr accept_socket = std::make_unique(); + glz::socket accept_socket{}; - const auto ec = accept_socket->bind_and_listen(port); + const auto ec = accept_socket.bind_and_listen(port); if (ec) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - else { - //accept_socket->set_non_blocking(); - while (active) { - sockaddr_in client_addr; - socklen_t client_len = sizeof(client_addr); - // As long as we're not calling accept on the same port we are safe - auto client_fd = ::accept(accept_socket->socket_fd, (sockaddr*)&client_addr, &client_len); - if (client_fd != -1) { - threads.emplace_back(std::async([callback = std::move(callback), client_fd] { - callback(socket{client_fd}); - })); - } - else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; - } + + //accept_socket->set_non_blocking(); + while (active) { + sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + // As long as we're not calling accept on the same port we are safe + auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); + if (client_fd != -1) { + threads.emplace_back(std::async([callback = std::move(callback), client_fd] { + callback(socket{client_fd}); + })); + } + else { + if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { + break; } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - threads.erase(std::partition(threads.begin(), threads.end(), [](auto& future) { - if (auto status = future.wait_for(std::chrono::milliseconds(0)); status == std::future_status::ready) { - return false; - } - return true; - }), threads.end()); } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + threads.erase(std::partition(threads.begin(), threads.end(), [](auto& future) { + if (auto status = future.wait_for(std::chrono::milliseconds(0)); status == std::future_status::ready) { + return false; + } + return true; + }), threads.end()); } return {}; From 279581e1d4ac84e0dc02f78ffe05b61ba475a7aa Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 16:08:10 -0500 Subject: [PATCH 031/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index bc6d6f9039..9fdbb23294 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -286,8 +286,6 @@ namespace glz local.connect("127.0.0.1", port); }}; - using AcceptCallback = std::function; - template std::error_code accept(AcceptCallback&& callback) { @@ -298,7 +296,6 @@ namespace glz return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - //accept_socket->set_non_blocking(); while (active) { sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); From 8ab4709dbb53cd38cbcd4d3ab81fc140c4050ed1 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 16:23:30 -0500 Subject: [PATCH 032/309] updates --- include/glaze/socket/socket.hpp | 14 +++++++------- tests/socket_test/socket_test.cpp | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 9fdbb23294..26890f9928 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -279,11 +279,8 @@ namespace glz int port{}; std::vector> threads{}; // TODO: Remove dead clients - destructor on_destruct{[this] { + destructor on_destruct{[] { active = false; - // connect to self to trigger accept return -1 so that we can close - glz::socket local{}; - local.connect("127.0.0.1", port); }}; template @@ -302,13 +299,16 @@ namespace glz // As long as we're not calling accept on the same port we are safe auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); if (client_fd != -1) { - threads.emplace_back(std::async([callback = std::move(callback), client_fd] { + threads.emplace_back(std::async([callback, client_fd] { callback(socket{client_fd}); })); } else { - if (SOCKET_ERROR_CODE != EWOULDBLOCK && SOCKET_ERROR_CODE != EAGAIN) { - break; + if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { + // keep polling + } + else { + break; // error } } std::this_thread::sleep_for(std::chrono::milliseconds(10)); diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index a23eb582b4..fa0affed3d 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -46,6 +46,8 @@ suite make_server = [] { std::cerr << ec.message() << '\n'; } }); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); }; suite socket_test = [] { From d931f1256f4afbfaf36a121a5391589c35f126d8 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 17 Jun 2024 16:43:52 -0500 Subject: [PATCH 033/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 26890f9928..1b6424ef37 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -189,9 +189,6 @@ namespace glz } if (total_bytes > 17 && not size_obtained) { - //std::string json{}; - //std::ignore = glz::beve_to_json(std::string_view{buffer.data(), 34}, json); - //std::cout << std::format("JSON: {}\n", json); std::tuple> header{}; const auto ec = glz::read(header, buffer); if (ec) { From edc7f312fbbb36d4fb422679e05404520c512396 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Tue, 18 Jun 2024 14:19:00 -0500 Subject: [PATCH 034/309] Added macro, INVALID_SOCKET -1 on macOS and Linux, and (~0) on Windows where it is unsigned.\nAdded 'using ssize_t = int64_t;' which is not available on Windows. --- include/glaze/socket/socket.hpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 1b6424ef37..230b84ca8b 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -4,11 +4,14 @@ #pragma once #ifdef _WIN32 +#define NOMINMAX +#include #include #include #pragma comment(lib, "Ws2_32.lib") #define CLOSESOCKET closesocket #define SOCKET_ERROR_CODE WSAGetLastError() +using ssize_t = int64_t; #else #include #include @@ -24,6 +27,7 @@ #define CLOSESOCKET close #define SOCKET_ERROR_CODE errno #define SOCKET int +#define SOCKET_ERROR (-1) #endif #include @@ -91,7 +95,7 @@ namespace glz struct socket { - SOCKET socket_fd{-1}; + SOCKET socket_fd{INVALID_SOCKET}; void set_non_blocking() { @@ -128,7 +132,7 @@ namespace glz sockaddr_in server_addr; server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(port); + server_addr.sin_port = htons(uint16_t(port)); inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); if (::connect(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { @@ -157,7 +161,7 @@ namespace glz sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; - server_addr.sin_port = htons(port); + server_addr.sin_port = htons(uint16_t(port)); if (::bind(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; @@ -180,7 +184,7 @@ namespace glz size_t total_bytes{}; bool size_obtained = false; while (total_bytes < size) { - ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, buffer.size() - total_bytes, 0); + ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, uint16_t(buffer.size() - total_bytes), 0); if (bytes == -1) { buffer.clear(); return bytes; @@ -207,7 +211,7 @@ namespace glz const size_t size = buffer.size(); size_t total_bytes{}; while (total_bytes < size) { - ssize_t bytes = ::send(socket_fd, buffer.data() + total_bytes, buffer.size() - total_bytes, 0); + ssize_t bytes = ::send(socket_fd, buffer.data() + total_bytes, uint16_t(buffer.size() - total_bytes), 0); if (bytes == -1) { return bytes; } From 295768a9eb2010238d87c74e34cdf1b01b8e8134 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Tue, 18 Jun 2024 14:42:27 -0500 Subject: [PATCH 035/309] Refactored socket error handling and message generation functions: - Introduced get_ip_port to format IP address and port from sockaddr_in. - Implemented get_socket_error_message to retrieve error messages based on platform (Windows or POSIX). - Added socket_api_error_category_t for custom error category handling with optional details. - Refactored get_socket_error and check_status to use standardized error_code for socket operations. --- include/glaze/socket/socket.hpp | 90 +++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 230b84ca8b..1dd27c2361 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -44,6 +44,96 @@ using ssize_t = int64_t; namespace glz { + inline std::string get_ip_port(const sockaddr_in& server_addr) + { + char ip_str[INET_ADDRSTRLEN]{}; + +#ifdef _WIN32 + inet_ntop(AF_INET, &(server_addr.sin_addr), ip_str, INET_ADDRSTRLEN); +#else + inet_ntop(AF_INET, &(server_addr.sin_addr), ip_str, sizeof(ip_str)); +#endif + + return {std::format("{}:{}", ip_str, ntohs(server_addr.sin_port))}; + } + + inline std::string get_socket_error_message(int err) + { +#ifdef _WIN32 + + char* msg = nullptr; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, + err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL); + std::string message(msg); + LocalFree(msg); + return {message}; + +#else + return strerror(err); +#endif + } + + struct socket_api_error_category_t final : public std::error_category + { + std::string what{}; + const char* name() const noexcept override { return "socket error"; } + std::string message(int ev) const override + { + if (what.empty()) { + return {get_socket_error_message(ev)}; + } + else { + return {std::format("{}\nDetails: {}", what, get_socket_error_message(ev))}; + } + } + void operator()(int ev, const std::string_view w) + { + what = w; + this->message(ev); + } + }; + + inline const socket_api_error_category_t& socket_api_error_category(const std::string_view what) + { + static socket_api_error_category_t singleton; + singleton.what = what; + return singleton; + } + + inline std::error_code get_socket_error(const std::string_view what = "") + { +#ifdef _WIN32 + int err = WSAGetLastError(); +#else + int err = errno; +#endif + + if (what.empty()) + return {std::error_code(err, socket_api_error_category(what))}; + else + return {std::error_code(err, socket_api_error_category(what))}; + } + + inline std::error_code check_status(int ec = -1 /*assume error*/, const std::string_view what = "") + { + if (ec >= 0) { + return {std::error_code{}}; + } + + return {get_socket_error(what)}; + } + + // Example: + // + // std::error_code ec = check_status(result, "connect failed"); + // + // if (ec) { + // std::cerr << get_socket_error(std::format("Failed to connect to socket at address: {}.\nIs the server running?", ip_port)).message(); + // } + // else { + // std::cout << "Connected successfully!"; + // } + namespace ip { struct opts From 85e8b13374d1322fa5df45a35ac7af28adfebecf Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Tue, 18 Jun 2024 15:16:33 -0500 Subject: [PATCH 036/309] Added windows_socket_startup_t RAII class for Windows platform. --- include/glaze/socket/socket.hpp | 106 ++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 47 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 1dd27c2361..6789f11b30 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -5,9 +5,10 @@ #ifdef _WIN32 #define NOMINMAX -#include #include #include + +#include #pragma comment(lib, "Ws2_32.lib") #define CLOSESOCKET closesocket #define SOCKET_ERROR_CODE WSAGetLastError() @@ -33,12 +34,12 @@ using ssize_t = int64_t; #include #include #include +#include #include #include #include #include #include -#include #include "glaze/rpc/repe.hpp" @@ -124,11 +125,12 @@ namespace glz } // Example: - // + // // std::error_code ec = check_status(result, "connect failed"); - // + // // if (ec) { - // std::cerr << get_socket_error(std::format("Failed to connect to socket at address: {}.\nIs the server running?", ip_port)).message(); + // std::cerr << get_socket_error(std::format("Failed to connect to socket at address: {}.\nIs the server + // running?", ip_port)).message(); // } // else { // std::cout << "Connected successfully!"; @@ -140,22 +142,32 @@ namespace glz {}; } - inline void windows_startup() + // Note: WSAStartup and WSACleanup must be run from same thread. + // + struct windows_socket_startup_t final { - static std::once_flag flag{}; - std::call_once(flag, [] { -#ifdef _WIN32 - WSADATA wsaData; - WSAStartup(MAKEWORD(2, 2), &wsaData); -#endif - }); - } - - inline void windows_cleanup() { -#ifdef _WIN32 - WSACleanup(); +#ifdef _WIN64 + WSADATA wsa_data_{}; + + inline std::error_code windows_startup() + { + static std::once_flag flag{}; + std::error_code startup_error{}; + std::call_once(flag, [this, &startup_error]() { + constexpr auto win_sock_version = MAKEWORD(2, 2); // Request Winsock version 2.2 + int result = WSAStartup(win_sock_version, &wsa_data_); + if (result != 0) { + startup_error = get_socket_error("Unable initialize win socket library."); + } + }); + return startup_error; + } + + ~windows_socket_startup_t() { WSACleanup(); } +#else + inline std::error_code windows_startup() { return std::error_code{}; } #endif - } + }; enum ip_error { none = 0, socket_connect_failed = 1001, socket_bind_failed = 1002 }; @@ -202,7 +214,7 @@ namespace glz socket(SOCKET fd) : socket_fd(fd) { - //set_non_blocking(); + // set_non_blocking(); } ~socket() @@ -229,7 +241,7 @@ namespace glz return {ip_error::socket_connect_failed, ip_error_category::instance()}; } - //set_non_blocking(); + // set_non_blocking(); return {}; } @@ -261,7 +273,7 @@ namespace glz return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - //set_non_blocking(); + // set_non_blocking(); no_delay(); return {}; @@ -278,10 +290,11 @@ namespace glz if (bytes == -1) { buffer.clear(); return bytes; - } else { + } + else { total_bytes += bytes; } - + if (total_bytes > 17 && not size_obtained) { std::tuple> header{}; const auto ec = glz::read(header, buffer); @@ -311,38 +324,38 @@ namespace glz } return buffer.size(); } - + template ssize_t read_value(T&& value) { static thread_local std::string buffer{}; // TODO: use a buffer pool - + read(buffer); - + const auto ec = glz::read_binary(std::forward_as_tuple(repe::header{}, std::forward(value)), buffer); if (ec) { // error } - + return buffer.size(); } - + template ssize_t write_value(T&& value) { static thread_local std::string buffer{}; // TODO: use a buffer pool - + const auto ec = glz::write_binary(std::forward_as_tuple(repe::header{}, std::forward(value)), buffer); if (ec) { // error } - + // write into location of size const uint64_t size = buffer.size(); std::memcpy(buffer.data() + 9, &size, sizeof(uint64_t)); - + write(buffer); - + return buffer.size(); } }; @@ -362,7 +375,7 @@ namespace glz destructor(Func&& f) : destroy(std::forward(f)) {} }; - + inline static std::atomic active = true; struct server final @@ -370,9 +383,7 @@ namespace glz int port{}; std::vector> threads{}; // TODO: Remove dead clients - destructor on_destruct{[] { - active = false; - }}; + destructor on_destruct{[] { active = false; }}; template std::error_code accept(AcceptCallback&& callback) @@ -383,16 +394,14 @@ namespace glz if (ec) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - + while (active) { sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); // As long as we're not calling accept on the same port we are safe auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); if (client_fd != -1) { - threads.emplace_back(std::async([callback, client_fd] { - callback(socket{client_fd}); - })); + threads.emplace_back(std::async([callback, client_fd] { callback(socket{client_fd}); })); } else { if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { @@ -403,12 +412,15 @@ namespace glz } } std::this_thread::sleep_for(std::chrono::milliseconds(10)); - threads.erase(std::partition(threads.begin(), threads.end(), [](auto& future) { - if (auto status = future.wait_for(std::chrono::milliseconds(0)); status == std::future_status::ready) { - return false; - } - return true; - }), threads.end()); + threads.erase(std::partition(threads.begin(), threads.end(), + [](auto& future) { + if (auto status = future.wait_for(std::chrono::milliseconds(0)); + status == std::future_status::ready) { + return false; + } + return true; + }), + threads.end()); } return {}; From 05a31dbf3e32c4426eac718b0fc86aad336b1bdd Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Thu, 20 Jun 2024 08:13:30 -0500 Subject: [PATCH 037/309] Updated with corrections to error handling code. --- include/glaze/socket/socket.hpp | 85 +++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 19 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 6789f11b30..c28293c73e 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -45,6 +45,12 @@ using ssize_t = int64_t; namespace glz { + namespace ip + { + struct opts + {}; + } + inline std::string get_ip_port(const sockaddr_in& server_addr) { char ip_str[INET_ADDRSTRLEN]{}; @@ -109,13 +115,10 @@ namespace glz int err = errno; #endif - if (what.empty()) - return {std::error_code(err, socket_api_error_category(what))}; - else - return {std::error_code(err, socket_api_error_category(what))}; + return {std::error_code(err, socket_api_error_category(what))}; } - inline std::error_code check_status(int ec = -1 /*assume error*/, const std::string_view what = "") + inline std::error_code check_status(int ec, const std::string_view what = "") { if (ec >= 0) { return {std::error_code{}}; @@ -136,36 +139,78 @@ namespace glz // std::cout << "Connected successfully!"; // } - namespace ip + // For Windows WSASocket Compatability + + inline constexpr uint16_t make_version(uint8_t low_byte, uint8_t high_byte) { - struct opts - {}; + return static_cast(low_byte) | (static_cast(high_byte) << 8); + } + + inline constexpr uint8_t major_version(uint16_t version) + { + return static_cast(version & 0xFF); // Extract the low byte + } + + inline constexpr uint8_t minor_version(uint16_t version) + { + return static_cast((version >> 8) & 0xFF); // Shift right by 8 bits and extract the low byte + } + + // Function to get Winsock version string on Windows, return "na" otherwise + inline std::string get_winsock_version_string(uint32_t version = make_version(2, 2)) + { +#if _WIN32 + BYTE major = major_version(uint16_t(version)); + BYTE minor = minor_version(uint16_t(version)); + return std::format("{}.{}", static_cast(major), static_cast(minor)); +#else + return "na"; // Default behavior for non-Windows platforms +#endif } - // Note: WSAStartup and WSACleanup must be run from same thread. + // The 'wsa_startup_t' calls the windows WSAStartup function. This must be the first Windows + // Sockets function called by an application or DLL. It allows an application or DLL to + // specify the version of Windows Sockets required and retrieve details of the specific + // Windows Sockets implementation.The application or DLL can only issue further Windows Sockets + // functions after successfully calling WSAStartup. // - struct windows_socket_startup_t final + // Important: WSAStartup and its corresponding WSACleanup must be called on the same thread. + // + template + struct wsa_startup_t final { #ifdef _WIN64 - WSADATA wsa_data_{}; + WSADATA wsa_data{}; + + std::error_code error_code{}; - inline std::error_code windows_startup() + inline std::error_code start(const WORD win_sock_version = make_version(2, 2)) // Request latest Winsock version 2.2 { static std::once_flag flag{}; std::error_code startup_error{}; - std::call_once(flag, [this, &startup_error]() { - constexpr auto win_sock_version = MAKEWORD(2, 2); // Request Winsock version 2.2 - int result = WSAStartup(win_sock_version, &wsa_data_); + std::call_once(flag, [this, win_sock_version, &startup_error]() { + int result = WSAStartup(win_sock_version, &wsa_data); if (result != 0) { - startup_error = get_socket_error("Unable initialize win socket library."); + error_code = get_socket_error( + std::format("Unable to initialize Winsock library version {}.", get_winsock_version_string())); } }); - return startup_error; + return {error_code}; + } + + wsa_startup_t() + { + if constexpr (run_wsa_startup) { + error_code = start(); + } + } + + ~wsa_startup_t() { + WSACleanup(); } - ~windows_socket_startup_t() { WSACleanup(); } #else - inline std::error_code windows_startup() { return std::error_code{}; } + inline std::error_code startup() { return {std::error_code{}}; } #endif }; @@ -197,6 +242,8 @@ namespace glz struct socket { + wsa_startup_t<> wsa; // wsa_startup (ignored on macOS and Linux) + SOCKET socket_fd{INVALID_SOCKET}; void set_non_blocking() From ea228a35e677cccfea0a8ae4881fc111ade556f8 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Thu, 20 Jun 2024 09:45:13 -0500 Subject: [PATCH 038/309] Added working clients counter to inform when threads have competed work. --- tests/socket_test/socket_test.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index fa0affed3d..3c50a35d17 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -15,9 +15,15 @@ using namespace ut; std::future server_thread{}; +inline constexpr auto n_clients = 10; +inline constexpr auto service_0_port{8080}; +inline constexpr auto service_0_ip{"127.0.0.1"}; + +static std::atomic_int working_clients = n_clients; + suite make_server = [] { server_thread = std::async([] { - glz::server server{8080}; + glz::server server{service_0_port}; std::cout << std::format("Server started on port: {}\n", server.port); @@ -28,7 +34,9 @@ suite make_server = [] { // TODO: Change this to a std::condition_variable??? while (glz::active) { + std::string received{}; + client.read_value(received); if (received == "disconnect") { @@ -52,17 +60,15 @@ suite make_server = [] { suite socket_test = [] { - constexpr auto n_clients = 10; std::vector sockets(n_clients); std::vector> threads(n_clients); - for (size_t id{}; id < n_clients; ++id) { threads.emplace_back(std::async([id, &sockets]{ glz::socket& socket = sockets[id]; - if (socket.connect("127.0.0.1", 8080)) { - std::cerr << "Failed to connect to server.\n"; + if (socket.connect(service_0_ip, service_0_port)) { + std::cerr << std::format("Failed to connect to server.\nDetails: {}", glz::get_socket_error().message()); } else { std::string received{}; @@ -75,10 +81,14 @@ suite socket_test = [] { std::this_thread::sleep_for(std::chrono::seconds(2)); ++tick; } + --working_clients; } })); } + while (working_clients > 0) std::this_thread::yield(); + + std::cout << "\nFinished! Press any key to exit."; std::cin.get(); glz::active = false; }; From 3b3e2e3ae061a9463821b7ad6464f067bd5130ef Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 09:58:04 -0500 Subject: [PATCH 039/309] using GLZ_ for macros --- include/glaze/socket/socket.hpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index c28293c73e..283c91f2e9 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -4,13 +4,15 @@ #pragma once #ifdef _WIN32 +#define GLZ_INVALID_SOCKET INVALID_SOCKET +#define GLZ_SOCKET GLZ_SOCKET #define NOMINMAX #include #include #include #pragma comment(lib, "Ws2_32.lib") -#define CLOSESOCKET closesocket +#define GLZ_CLOSESOCKET closesocket #define SOCKET_ERROR_CODE WSAGetLastError() using ssize_t = int64_t; #else @@ -25,10 +27,11 @@ using ssize_t = int64_t; #include #include -#define CLOSESOCKET close +#define GLZ_CLOSESOCKET close #define SOCKET_ERROR_CODE errno -#define SOCKET int +#define GLZ_SOCKET int #define SOCKET_ERROR (-1) +#define GLZ_INVALID_SOCKET (-1) #endif #include @@ -164,7 +167,8 @@ namespace glz BYTE minor = minor_version(uint16_t(version)); return std::format("{}.{}", static_cast(major), static_cast(minor)); #else - return "na"; // Default behavior for non-Windows platforms + (void)version; + return ""; // Default behavior for non-Windows platforms #endif } @@ -244,7 +248,7 @@ namespace glz { wsa_startup_t<> wsa; // wsa_startup (ignored on macOS and Linux) - SOCKET socket_fd{INVALID_SOCKET}; + GLZ_SOCKET socket_fd{GLZ_INVALID_SOCKET}; void set_non_blocking() { @@ -259,7 +263,7 @@ namespace glz socket() = default; - socket(SOCKET fd) : socket_fd(fd) + socket(GLZ_SOCKET fd) : socket_fd(fd) { // set_non_blocking(); } @@ -268,7 +272,7 @@ namespace glz { if (socket_fd != -1) { write_value("disconnect"); - CLOSESOCKET(socket_fd); + GLZ_CLOSESOCKET(socket_fd); } } From 1d0d033a12e76779989221da848dfef82cdbad08 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 10:00:07 -0500 Subject: [PATCH 040/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 283c91f2e9..5e06c6f5ec 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -5,7 +5,7 @@ #ifdef _WIN32 #define GLZ_INVALID_SOCKET INVALID_SOCKET -#define GLZ_SOCKET GLZ_SOCKET +#define GLZ_SOCKET SOCKET #define NOMINMAX #include #include From d4ed0eb4b75a37af2b0fd7bed5e8c4a7ada534b6 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 10:02:48 -0500 Subject: [PATCH 041/309] using std::latch --- tests/socket_test/socket_test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 3c50a35d17..6dc2a8bd4a 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -19,7 +19,7 @@ inline constexpr auto n_clients = 10; inline constexpr auto service_0_port{8080}; inline constexpr auto service_0_ip{"127.0.0.1"}; -static std::atomic_int working_clients = n_clients; +static std::latch working_clients{n_clients}; suite make_server = [] { server_thread = std::async([] { @@ -81,12 +81,12 @@ suite socket_test = [] { std::this_thread::sleep_for(std::chrono::seconds(2)); ++tick; } - --working_clients; + working_clients.count_down(); } })); } - while (working_clients > 0) std::this_thread::yield(); + working_clients.arrive_and_wait(); std::cout << "\nFinished! Press any key to exit."; std::cin.get(); From f46aa1ebc5f3df049e1b73f16b0e43868e4e7ee6 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 10:04:35 -0500 Subject: [PATCH 042/309] Update socket_test.cpp --- tests/socket_test/socket_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 6dc2a8bd4a..31f025d759 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -67,8 +67,8 @@ suite socket_test = [] { threads.emplace_back(std::async([id, &sockets]{ glz::socket& socket = sockets[id]; - if (socket.connect(service_0_ip, service_0_port)) { - std::cerr << std::format("Failed to connect to server.\nDetails: {}", glz::get_socket_error().message()); + if (socket.connect(service_0_ip, 90)) { + std::cerr << std::format("Failed to connect to server.\nDetails: {}\n", glz::get_socket_error().message()); } else { std::string received{}; From f959b925537fafa40b0ac608fca78a365f84353f Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 10:04:55 -0500 Subject: [PATCH 043/309] fix port --- tests/socket_test/socket_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 31f025d759..4c600f5574 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -67,7 +67,7 @@ suite socket_test = [] { threads.emplace_back(std::async([id, &sockets]{ glz::socket& socket = sockets[id]; - if (socket.connect(service_0_ip, 90)) { + if (socket.connect(service_0_ip, service_0_port)) { std::cerr << std::format("Failed to connect to server.\nDetails: {}\n", glz::get_socket_error().message()); } else { From 31fa55569169153de3f6fdbf5fe71f1c1ff41926 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 10:06:28 -0500 Subject: [PATCH 044/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 5e06c6f5ec..c6ad7e63bd 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -146,17 +146,17 @@ namespace glz inline constexpr uint16_t make_version(uint8_t low_byte, uint8_t high_byte) { - return static_cast(low_byte) | (static_cast(high_byte) << 8); + return uint16_t(low_byte) | (uint16_t(high_byte) << 8); } inline constexpr uint8_t major_version(uint16_t version) { - return static_cast(version & 0xFF); // Extract the low byte + return uint8_t(version & 0xFF); // Extract the low byte } inline constexpr uint8_t minor_version(uint16_t version) { - return static_cast((version >> 8) & 0xFF); // Shift right by 8 bits and extract the low byte + return uint8_t((version >> 8) & 0xFF); // Shift right by 8 bits and extract the low byte } // Function to get Winsock version string on Windows, return "na" otherwise From 3a4725627bfcfb54d3a83df106dc7d9d2d7d6c04 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 10:22:43 -0500 Subject: [PATCH 045/309] Attempt to use Xcode 15.4 --- .github/workflows/clang.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index f7c5115522..96f45910c0 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -23,6 +23,9 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Set up Xcode 15.4 + run: sudo xcode-select -s /Applications/Xcode_15.4.app + - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} From 3e91119afea743b8d3a458861f13f21887bc36b3 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 10:45:01 -0500 Subject: [PATCH 046/309] cleanup --- include/glaze/socket/socket.hpp | 8 +++----- tests/socket_test/socket_test.cpp | 16 +++++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index c6ad7e63bd..7cebe62aac 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -181,14 +181,14 @@ namespace glz // Important: WSAStartup and its corresponding WSACleanup must be called on the same thread. // template - struct wsa_startup_t final + struct windows_socket_startup_t final { #ifdef _WIN64 WSADATA wsa_data{}; std::error_code error_code{}; - inline std::error_code start(const WORD win_sock_version = make_version(2, 2)) // Request latest Winsock version 2.2 + std::error_code start(const WORD win_sock_version = make_version(2, 2)) // Request latest Winsock version 2.2 { static std::once_flag flag{}; std::error_code startup_error{}; @@ -214,7 +214,7 @@ namespace glz } #else - inline std::error_code startup() { return {std::error_code{}}; } + std::error_code start() { return std::error_code{}; } #endif }; @@ -246,8 +246,6 @@ namespace glz struct socket { - wsa_startup_t<> wsa; // wsa_startup (ignored on macOS and Linux) - GLZ_SOCKET socket_fd{GLZ_INVALID_SOCKET}; void set_non_blocking() diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 4c600f5574..a05e9a2976 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -13,14 +13,18 @@ using namespace ut; +constexpr bool user_input = false; + std::future server_thread{}; -inline constexpr auto n_clients = 10; -inline constexpr auto service_0_port{8080}; -inline constexpr auto service_0_ip{"127.0.0.1"}; +constexpr auto n_clients = 10; +constexpr auto service_0_port{8080}; +constexpr auto service_0_ip{"127.0.0.1"}; static std::latch working_clients{n_clients}; +glz::windows_socket_startup_t<> wsa; // wsa_startup (ignored on macOS and Linux) + suite make_server = [] { server_thread = std::async([] { glz::server server{service_0_port}; @@ -88,8 +92,10 @@ suite socket_test = [] { working_clients.arrive_and_wait(); - std::cout << "\nFinished! Press any key to exit."; - std::cin.get(); + if constexpr (user_input) { + std::cout << "\nFinished! Press any key to exit."; + std::cin.get(); + } glz::active = false; }; From e5aa968dd93254f89fcfb0bb991d9607f4af07ed Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Thu, 20 Jun 2024 10:58:52 -0500 Subject: [PATCH 047/309] std::latch does not work on windows, therefore working_clients counter was changed back to a std::atomic_int. --- include/glaze/socket/socket.hpp | 4 ++-- tests/socket_test/socket_test.cpp | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 7cebe62aac..4edf40d0e1 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -202,14 +202,14 @@ namespace glz return {error_code}; } - wsa_startup_t() + windows_socket_startup_t() { if constexpr (run_wsa_startup) { error_code = start(); } } - ~wsa_startup_t() { + ~windows_socket_startup_t() { WSACleanup(); } diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index a05e9a2976..3bbf390ea4 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -21,7 +21,9 @@ constexpr auto n_clients = 10; constexpr auto service_0_port{8080}; constexpr auto service_0_ip{"127.0.0.1"}; -static std::latch working_clients{n_clients}; +// std::latch is broken on MSVC: +//std::latch working_clients{n_clients}; +static std::atomic_int working_clients{n_clients}; glz::windows_socket_startup_t<> wsa; // wsa_startup (ignored on macOS and Linux) @@ -85,17 +87,21 @@ suite socket_test = [] { std::this_thread::sleep_for(std::chrono::seconds(2)); ++tick; } - working_clients.count_down(); + //working_clients.count_down(); + --working_clients; + } })); } - working_clients.arrive_and_wait(); + //working_clients.arrive_and_wait(); + while (working_clients) std::this_thread::yield(); if constexpr (user_input) { std::cout << "\nFinished! Press any key to exit."; std::cin.get(); } + glz::active = false; }; From dec888b009019e041d6d6b985d62495334ef09b6 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 11:07:45 -0500 Subject: [PATCH 048/309] asynchronous accept --- include/glaze/socket/socket.hpp | 3 +++ tests/socket_test/socket_test.cpp | 9 +++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 4edf40d0e1..29d89ca0f7 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -384,6 +384,7 @@ namespace glz const auto ec = glz::read_binary(std::forward_as_tuple(repe::header{}, std::forward(value)), buffer); if (ec) { // error + return 0; } return buffer.size(); @@ -443,6 +444,8 @@ namespace glz if (ec) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } + + accept_socket.set_non_blocking(); while (active) { sockaddr_in client_addr; diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 3bbf390ea4..1d879922a8 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -40,19 +40,16 @@ suite make_server = [] { // TODO: Change this to a std::condition_variable??? while (glz::active) { - std::string received{}; - client.read_value(received); if (received == "disconnect") { std::cout << std::format("Client Disconnecting\n"); break; } - - std::cout << std::format("Server: {}\n", received); - - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + else if (received.size()) { + std::cout << std::format("Server: {}\n", received); + } } }); From 6834d71d1e679c654abd00ab1888c16f75f0445a Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 13:23:50 -0500 Subject: [PATCH 049/309] using polling and asynchronous sockets --- include/glaze/socket/socket.hpp | 134 +++++++++++++++++++++++------- tests/socket_test/socket_test.cpp | 2 +- 2 files changed, 107 insertions(+), 29 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 29d89ca0f7..58983ecab6 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -34,6 +34,12 @@ using ssize_t = int64_t; #define GLZ_INVALID_SOCKET (-1) #endif +#if defined(__APPLE__) +#include // for kqueue on macOS +#elif defined(__linux__) +#include // for epoll on Linux +#endif + #include #include #include @@ -209,16 +215,21 @@ namespace glz } } - ~windows_socket_startup_t() { - WSACleanup(); - } + ~windows_socket_startup_t() { WSACleanup(); } #else std::error_code start() { return std::error_code{}; } #endif }; - enum ip_error { none = 0, socket_connect_failed = 1001, socket_bind_failed = 1002 }; + enum ip_error { + none = 0, + queue_create_failed, + event_ctl_failed, + event_wait_failed, + socket_connect_failed = 1001, + socket_bind_failed = 1002 + }; struct ip_error_category : public std::error_category { @@ -234,6 +245,12 @@ namespace glz { using enum ip_error; switch (static_cast(ec)) { + case queue_create_failed: + return "queue_create_failed"; + case event_ctl_failed: + return "event_ctl_failed"; + case event_wait_failed: + return "event_wait_failed"; case socket_connect_failed: return "socket_connect_failed"; case socket_bind_failed: @@ -261,10 +278,7 @@ namespace glz socket() = default; - socket(GLZ_SOCKET fd) : socket_fd(fd) - { - // set_non_blocking(); - } + socket(GLZ_SOCKET fd) : socket_fd(fd) { set_non_blocking(); } ~socket() { @@ -290,7 +304,7 @@ namespace glz return {ip_error::socket_connect_failed, ip_error_category::instance()}; } - // set_non_blocking(); + set_non_blocking(); return {}; } @@ -322,7 +336,7 @@ namespace glz return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - // set_non_blocking(); + set_non_blocking(); no_delay(); return {}; @@ -337,8 +351,15 @@ namespace glz while (total_bytes < size) { ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, uint16_t(buffer.size() - total_bytes), 0); if (bytes == -1) { - buffer.clear(); - return bytes; + if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + else { + // error + buffer.clear(); + return 0; + } } else { total_bytes += bytes; @@ -365,7 +386,14 @@ namespace glz while (total_bytes < size) { ssize_t bytes = ::send(socket_fd, buffer.data() + total_bytes, uint16_t(buffer.size() - total_bytes), 0); if (bytes == -1) { - return bytes; + if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { + std::this_thread::yield(); + continue; + } + else { + // error + return bytes; + } } else { total_bytes += bytes; @@ -444,26 +472,75 @@ namespace glz if (ec) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - - accept_socket.set_non_blocking(); + +#if defined(__APPLE__) + int event_fd = ::kqueue(); +#elif defined(__linux__) + int event_fd = ::epoll_create1(0); +#endif + + if (event_fd == -1) { + return {ip_error::queue_create_failed, ip_error_category::instance()}; + } + + struct kevent change; +#if defined(__APPLE__) + EV_SET(&change, accept_socket.socket_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, nullptr); + + if (::kevent(event_fd, &change, 1, nullptr, 0, nullptr) == -1) { + close(event_fd); + return {ip_error::event_ctl_failed, ip_error_category::instance()}; + } +#elif defined(__linux__) + ev.events = EPOLLIN; + ev.data.fd = accept_socket.socket_fd; + + if (epoll_ctl(event_fd, EPOLL_CTL_ADD, accept_socket.socket_fd, &ev) == -1) { + close(event_fd); + return {ip_error::event_ctl_failed, ip_error_category::instance()}; + } +#endif + +#if defined(__APPLE__) + std::vector events(16); +#elif defined(__linux__) + std::vector epoll_events(16); +#endif while (active) { - sockaddr_in client_addr; - socklen_t client_len = sizeof(client_addr); - // As long as we're not calling accept on the same port we are safe - auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); - if (client_fd != -1) { - threads.emplace_back(std::async([callback, client_fd] { callback(socket{client_fd}); })); + int n{}; + +#if defined(__APPLE__) || defined(__MACH__) + struct timespec timeout + { + 0, 10000000 + }; // 10ms + n = ::kevent(event_fd, nullptr, 0, events.data(), static_cast(events.size()), &timeout); +#elif defined(__linux__) + n = ::epoll_wait(event_fd, epoll_events.data(), static_cast(epoll_events.size()), 10); +#endif + + if (n == -1) { + if (errno == EINTR) continue; + close(event_fd); + return {ip_error::event_wait_failed, ip_error_category::instance()}; } - else { - if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { - // keep polling - } - else { - break; // error + + for (int i = 0; i < n; ++i) { +#if defined(__APPLE__) || defined(__MACH__) + if (events[i].ident == uintptr_t(accept_socket.socket_fd) && events[i].filter == EVFILT_READ) { +#elif defined(__linux__) + if (epoll_events[i].data.fd == accept_socket.socket_fd && epoll_events[i].events & EPOLLIN) { +#endif + sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); + if (client_fd != -1) { + threads.emplace_back(std::async([callback, client_fd] { callback(socket{client_fd}); })); + } } } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + threads.erase(std::partition(threads.begin(), threads.end(), [](auto& future) { if (auto status = future.wait_for(std::chrono::milliseconds(0)); @@ -475,6 +552,7 @@ namespace glz threads.end()); } + GLZ_CLOSESOCKET(event_fd); return {}; } }; diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 1d879922a8..84a8540c07 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -92,7 +92,7 @@ suite socket_test = [] { } //working_clients.arrive_and_wait(); - while (working_clients) std::this_thread::yield(); + while (working_clients) std::this_thread::sleep_for(std::chrono::milliseconds(1)); if constexpr (user_input) { std::cout << "\nFinished! Press any key to exit."; From 3f5b3e82990d53784ec2af7a54b96abadbabff50 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 13:29:47 -0500 Subject: [PATCH 050/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 58983ecab6..3803227750 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -492,6 +492,7 @@ namespace glz return {ip_error::event_ctl_failed, ip_error_category::instance()}; } #elif defined(__linux__) + struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = accept_socket.socket_fd; @@ -510,7 +511,7 @@ namespace glz while (active) { int n{}; -#if defined(__APPLE__) || defined(__MACH__) +#if defined(__APPLE__) struct timespec timeout { 0, 10000000 From 18c6690d1f3d1f3dd2d7c5e30b1dbedb21c63073 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 13:31:13 -0500 Subject: [PATCH 051/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 3803227750..1e3c004743 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -482,9 +482,9 @@ namespace glz if (event_fd == -1) { return {ip_error::queue_create_failed, ip_error_category::instance()}; } - - struct kevent change; + #if defined(__APPLE__) + struct kevent change; EV_SET(&change, accept_socket.socket_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, nullptr); if (::kevent(event_fd, &change, 1, nullptr, 0, nullptr) == -1) { From ae79066b40006934fe0f1d96d2efc13b3472798e Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 14:04:04 -0500 Subject: [PATCH 052/309] Adding windows support --- include/glaze/socket/socket.hpp | 57 ++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 1e3c004743..0543f65baa 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -6,6 +6,9 @@ #ifdef _WIN32 #define GLZ_INVALID_SOCKET INVALID_SOCKET #define GLZ_SOCKET SOCKET +#define GLZ_INVALID_EVENT WSA_INVALID_EVENT +#define GLZ_WAIT_RESULT_TYPE DWORD +#define GLZ_WAIT_FAILED WSA_WAIT_FAILED #define NOMINMAX #include #include @@ -13,6 +16,7 @@ #include #pragma comment(lib, "Ws2_32.lib") #define GLZ_CLOSESOCKET closesocket +#define GLZ_EVENT_CLOSE WSACloseEvent #define SOCKET_ERROR_CODE WSAGetLastError() using ssize_t = int64_t; #else @@ -28,6 +32,10 @@ using ssize_t = int64_t; #include #define GLZ_CLOSESOCKET close +#define GLZ_EVENT_CLOSE close +#define GLZ_WAIT_RESULT_TYPE int +#define GLZ_WAIT_FAILED (-1) +#define GLZ_INVALID_EVENT (-1) #define SOCKET_ERROR_CODE errno #define GLZ_SOCKET int #define SOCKET_ERROR (-1) @@ -472,14 +480,16 @@ namespace glz if (ec) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - + #if defined(__APPLE__) int event_fd = ::kqueue(); #elif defined(__linux__) int event_fd = ::epoll_create1(0); +#elif defined(_WIN32) + HANDLE event_fd = WSACreateEvent(); #endif - if (event_fd == -1) { + if (event_fd == GLZ_INVALID_EVENT) { return {ip_error::queue_create_failed, ip_error_category::instance()}; } @@ -500,6 +510,11 @@ namespace glz close(event_fd); return {ip_error::event_ctl_failed, ip_error_category::instance()}; } +#elif defined(_WIN32) + if (WSAEventSelect(accept_socket, event_fd, FD_ACCEPT) == SOCKET_ERROR) { + WSACloseEvent(event_fd); + return {ip_error::event_ctl_failed, ip_error_category::instance()}; + } #endif #if defined(__APPLE__) @@ -509,7 +524,7 @@ namespace glz #endif while (active) { - int n{}; + GLZ_WAIT_RESULT_TYPE n{}; #if defined(__APPLE__) struct timespec timeout @@ -519,16 +534,23 @@ namespace glz n = ::kevent(event_fd, nullptr, 0, events.data(), static_cast(events.size()), &timeout); #elif defined(__linux__) n = ::epoll_wait(event_fd, epoll_events.data(), static_cast(epoll_events.size()), 10); +#elif defined(_WIN32) + n = WSAWaitForMultipleEvents(1, &event_fd, FALSE, 10, FALSE); #endif - if (n == -1) { + if (n == GLZ_WAIT_FAILED) { +#if defined(__APPLE__) || defined(__linux__) if (errno == EINTR) continue; - close(event_fd); +#else + if (n == WSA_WAIT_TIMEOUT) continue; +#endif + GLZ_EVENT_CLOSE(event_fd); return {ip_error::event_wait_failed, ip_error_category::instance()}; } +#if defined(__APPLE__) || defined(__linux__) for (int i = 0; i < n; ++i) { -#if defined(__APPLE__) || defined(__MACH__) +#if defined(__APPLE__) if (events[i].ident == uintptr_t(accept_socket.socket_fd) && events[i].filter == EVFILT_READ) { #elif defined(__linux__) if (epoll_events[i].data.fd == accept_socket.socket_fd && epoll_events[i].events & EPOLLIN) { @@ -536,11 +558,30 @@ namespace glz sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); - if (client_fd != -1) { + if (client_fd != GLZ_INVALID_SOCKET) { threads.emplace_back(std::async([callback, client_fd] { callback(socket{client_fd}); })); } } } + +#else // Windows + WSANETWORKEVENTS events; + if (WSAEnumNetworkEvents(accept_socket, event_fd, &events) == SOCKET_ERROR) { + WSACloseEvent(event_fd); + return {ip_error::event_enum_failed, ip_error_category::instance()}; + } + + if (events.lNetworkEvents & FD_ACCEPT) { + if (events.iErrorCode[FD_ACCEPT_BIT] == 0) { + sockaddr_in client_addr; + int client_len = sizeof(client_addr); + SOCKET client_socket = ::accept(accept_socket, (sockaddr*)&client_addr, &client_len); + if (client_socket != GLZ_INVALID_SOCKET) { + threads.emplace_back(std::async([callback, client_socket] { callback(socket{client_socket}); })); + } + } + } +#endif threads.erase(std::partition(threads.begin(), threads.end(), [](auto& future) { @@ -553,7 +594,7 @@ namespace glz threads.end()); } - GLZ_CLOSESOCKET(event_fd); + GLZ_EVENT_CLOSE(event_fd); return {}; } }; From 7db635c522b7b208ae096f21f98fa43da3757ef9 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 14:04:41 -0500 Subject: [PATCH 053/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 0543f65baa..15fbb5cfa2 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -480,7 +480,7 @@ namespace glz if (ec) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - + #if defined(__APPLE__) int event_fd = ::kqueue(); #elif defined(__linux__) @@ -492,7 +492,7 @@ namespace glz if (event_fd == GLZ_INVALID_EVENT) { return {ip_error::queue_create_failed, ip_error_category::instance()}; } - + #if defined(__APPLE__) struct kevent change; EV_SET(&change, accept_socket.socket_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, nullptr); @@ -563,7 +563,7 @@ namespace glz } } } - + #else // Windows WSANETWORKEVENTS events; if (WSAEnumNetworkEvents(accept_socket, event_fd, &events) == SOCKET_ERROR) { From a46b6f2a44040258b065fa1ccbde8b3217016e2b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 14:08:37 -0500 Subject: [PATCH 054/309] event_enum_failed --- include/glaze/socket/socket.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 15fbb5cfa2..cbe9bb2e8f 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -235,6 +235,7 @@ namespace glz queue_create_failed, event_ctl_failed, event_wait_failed, + event_enum_failed, socket_connect_failed = 1001, socket_bind_failed = 1002 }; @@ -259,6 +260,8 @@ namespace glz return "event_ctl_failed"; case event_wait_failed: return "event_wait_failed"; + case event_enum_failed: + return "event_enum_failed"; case socket_connect_failed: return "socket_connect_failed"; case socket_bind_failed: @@ -511,7 +514,7 @@ namespace glz return {ip_error::event_ctl_failed, ip_error_category::instance()}; } #elif defined(_WIN32) - if (WSAEventSelect(accept_socket, event_fd, FD_ACCEPT) == SOCKET_ERROR) { + if (WSAEventSelect(accept_socket.socket_fd, event_fd, FD_ACCEPT) == SOCKET_ERROR) { WSACloseEvent(event_fd); return {ip_error::event_ctl_failed, ip_error_category::instance()}; } From 82ffd46e155a187e1c359283d6e293944f0b3eab Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 14:09:35 -0500 Subject: [PATCH 055/309] client_fd --- include/glaze/socket/socket.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index cbe9bb2e8f..ae2eb10fa3 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -578,9 +578,9 @@ namespace glz if (events.iErrorCode[FD_ACCEPT_BIT] == 0) { sockaddr_in client_addr; int client_len = sizeof(client_addr); - SOCKET client_socket = ::accept(accept_socket, (sockaddr*)&client_addr, &client_len); - if (client_socket != GLZ_INVALID_SOCKET) { - threads.emplace_back(std::async([callback, client_socket] { callback(socket{client_socket}); })); + auto client_fd = ::accept(accept_socket, (sockaddr*)&client_addr, &client_len); + if (client_fd != GLZ_INVALID_SOCKET) { + threads.emplace_back(std::async([callback, client_fd] { callback(socket{client_fd}); })); } } } From 402d25a650f65fed7ce824a6862584637da60c96 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 14:11:09 -0500 Subject: [PATCH 056/309] spawn_socket --- include/glaze/socket/socket.hpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index ae2eb10fa3..7476fd3de3 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -550,6 +550,15 @@ namespace glz GLZ_EVENT_CLOSE(event_fd); return {ip_error::event_wait_failed, ip_error_category::instance()}; } + + auto spawn_socket = [&] { + sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); + if (client_fd != GLZ_INVALID_SOCKET) { + threads.emplace_back(std::async([callback, client_fd] { callback(socket{client_fd}); })); + } + }; #if defined(__APPLE__) || defined(__linux__) for (int i = 0; i < n; ++i) { @@ -558,12 +567,7 @@ namespace glz #elif defined(__linux__) if (epoll_events[i].data.fd == accept_socket.socket_fd && epoll_events[i].events & EPOLLIN) { #endif - sockaddr_in client_addr; - socklen_t client_len = sizeof(client_addr); - auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); - if (client_fd != GLZ_INVALID_SOCKET) { - threads.emplace_back(std::async([callback, client_fd] { callback(socket{client_fd}); })); - } + spawn_socket(); } } @@ -576,12 +580,7 @@ namespace glz if (events.lNetworkEvents & FD_ACCEPT) { if (events.iErrorCode[FD_ACCEPT_BIT] == 0) { - sockaddr_in client_addr; - int client_len = sizeof(client_addr); - auto client_fd = ::accept(accept_socket, (sockaddr*)&client_addr, &client_len); - if (client_fd != GLZ_INVALID_SOCKET) { - threads.emplace_back(std::async([callback, client_fd] { callback(socket{client_fd}); })); - } + spawn_socket(); } } #endif From e697a14e05c832b3460de3e6f4f43ec96f3cd549 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 14:15:37 -0500 Subject: [PATCH 057/309] more cleanup --- include/glaze/socket/socket.hpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 7476fd3de3..39a7d76b81 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -496,29 +496,24 @@ namespace glz return {ip_error::queue_create_failed, ip_error_category::instance()}; } + bool event_setup_failed = false; #if defined(__APPLE__) struct kevent change; EV_SET(&change, accept_socket.socket_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, nullptr); - - if (::kevent(event_fd, &change, 1, nullptr, 0, nullptr) == -1) { - close(event_fd); - return {ip_error::event_ctl_failed, ip_error_category::instance()}; - } + event_setup_failed = ::kevent(event_fd, &change, 1, nullptr, 0, nullptr) == -1; #elif defined(__linux__) struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = accept_socket.socket_fd; - - if (epoll_ctl(event_fd, EPOLL_CTL_ADD, accept_socket.socket_fd, &ev) == -1) { - close(event_fd); - return {ip_error::event_ctl_failed, ip_error_category::instance()}; - } + event_setup_failed = epoll_ctl(event_fd, EPOLL_CTL_ADD, accept_socket.socket_fd, &ev) == -1; #elif defined(_WIN32) - if (WSAEventSelect(accept_socket.socket_fd, event_fd, FD_ACCEPT) == SOCKET_ERROR) { - WSACloseEvent(event_fd); + event_setup_failed = WSAEventSelect(accept_socket.socket_fd, event_fd, FD_ACCEPT) == SOCKET_ERROR; +#endif + + if (event_setup_failed) { + GLZ_EVENT_CLOSE(event_fd); return {ip_error::event_ctl_failed, ip_error_category::instance()}; } -#endif #if defined(__APPLE__) std::vector events(16); From 62aabb12258146318f4000e26c26533ce9bdfbea Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Thu, 20 Jun 2024 14:18:24 -0500 Subject: [PATCH 058/309] Missing accept_socket.socket_fd on Windows build. --- include/glaze/socket/socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 39a7d76b81..59a0532d95 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -568,7 +568,7 @@ namespace glz #else // Windows WSANETWORKEVENTS events; - if (WSAEnumNetworkEvents(accept_socket, event_fd, &events) == SOCKET_ERROR) { + if (WSAEnumNetworkEvents(accept_socket.socket_fd, event_fd, &events) == SOCKET_ERROR) { WSACloseEvent(event_fd); return {ip_error::event_enum_failed, ip_error_category::instance()}; } From 778c9a30692c98ae46cb93ea29ce87e1d460ff43 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 14:22:34 -0500 Subject: [PATCH 059/309] Removing destructor class --- include/glaze/socket/socket.hpp | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 59a0532d95..c9facd9cf4 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -449,22 +449,6 @@ namespace glz } }; - struct destructor final - { - std::function destroy{}; - - ~destructor() - { - if (destroy) { - destroy(); - } - } - - template - destructor(Func&& f) : destroy(std::forward(f)) - {} - }; - inline static std::atomic active = true; struct server final @@ -472,7 +456,9 @@ namespace glz int port{}; std::vector> threads{}; // TODO: Remove dead clients - destructor on_destruct{[] { active = false; }}; + ~server() { + active = false; + } template std::error_code accept(AcceptCallback&& callback) From fe33b5a2c1a3771b06043085130d5167b5960a2a Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 14:35:56 -0500 Subject: [PATCH 060/309] active local to server --- include/glaze/socket/socket.hpp | 6 +++--- tests/socket_test/socket_test.cpp | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index c9facd9cf4..0b8062af12 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -54,6 +54,7 @@ using ssize_t = int64_t; #include #include #include +#include #include #include #include @@ -449,12 +450,11 @@ namespace glz } }; - inline static std::atomic active = true; - struct server final { int port{}; std::vector> threads{}; // TODO: Remove dead clients + std::atomic active = true; ~server() { active = false; @@ -537,7 +537,7 @@ namespace glz socklen_t client_len = sizeof(client_addr); auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); if (client_fd != GLZ_INVALID_SOCKET) { - threads.emplace_back(std::async([callback, client_fd] { callback(socket{client_fd}); })); + threads.emplace_back(std::async([this, callback, client_fd] { callback(socket{client_fd}, active); })); } }; diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 84a8540c07..1a262fb2df 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -27,19 +27,19 @@ static std::atomic_int working_clients{n_clients}; glz::windows_socket_startup_t<> wsa; // wsa_startup (ignored on macOS and Linux) -suite make_server = [] { - server_thread = std::async([] { - glz::server server{service_0_port}; +glz::server server{service_0_port}; +suite make_server = [] { + server_thread = std::async([&] { std::cout << std::format("Server started on port: {}\n", server.port); - const auto ec = server.accept([](glz::socket&& client) { + const auto ec = server.accept([](glz::socket&& client, auto& active) { std::cout << "New client connected!\n"; client.write_value("Welcome!"); // TODO: Change this to a std::condition_variable??? - while (glz::active) { + while (active) { std::string received{}; client.read_value(received); @@ -79,7 +79,7 @@ suite socket_test = [] { std::cout << std::format("Received from server: {}\n", received); size_t tick{}; - while (glz::active && tick < 3) { + while (tick < 3) { socket.write_value(std::format("Client {}, {}", id, tick)); std::this_thread::sleep_for(std::chrono::seconds(2)); ++tick; @@ -99,12 +99,12 @@ suite socket_test = [] { std::cin.get(); } - glz::active = false; + server.active = false; }; int main() { std::signal(SIGINT, [](int){ - glz::active = false; + server.active = false; std::exit(0); }); return 0; From 29d432e8f2374f4c5809790e79b6873efe5823b1 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 14:40:59 -0500 Subject: [PATCH 061/309] Update repe_test.cpp --- tests/repe_test/repe_test.cpp | 88 +++++++++++++++++------------------ 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/tests/repe_test/repe_test.cpp b/tests/repe_test/repe_test.cpp index 5c1193e7f4..a939843ee1 100644 --- a/tests/repe_test/repe_test.cpp +++ b/tests/repe_test/repe_test.cpp @@ -80,28 +80,28 @@ suite structs_of_functions = [] { response = server.call(request.value()); } - expect(response->value() == R"([[0,0,0,"/i",null],55])") << response->value(); + expect(response->value() == R"([[0,0,-1,0,"/i",null],55])") << response->value(); { auto request = repe::request_json({.method = "/i"}, 42); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,2,"/i",null],null])") << response->value(); + expect(response->value() == R"([[0,0,-1,2,"/i",null],null])") << response->value(); { auto request = repe::request_json({"/hello"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,0,"/hello",null],"Hello"])"); + expect(response->value() == R"([[0,0,-1,0,"/hello",null],"Hello"])"); { auto request = repe::request_json({"/get_number"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,0,"/get_number",null],42])"); + expect(response->value() == R"([[0,0,-1,0,"/get_number",null],42])"); }; "nested_structs_of_functions"_test = [] { @@ -118,42 +118,42 @@ suite structs_of_functions = [] { response = server.call(request.value()); } - expect(response->value() == R"([[0,0,2,"/my_functions/void_func",null],null])") << response->value(); + expect(response->value() == R"([[0,0,-1,2,"/my_functions/void_func",null],null])") << response->value(); { auto request = repe::request_json({"/my_functions/hello"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,0,"/my_functions/hello",null],"Hello"])"); + expect(response->value() == R"([[0,0,-1,0,"/my_functions/hello",null],"Hello"])"); { auto request = repe::request_json({"/meta_functions/hello"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,0,"/meta_functions/hello",null],"Hello"])"); + expect(response->value() == R"([[0,0,-1,0,"/meta_functions/hello",null],"Hello"])"); { auto request = repe::request_json({"/append_awesome"}, "you are"); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,0,"/append_awesome",null],"you are awesome!"])"); + expect(response->value() == R"([[0,0,-1,0,"/append_awesome",null],"you are awesome!"])"); { auto request = repe::request_json({"/my_string"}, "Howdy!"); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,2,"/my_string",null],null])"); + expect(response->value() == R"([[0,0,-1,2,"/my_string",null],null])"); { auto request = repe::request_json({"/my_string"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,0,"/my_string",null],"Howdy!"])") << response->value(); + expect(response->value() == R"([[0,0,-1,0,"/my_string",null],"Howdy!"])") << response->value(); obj.my_string.clear(); @@ -163,14 +163,14 @@ suite structs_of_functions = [] { } // we expect an empty string returned because we cleared it - expect(response->value() == R"([[0,0,0,"/my_string",null],""])"); + expect(response->value() == R"([[0,0,-1,0,"/my_string",null],""])"); { auto request = repe::request_json({"/my_functions/max"}, std::vector{1.1, 3.3, 2.25}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,0,"/my_functions/max",null],3.3])") << response->value(); + expect(response->value() == R"([[0,0,-1,0,"/my_functions/max",null],3.3])") << response->value(); { auto request = repe::request_json({"/my_functions"}); @@ -179,7 +179,7 @@ suite structs_of_functions = [] { expect( response->value() == - R"([[0,0,0,"/my_functions",null],{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"}])") + R"([[0,0,-1,0,"/my_functions",null],{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"}])") << response->value(); { @@ -189,7 +189,7 @@ suite structs_of_functions = [] { expect( response->value() == - R"([[0,0,0,"",null],{"my_functions":{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"},"meta_functions":{"hello":"std::function","world":"std::function","get_number":"std::function"},"append_awesome":"std::function","my_string":""}])") + R"([[0,0,-1,0,"",null],{"my_functions":{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"},"meta_functions":{"hello":"std::function","world":"std::function","get_number":"std::function"},"append_awesome":"std::function","my_string":""}])") << response->value(); }; @@ -207,14 +207,14 @@ suite structs_of_functions = [] { response = server.call(request.value()); } - expect(response->value() == R"([[0,0,2,"/name",null],null])") << response->value(); + expect(response->value() == R"([[0,0,-1,2,"/name",null],null])") << response->value(); { auto request = repe::request_json({"/get_name"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,0,"/get_name",null],"Susan"])") << response->value(); + expect(response->value() == R"([[0,0,-1,0,"/get_name",null],"Susan"])") << response->value(); { auto request = repe::request_json({"/get_name"}, "Bob"); @@ -222,7 +222,7 @@ suite structs_of_functions = [] { } expect(obj.name == "Susan"); // we expect the name to not have changed because this function take no inputs - expect(response->value() == R"([[0,0,0,"/get_name",null],"Susan"])") << response->value(); + expect(response->value() == R"([[0,0,-1,0,"/get_name",null],"Susan"])") << response->value(); { auto request = repe::request_json({"/set_name"}, "Bob"); @@ -230,7 +230,7 @@ suite structs_of_functions = [] { } expect(obj.name == "Bob"); - expect(response->value() == R"([[0,0,2,"/set_name",null],null])") << response->value(); + expect(response->value() == R"([[0,0,-1,2,"/set_name",null],null])") << response->value(); { auto request = repe::request_json({"/custom_name"}, "Alice"); @@ -238,7 +238,7 @@ suite structs_of_functions = [] { } expect(obj.name == "Alice"); - expect(response->value() == R"([[0,0,2,"/custom_name",null],null])") << response->value(); + expect(response->value() == R"([[0,0,-1,2,"/custom_name",null],null])") << response->value(); }; }; @@ -261,7 +261,7 @@ suite structs_of_functions_binary = [] { std::string res{}; expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,0,"/i",null],55])") << res; + expect(res == R"([[0,0,-1,0,"/i",null],55])") << res; { auto request = repe::request_binary({.method = "/i"}, 42); @@ -269,7 +269,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,2,"/i",null],null])") << res; + expect(res == R"([[0,0,-1,2,"/i",null],null])") << res; { auto request = repe::request_binary({"/hello"}); @@ -277,7 +277,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,0,"/hello",null],"Hello"])"); + expect(res == R"([[0,0,-1,0,"/hello",null],"Hello"])"); { auto request = repe::request_binary({"/get_number"}); @@ -285,7 +285,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,0,"/get_number",null],42])"); + expect(res == R"([[0,0,-1,0,"/get_number",null],42])"); }; "nested_structs_of_functions"_test = [] { @@ -304,7 +304,7 @@ suite structs_of_functions_binary = [] { std::string res{}; expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,2,"/my_functions/void_func",null],null])") << response; + expect(res == R"([[0,0,-1,2,"/my_functions/void_func",null],null])") << response; { auto request = repe::request_binary({"/my_functions/hello"}); @@ -312,7 +312,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,0,"/my_functions/hello",null],"Hello"])"); + expect(res == R"([[0,0,-1,0,"/my_functions/hello",null],"Hello"])"); { auto request = repe::request_binary({"/meta_functions/hello"}); @@ -320,7 +320,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,0,"/meta_functions/hello",null],"Hello"])"); + expect(res == R"([[0,0,-1,0,"/meta_functions/hello",null],"Hello"])"); { auto request = repe::request_binary({"/append_awesome"}, "you are"); @@ -328,7 +328,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,0,"/append_awesome",null],"you are awesome!"])"); + expect(res == R"([[0,0,-1,0,"/append_awesome",null],"you are awesome!"])"); { auto request = repe::request_binary({"/my_string"}, "Howdy!"); @@ -336,7 +336,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,2,"/my_string",null],null])"); + expect(res == R"([[0,0,-1,2,"/my_string",null],null])"); { auto request = repe::request_binary({"/my_string"}); @@ -344,7 +344,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,0,"/my_string",null],"Howdy!"])") << response; + expect(res == R"([[0,0,-1,0,"/my_string",null],"Howdy!"])") << response; obj.my_string.clear(); @@ -355,7 +355,7 @@ suite structs_of_functions_binary = [] { expect(!glz::beve_to_json(response->value(), res)); // we expect an empty string returned because we cleared it - expect(res == R"([[0,0,0,"/my_string",null],""])"); + expect(res == R"([[0,0,-1,0,"/my_string",null],""])"); { auto request = repe::request_binary({"/my_functions/max"}, std::vector{1.1, 3.3, 2.25}); @@ -363,7 +363,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,0,"/my_functions/max",null],3.3])") << response; + expect(res == R"([[0,0,-1,0,"/my_functions/max",null],3.3])") << response; { auto request = repe::request_binary({"/my_functions"}); @@ -373,7 +373,7 @@ suite structs_of_functions_binary = [] { expect(!glz::beve_to_json(response->value(), res)); expect( res == - R"([[0,0,0,"/my_functions",null],{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"}])") + R"([[0,0,-1,0,"/my_functions",null],{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"}])") << res; { @@ -384,7 +384,7 @@ suite structs_of_functions_binary = [] { expect(!glz::beve_to_json(response->value(), res)); expect( res == - R"([[0,0,0,"",null],{"my_functions":{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"},"meta_functions":{"hello":"std::function","world":"std::function","get_number":"std::function"},"append_awesome":"std::function","my_string":""}])") + R"([[0,0,-1,0,"",null],{"my_functions":{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"},"meta_functions":{"hello":"std::function","world":"std::function","get_number":"std::function"},"append_awesome":"std::function","my_string":""}])") << res; }; @@ -404,7 +404,7 @@ suite structs_of_functions_binary = [] { std::string res{}; expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,2,"/name",null],null])") << response; + expect(res == R"([[0,0,-1,2,"/name",null],null])") << response; { auto request = repe::request_binary({"/get_name"}); @@ -412,7 +412,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,0,"/get_name",null],"Susan"])") << response; + expect(res == R"([[0,0,-1,0,"/get_name",null],"Susan"])") << response; { auto request = repe::request_binary({"/get_name"}, "Bob"); @@ -421,7 +421,7 @@ suite structs_of_functions_binary = [] { expect(!glz::beve_to_json(response->value(), res)); expect(obj.name == "Susan"); // we expect the name to not have changed because this function take no inputs - expect(res == R"([[0,0,0,"/get_name",null],"Susan"])") << res; + expect(res == R"([[0,0,-1,0,"/get_name",null],"Susan"])") << res; { auto request = repe::request_binary({"/set_name"}, "Bob"); @@ -430,7 +430,7 @@ suite structs_of_functions_binary = [] { expect(!glz::beve_to_json(response->value(), res)); expect(obj.name == "Bob"); - expect(res == R"([[0,0,2,"/set_name",null],null])") << response; + expect(res == R"([[0,0,-1,2,"/set_name",null],null])") << response; { auto request = repe::request_binary({"/custom_name"}, "Alice"); @@ -439,7 +439,7 @@ suite structs_of_functions_binary = [] { expect(!glz::beve_to_json(response->value(), res)); expect(obj.name == "Alice"); - expect(res == R"([[0,0,2,"/custom_name",null],null])") << response; + expect(res == R"([[0,0,-1,2,"/custom_name",null],null])") << response; }; }; @@ -470,14 +470,14 @@ suite wrapper_tests = [] { response = server.call(request.value()); } - expect(response->value() == R"([[0,0,2,"/sub/my_functions/void_func",null],null])") << response->value(); + expect(response->value() == R"([[0,0,-1,2,"/sub/my_functions/void_func",null],null])") << response->value(); { auto request = repe::request_json({"/sub/my_functions/hello"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,0,"/sub/my_functions/hello",null],"Hello"])"); + expect(response->value() == R"([[0,0,-1,0,"/sub/my_functions/hello",null],"Hello"])"); }; }; @@ -496,14 +496,14 @@ suite root_tests = [] { response = server.call(request.value()); } - expect(response->value() == R"([[0,0,2,"/sub/my_functions/void_func",null],null])") << response->value(); + expect(response->value() == R"([[0,0,-1,2,"/sub/my_functions/void_func",null],null])") << response->value(); { auto request = repe::request_json({"/sub/my_functions/hello"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,0,"/sub/my_functions/hello",null],"Hello"])"); + expect(response->value() == R"([[0,0,-1,0,"/sub/my_functions/hello",null],"Hello"])"); }; }; @@ -525,7 +525,7 @@ suite wrapper_tests_binary = [] { std::string res{}; expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,2,"/sub/my_functions/void_func",null],null])") << response; + expect(res == R"([[0,0,-1,2,"/sub/my_functions/void_func",null],null])") << response; { auto request = repe::request_binary({"/sub/my_functions/hello"}); @@ -533,7 +533,7 @@ suite wrapper_tests_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,0,"/sub/my_functions/hello",null],"Hello"])"); + expect(res == R"([[0,0,-1,0,"/sub/my_functions/hello",null],"Hello"])"); }; }; From f4788a9bd555dd8454635f5f51ff1b708a63f31c Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 14:43:39 -0500 Subject: [PATCH 062/309] Update clang.yml --- .github/workflows/clang.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 96f45910c0..893c20a1cb 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -26,6 +26,12 @@ jobs: - name: Set up Xcode 15.4 run: sudo xcode-select -s /Applications/Xcode_15.4.app + - name: Set up Clang from Xcode + run: | + export PATH="/Applications/Xcode_15.4.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:$PATH" + export CC=clang + export CXX=clang++ + - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} From 84b1827275408919d1b1bc554045a616e0ce2d46 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 14:45:59 -0500 Subject: [PATCH 063/309] Update clang.yml --- .github/workflows/clang.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 893c20a1cb..6e358c96fb 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -28,10 +28,14 @@ jobs: - name: Set up Clang from Xcode run: | - export PATH="/Applications/Xcode_15.4.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:$PATH" + export DEVELOPER_DIR="/Applications/Xcode_15.4.app/Contents/Developer" + export PATH="${DEVELOPER_DIR}/Toolchains/XcodeDefault.xctoolchain/usr/bin:$PATH" export CC=clang export CXX=clang++ + - name: Verify Clang version + run: clang++ --version + - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} From a09e0e6d4a6cc3260bebe6e0ea0d986326e71f92 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 15:19:34 -0500 Subject: [PATCH 064/309] Update clang.yml --- .github/workflows/clang.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 6e358c96fb..3f9e3b56bb 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -23,15 +23,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Xcode 15.4 - run: sudo xcode-select -s /Applications/Xcode_15.4.app - - - name: Set up Clang from Xcode - run: | - export DEVELOPER_DIR="/Applications/Xcode_15.4.app/Contents/Developer" - export PATH="${DEVELOPER_DIR}/Toolchains/XcodeDefault.xctoolchain/usr/bin:$PATH" - export CC=clang - export CXX=clang++ + - name: Setup Xcode + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable - name: Verify Clang version run: clang++ --version From 2b500902bc5d04af3bf0ad010c79b0c3f6ce343b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 15:21:44 -0500 Subject: [PATCH 065/309] formatting --- include/glaze/socket/socket.hpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 0b8062af12..e47b9fc483 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -456,9 +456,7 @@ namespace glz std::vector> threads{}; // TODO: Remove dead clients std::atomic active = true; - ~server() { - active = false; - } + ~server() { active = false; } template std::error_code accept(AcceptCallback&& callback) @@ -495,7 +493,7 @@ namespace glz #elif defined(_WIN32) event_setup_failed = WSAEventSelect(accept_socket.socket_fd, event_fd, FD_ACCEPT) == SOCKET_ERROR; #endif - + if (event_setup_failed) { GLZ_EVENT_CLOSE(event_fd); return {ip_error::event_ctl_failed, ip_error_category::instance()}; @@ -531,13 +529,14 @@ namespace glz GLZ_EVENT_CLOSE(event_fd); return {ip_error::event_wait_failed, ip_error_category::instance()}; } - + auto spawn_socket = [&] { sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); if (client_fd != GLZ_INVALID_SOCKET) { - threads.emplace_back(std::async([this, callback, client_fd] { callback(socket{client_fd}, active); })); + threads.emplace_back( + std::async([this, callback, client_fd] { callback(socket{client_fd}, active); })); } }; From 8f7fd2d8476de267c2065ecbed09fb60e23f62b0 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 15:26:02 -0500 Subject: [PATCH 066/309] Update clang.yml --- .github/workflows/clang.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 3f9e3b56bb..07038bb720 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -23,7 +23,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup Xcode - uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: latest-stable From 0ac020f56a2b91094384d8092e83072bfef562de Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 15:28:53 -0500 Subject: [PATCH 067/309] xcode 15.3 --- .github/workflows/clang.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 07038bb720..e1830b9b7e 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -25,7 +25,7 @@ jobs: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: '15.3' - name: Verify Clang version run: clang++ --version From 0aa3698f8314478c73d1b920e5a48ac0e43fb591 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 15:30:52 -0500 Subject: [PATCH 068/309] latest-stable on macOS --- .github/workflows/clang.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index e1830b9b7e..b9b4690f13 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -25,10 +25,7 @@ jobs: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '15.3' - - - name: Verify Clang version - run: clang++ --version + xcode-version: latest-stable - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} From 95837722385dab735f2d75785d86c01167a6f6b4 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 15:32:56 -0500 Subject: [PATCH 069/309] sleep before disconnect --- include/glaze/socket/socket.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index e47b9fc483..ae9c918706 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -296,6 +296,7 @@ namespace glz { if (socket_fd != -1) { write_value("disconnect"); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); GLZ_CLOSESOCKET(socket_fd); } } From db2ce31574474002fc096134e81fa2edf2195fb0 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 20 Jun 2024 15:40:38 -0500 Subject: [PATCH 070/309] updates --- include/glaze/socket/socket.hpp | 9 ++++----- tests/socket_test/socket_test.cpp | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index ae9c918706..64360b16e2 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -139,7 +139,7 @@ namespace glz inline std::error_code check_status(int ec, const std::string_view what = "") { if (ec >= 0) { - return {std::error_code{}}; + return {}; } return {get_socket_error(what)}; @@ -159,17 +159,17 @@ namespace glz // For Windows WSASocket Compatability - inline constexpr uint16_t make_version(uint8_t low_byte, uint8_t high_byte) + inline constexpr uint16_t make_version(uint8_t low_byte, uint8_t high_byte) noexcept { return uint16_t(low_byte) | (uint16_t(high_byte) << 8); } - inline constexpr uint8_t major_version(uint16_t version) + inline constexpr uint8_t major_version(uint16_t version) noexcept { return uint8_t(version & 0xFF); // Extract the low byte } - inline constexpr uint8_t minor_version(uint16_t version) + inline constexpr uint8_t minor_version(uint16_t version) noexcept { return uint8_t((version >> 8) & 0xFF); // Shift right by 8 bits and extract the low byte } @@ -296,7 +296,6 @@ namespace glz { if (socket_fd != -1) { write_value("disconnect"); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); GLZ_CLOSESOCKET(socket_fd); } } diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 1a262fb2df..e5cfbda67c 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -38,7 +38,6 @@ suite make_server = [] { client.write_value("Welcome!"); - // TODO: Change this to a std::condition_variable??? while (active) { std::string received{}; client.read_value(received); From ff8fd9f68a4fd9388d41d440480d468658111abf Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 06:44:48 -0500 Subject: [PATCH 071/309] cleaning --- include/glaze/socket/socket.hpp | 8 ++++---- tests/socket_test/socket_test.cpp | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 64360b16e2..59eaa926f9 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -17,7 +17,7 @@ #pragma comment(lib, "Ws2_32.lib") #define GLZ_CLOSESOCKET closesocket #define GLZ_EVENT_CLOSE WSACloseEvent -#define SOCKET_ERROR_CODE WSAGetLastError() +#define GLZ_SOCKET_ERROR_CODE WSAGetLastError() using ssize_t = int64_t; #else #include @@ -36,7 +36,7 @@ using ssize_t = int64_t; #define GLZ_WAIT_RESULT_TYPE int #define GLZ_WAIT_FAILED (-1) #define GLZ_INVALID_EVENT (-1) -#define SOCKET_ERROR_CODE errno +#define GLZ_SOCKET_ERROR_CODE errno #define GLZ_SOCKET int #define SOCKET_ERROR (-1) #define GLZ_INVALID_SOCKET (-1) @@ -363,7 +363,7 @@ namespace glz while (total_bytes < size) { ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, uint16_t(buffer.size() - total_bytes), 0); if (bytes == -1) { - if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { + if (GLZ_SOCKET_ERROR_CODE == EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } @@ -398,7 +398,7 @@ namespace glz while (total_bytes < size) { ssize_t bytes = ::send(socket_fd, buffer.data() + total_bytes, uint16_t(buffer.size() - total_bytes), 0); if (bytes == -1) { - if (SOCKET_ERROR_CODE == EWOULDBLOCK || SOCKET_ERROR_CODE == EAGAIN) { + if (GLZ_SOCKET_ERROR_CODE == EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::yield(); continue; } diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index e5cfbda67c..32e7f993ba 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -97,8 +97,6 @@ suite socket_test = [] { std::cout << "\nFinished! Press any key to exit."; std::cin.get(); } - - server.active = false; }; int main() { @@ -106,5 +104,8 @@ int main() { server.active = false; std::exit(0); }); + + server.active = false; + return 0; } From 28e034206247b4ca2b6e267c5344b25346e2e68e Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 06:48:01 -0500 Subject: [PATCH 072/309] cleanup --- include/glaze/socket/socket.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 59eaa926f9..73dabb8b42 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -300,7 +300,7 @@ namespace glz } } - std::error_code connect(const std::string& address, int port) + [[nodiscard]] std::error_code connect(const std::string& address, const int port) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (socket_fd == -1) { @@ -321,14 +321,14 @@ namespace glz return {}; } - bool no_delay() + [[nodiscard]] bool no_delay() { int flag = 1; int result = setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int)); return result == 0; } - std::error_code bind_and_listen(int port) + [[nodiscard]] std::error_code bind_and_listen(int port) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (socket_fd == -1) { @@ -349,7 +349,9 @@ namespace glz } set_non_blocking(); - no_delay(); + if (not no_delay()) { + return {ip_error::socket_bind_failed, ip_error_category::instance()}; + } return {}; } @@ -459,7 +461,7 @@ namespace glz ~server() { active = false; } template - std::error_code accept(AcceptCallback&& callback) + [[nodiscard]] std::error_code accept(AcceptCallback&& callback) { glz::socket accept_socket{}; From 5ac048caec44372cbca8848216a789b3d587be3c Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 07:05:19 -0500 Subject: [PATCH 073/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 73dabb8b42..8dfe12f105 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -238,7 +238,9 @@ namespace glz event_wait_failed, event_enum_failed, socket_connect_failed = 1001, - socket_bind_failed = 1002 + socket_bind_failed = 1002, + send_failed, + receive_failed }; struct ip_error_category : public std::error_category @@ -267,6 +269,10 @@ namespace glz return "socket_connect_failed"; case socket_bind_failed: return "socket_bind_failed"; + case send_failed: + return "send_failed"; + case receive_failed: + return "receive_failed"; default: return "unknown_error"; } @@ -356,7 +362,7 @@ namespace glz return {}; } - ssize_t read(std::string& buffer) + std::error_code receive(std::string& buffer) { buffer.resize(4096); // allocate enough bytes for reading size from header uint64_t size = (std::numeric_limits::max)(); @@ -372,28 +378,28 @@ namespace glz else { // error buffer.clear(); - return 0; + return {ip_error::receive_failed, ip_error_category::instance()}; } } else { total_bytes += bytes; } - if (total_bytes > 17 && not size_obtained) { + if (not size_obtained && total_bytes > 17) { std::tuple> header{}; const auto ec = glz::read(header, buffer); if (ec) { - // error + return {ip_error::receive_failed, ip_error_category::instance()}; } size = std::get<2>(std::get<0>(header)); // reading size from header buffer.resize(size); size_obtained = true; } } - return buffer.size(); + return {}; } - ssize_t write(const std::string& buffer) + std::error_code send(const std::string& buffer) { const size_t size = buffer.size(); size_t total_bytes{}; @@ -405,15 +411,14 @@ namespace glz continue; } else { - // error - return bytes; + return {ip_error::send_failed, ip_error_category::instance()}; } } else { total_bytes += bytes; } } - return buffer.size(); + return {}; } template @@ -421,7 +426,7 @@ namespace glz { static thread_local std::string buffer{}; // TODO: use a buffer pool - read(buffer); + receive(buffer); const auto ec = glz::read_binary(std::forward_as_tuple(repe::header{}, std::forward(value)), buffer); if (ec) { @@ -446,7 +451,7 @@ namespace glz const uint64_t size = buffer.size(); std::memcpy(buffer.data() + 9, &size, sizeof(uint64_t)); - write(buffer); + send(buffer); return buffer.size(); } From 547e976a05efd92444ec20327df92f9aced6452d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 07:40:21 -0500 Subject: [PATCH 074/309] new header and more error handling --- include/glaze/socket/socket.hpp | 113 +++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 39 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 8dfe12f105..65069bcc57 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -61,6 +61,31 @@ using ssize_t = int64_t; #include "glaze/rpc/repe.hpp" +// New REPE header +// TOD0: update the REPE code +namespace glz +{ + struct Header { + static constexpr size_t max_method_size = 256; + + uint8_t version = 1; // the REPE version + bool error{}; // whether an error has occurred + bool notify{}; // whether this message does not require a response + bool has_body{}; // whether a body is provided + uint32_t reserved1{}; + // --- + uint64_t id{}; // identifier + int64_t body_size = -1; // the total size of the body + uint32_t reserved2{}; + uint16_t reserved3{}; + // --- + uint16_t method_size{}; // the size of the method string + char method[max_method_size]; // the method name + }; + + static_assert((sizeof(Header) - Header::max_method_size) == 32); +} + namespace glz { namespace ip @@ -362,14 +387,14 @@ namespace glz return {}; } - std::error_code receive(std::string& buffer) + [[nodiscard]] std::error_code receive(std::string& buffer) { - buffer.resize(4096); // allocate enough bytes for reading size from header - uint64_t size = (std::numeric_limits::max)(); + // first receive the header + Header header{}; size_t total_bytes{}; - bool size_obtained = false; - while (total_bytes < size) { - ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, uint16_t(buffer.size() - total_bytes), 0); + while (total_bytes < sizeof(Header)) + { + ssize_t bytes = ::recv(socket_fd, &header + total_bytes, size_t(sizeof(Header) - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -381,30 +406,38 @@ namespace glz return {ip_error::receive_failed, ip_error_category::instance()}; } } - else { - total_bytes += bytes; - } - - if (not size_obtained && total_bytes > 17) { - std::tuple> header{}; - const auto ec = glz::read(header, buffer); - if (ec) { + + total_bytes += bytes; + } + + buffer.resize(header.body_size); + total_bytes = 0; + const auto n = size_t(header.body_size); + while (total_bytes < n) { + ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); + if (bytes == -1) { + if (GLZ_SOCKET_ERROR_CODE == EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + else { + // error + buffer.clear(); return {ip_error::receive_failed, ip_error_category::instance()}; } - size = std::get<2>(std::get<0>(header)); // reading size from header - buffer.resize(size); - size_obtained = true; } + + total_bytes += bytes; } return {}; } - std::error_code send(const std::string& buffer) + [[nodiscard]] std::error_code send(const std::string_view buffer) { const size_t size = buffer.size(); size_t total_bytes{}; while (total_bytes < size) { - ssize_t bytes = ::send(socket_fd, buffer.data() + total_bytes, uint16_t(buffer.size() - total_bytes), 0); + ssize_t bytes = ::send(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::yield(); @@ -414,46 +447,48 @@ namespace glz return {ip_error::send_failed, ip_error_category::instance()}; } } - else { - total_bytes += bytes; - } + + total_bytes += bytes; } return {}; } template - ssize_t read_value(T&& value) + std::error_code read_value(T&& value) { static thread_local std::string buffer{}; // TODO: use a buffer pool - receive(buffer); + if (auto ec = receive(buffer)) { + return {ip_error::receive_failed, ip_error_category::instance()}; + } - const auto ec = glz::read_binary(std::forward_as_tuple(repe::header{}, std::forward(value)), buffer); - if (ec) { - // error - return 0; + if (auto ec = glz::read_binary(std::forward(value), buffer)) { + return {ip_error::receive_failed, ip_error_category::instance()}; } - return buffer.size(); + return {}; } template - ssize_t write_value(T&& value) + std::error_code write_value(T&& value) { static thread_local std::string buffer{}; // TODO: use a buffer pool - const auto ec = glz::write_binary(std::forward_as_tuple(repe::header{}, std::forward(value)), buffer); - if (ec) { - // error + if (auto ec = glz::write_binary(std::forward(value), buffer)) { + return {ip_error::send_failed, ip_error_category::instance()}; } + + Header header{.body_size = int64_t(buffer.size())}; - // write into location of size - const uint64_t size = buffer.size(); - std::memcpy(buffer.data() + 9, &size, sizeof(uint64_t)); - - send(buffer); + if (auto ec = send(sv{(char*)(&header), sizeof(Header)})) { + return ec; + } + + if (auto ec = send(buffer)) { + return ec; + } - return buffer.size(); + return {}; } }; From ebc637f8596b985df3daeeb9f5aa6f0197c8a799 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 07:44:50 -0500 Subject: [PATCH 075/309] error handling --- include/glaze/socket/socket.hpp | 6 +++--- tests/socket_test/socket_test.cpp | 20 ++++++++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 65069bcc57..75a0f0e66f 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -326,7 +326,7 @@ namespace glz ~socket() { if (socket_fd != -1) { - write_value("disconnect"); + std::ignore = write_value("disconnect"); GLZ_CLOSESOCKET(socket_fd); } } @@ -454,7 +454,7 @@ namespace glz } template - std::error_code read_value(T&& value) + [[nodiscard]] std::error_code read_value(T&& value) { static thread_local std::string buffer{}; // TODO: use a buffer pool @@ -470,7 +470,7 @@ namespace glz } template - std::error_code write_value(T&& value) + [[nodiscard]] std::error_code write_value(T&& value) { static thread_local std::string buffer{}; // TODO: use a buffer pool diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 32e7f993ba..80c6d3262b 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -36,11 +36,17 @@ suite make_server = [] { const auto ec = server.accept([](glz::socket&& client, auto& active) { std::cout << "New client connected!\n"; - client.write_value("Welcome!"); + if (auto ec = client.write_value("Welcome!")) { + std::cerr << ec.message() << '\n'; + return; + } while (active) { std::string received{}; - client.read_value(received); + if (auto ec = client.read_value(received)) { + std::cerr << ec.message() << '\n'; + return; + } if (received == "disconnect") { std::cout << std::format("Client Disconnecting\n"); @@ -74,12 +80,18 @@ suite socket_test = [] { } else { std::string received{}; - socket.read_value(received); + if (auto ec = socket.read_value(received)) { + std::cerr << ec.message() << '\n'; + return; + } std::cout << std::format("Received from server: {}\n", received); size_t tick{}; while (tick < 3) { - socket.write_value(std::format("Client {}, {}", id, tick)); + if (auto ec = socket.write_value(std::format("Client {}, {}", id, tick))) { + std::cerr << ec.message() << '\n'; + return; + } std::this_thread::sleep_for(std::chrono::seconds(2)); ++tick; } From 52df8e9d3ba77187ae1d46b2cedc31f52010931b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 07:51:26 -0500 Subject: [PATCH 076/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 75a0f0e66f..97e5908ad3 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -495,7 +495,7 @@ namespace glz struct server final { int port{}; - std::vector> threads{}; // TODO: Remove dead clients + std::vector> threads{}; std::atomic active = true; ~server() { active = false; } From 54ee8b0e334a0fbaa06cc17e42581ddd4d50e5f6 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 07:53:22 -0500 Subject: [PATCH 077/309] server_thread_cleanup --- include/glaze/socket/socket.hpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 97e5908ad3..230eeea3b7 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -491,6 +491,20 @@ namespace glz return {}; } }; + + namespace detail { + inline void server_thread_cleanup(std::vector>& threads) { + threads.erase(std::partition(threads.begin(), threads.end(), + [](auto& future) { + if (auto status = future.wait_for(std::chrono::milliseconds(0)); + status == std::future_status::ready) { + return false; + } + return true; + }), + threads.end()); + } + } struct server final { @@ -607,15 +621,7 @@ namespace glz } #endif - threads.erase(std::partition(threads.begin(), threads.end(), - [](auto& future) { - if (auto status = future.wait_for(std::chrono::milliseconds(0)); - status == std::future_status::ready) { - return false; - } - return true; - }), - threads.end()); + detail::server_thread_cleanup(threads); } GLZ_EVENT_CLOSE(event_fd); From 12054ab29b085bf004a0e8f594ad70fe62d06b91 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 08:04:09 -0500 Subject: [PATCH 078/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 230eeea3b7..d96a744529 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -31,8 +31,8 @@ using ssize_t = int64_t; #include #include -#define GLZ_CLOSESOCKET close -#define GLZ_EVENT_CLOSE close +#define GLZ_CLOSESOCKET ::close +#define GLZ_EVENT_CLOSE ::close #define GLZ_WAIT_RESULT_TYPE int #define GLZ_WAIT_FAILED (-1) #define GLZ_INVALID_EVENT (-1) @@ -322,15 +322,20 @@ namespace glz socket() = default; socket(GLZ_SOCKET fd) : socket_fd(fd) { set_non_blocking(); } - - ~socket() - { + + void close() { if (socket_fd != -1) { std::ignore = write_value("disconnect"); GLZ_CLOSESOCKET(socket_fd); + socket_fd = -1; } } + ~socket() + { + close(); + } + [[nodiscard]] std::error_code connect(const std::string& address, const int port) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); From 39e0cace7b71facef60c05c9ea07e9e3a0a78230 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 08:10:31 -0500 Subject: [PATCH 079/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index d96a744529..fa7989eee7 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -327,7 +327,6 @@ namespace glz if (socket_fd != -1) { std::ignore = write_value("disconnect"); GLZ_CLOSESOCKET(socket_fd); - socket_fd = -1; } } From 3cc4afa6795cd5c1580062df47aa39a43840cb5c Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 08:16:26 -0500 Subject: [PATCH 080/309] client disconnection --- include/glaze/socket/socket.hpp | 14 +++++++++++--- tests/socket_test/socket_test.cpp | 8 +------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index fa7989eee7..44f55a20fc 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -265,7 +265,8 @@ namespace glz socket_connect_failed = 1001, socket_bind_failed = 1002, send_failed, - receive_failed + receive_failed, + client_disconnected }; struct ip_error_category : public std::error_category @@ -298,6 +299,8 @@ namespace glz return "send_failed"; case receive_failed: return "receive_failed"; + case client_disconnected: + return "client_disconnected"; default: return "unknown_error"; } @@ -325,7 +328,6 @@ namespace glz void close() { if (socket_fd != -1) { - std::ignore = write_value("disconnect"); GLZ_CLOSESOCKET(socket_fd); } } @@ -410,6 +412,9 @@ namespace glz return {ip_error::receive_failed, ip_error_category::instance()}; } } + else if (bytes == 0) { + return {ip_error::client_disconnected, ip_error_category::instance()}; + } total_bytes += bytes; } @@ -430,6 +435,9 @@ namespace glz return {ip_error::receive_failed, ip_error_category::instance()}; } } + else if (bytes == 0) { + return {ip_error::client_disconnected, ip_error_category::instance()}; + } total_bytes += bytes; } @@ -463,7 +471,7 @@ namespace glz static thread_local std::string buffer{}; // TODO: use a buffer pool if (auto ec = receive(buffer)) { - return {ip_error::receive_failed, ip_error_category::instance()}; + return ec; } if (auto ec = glz::read_binary(std::forward(value), buffer)) { diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 80c6d3262b..489cca72b2 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -48,13 +48,7 @@ suite make_server = [] { return; } - if (received == "disconnect") { - std::cout << std::format("Client Disconnecting\n"); - break; - } - else if (received.size()) { - std::cout << std::format("Server: {}\n", received); - } + std::cout << std::format("Server: {}\n", received); } }); From d4e04324e3ec28ebf0cebf92b502bd673672a72e Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 08:33:22 -0500 Subject: [PATCH 081/309] format and clang 18 fix --- include/glaze/socket/socket.hpp | 78 ++++++++++++++++----------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 44f55a20fc..0ecda926fa 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -65,24 +65,25 @@ using ssize_t = int64_t; // TOD0: update the REPE code namespace glz { - struct Header { + struct Header + { static constexpr size_t max_method_size = 256; - - uint8_t version = 1; // the REPE version - bool error{}; // whether an error has occurred - bool notify{}; // whether this message does not require a response - bool has_body{}; // whether a body is provided - uint32_t reserved1{}; - // --- - uint64_t id{}; // identifier - int64_t body_size = -1; // the total size of the body - uint32_t reserved2{}; - uint16_t reserved3{}; + + uint8_t version = 1; // the REPE version + bool error{}; // whether an error has occurred + bool notify{}; // whether this message does not require a response + bool has_body{}; // whether a body is provided + uint32_t reserved1{}; + // --- + uint64_t id{}; // identifier + int64_t body_size = -1; // the total size of the body + uint32_t reserved2{}; + uint16_t reserved3{}; // --- - uint16_t method_size{}; // the size of the method string - char method[max_method_size]; // the method name + uint16_t method_size{}; // the size of the method string + char method[max_method_size]{}; // the method name }; - + static_assert((sizeof(Header) - Header::max_method_size) == 32); } @@ -295,12 +296,12 @@ namespace glz return "socket_connect_failed"; case socket_bind_failed: return "socket_bind_failed"; - case send_failed: - return "send_failed"; - case receive_failed: - return "receive_failed"; - case client_disconnected: - return "client_disconnected"; + case send_failed: + return "send_failed"; + case receive_failed: + return "receive_failed"; + case client_disconnected: + return "client_disconnected"; default: return "unknown_error"; } @@ -325,17 +326,15 @@ namespace glz socket() = default; socket(GLZ_SOCKET fd) : socket_fd(fd) { set_non_blocking(); } - - void close() { + + void close() + { if (socket_fd != -1) { GLZ_CLOSESOCKET(socket_fd); } } - ~socket() - { - close(); - } + ~socket() { close(); } [[nodiscard]] std::error_code connect(const std::string& address, const int port) { @@ -398,8 +397,7 @@ namespace glz // first receive the header Header header{}; size_t total_bytes{}; - while (total_bytes < sizeof(Header)) - { + while (total_bytes < sizeof(Header)) { ssize_t bytes = ::recv(socket_fd, &header + total_bytes, size_t(sizeof(Header) - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { @@ -415,10 +413,10 @@ namespace glz else if (bytes == 0) { return {ip_error::client_disconnected, ip_error_category::instance()}; } - + total_bytes += bytes; } - + buffer.resize(header.body_size); total_bytes = 0; const auto n = size_t(header.body_size); @@ -438,7 +436,7 @@ namespace glz else if (bytes == 0) { return {ip_error::client_disconnected, ip_error_category::instance()}; } - + total_bytes += bytes; } return {}; @@ -459,7 +457,7 @@ namespace glz return {ip_error::send_failed, ip_error_category::instance()}; } } - + total_bytes += bytes; } return {}; @@ -489,13 +487,13 @@ namespace glz if (auto ec = glz::write_binary(std::forward(value), buffer)) { return {ip_error::send_failed, ip_error_category::instance()}; } - + Header header{.body_size = int64_t(buffer.size())}; if (auto ec = send(sv{(char*)(&header), sizeof(Header)})) { return ec; } - + if (auto ec = send(buffer)) { return ec; } @@ -503,9 +501,11 @@ namespace glz return {}; } }; - - namespace detail { - inline void server_thread_cleanup(std::vector>& threads) { + + namespace detail + { + inline void server_thread_cleanup(std::vector>& threads) + { threads.erase(std::partition(threads.begin(), threads.end(), [](auto& future) { if (auto status = future.wait_for(std::chrono::milliseconds(0)); @@ -633,7 +633,7 @@ namespace glz } #endif - detail::server_thread_cleanup(threads); + detail::server_thread_cleanup(threads); } GLZ_EVENT_CLOSE(event_fd); From 97f7565c1c2a4f115fe4e6d43852aca373ff090e Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 09:02:19 -0500 Subject: [PATCH 082/309] Free functions for send and receive --- include/glaze/socket/socket.hpp | 138 ++++++++++++++++-------------- tests/socket_test/socket_test.cpp | 8 +- 2 files changed, 78 insertions(+), 68 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 0ecda926fa..be53f2157f 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -269,6 +269,9 @@ namespace glz receive_failed, client_disconnected }; + + template + concept ip_header = std::same_as || requires { T::body_size; }; struct ip_error_category : public std::error_category { @@ -311,7 +314,7 @@ namespace glz struct socket { GLZ_SOCKET socket_fd{GLZ_INVALID_SOCKET}; - + void set_non_blocking() { #ifdef _WIN32 @@ -322,80 +325,80 @@ namespace glz fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); #endif } - + socket() = default; - + socket(GLZ_SOCKET fd) : socket_fd(fd) { set_non_blocking(); } - + void close() { if (socket_fd != -1) { GLZ_CLOSESOCKET(socket_fd); } } - + ~socket() { close(); } - + [[nodiscard]] std::error_code connect(const std::string& address, const int port) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (socket_fd == -1) { return {ip_error::socket_connect_failed, ip_error_category::instance()}; } - + sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(uint16_t(port)); inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); - + if (::connect(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { return {ip_error::socket_connect_failed, ip_error_category::instance()}; } - + set_non_blocking(); - + return {}; } - + [[nodiscard]] bool no_delay() { int flag = 1; int result = setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int)); return result == 0; } - + [[nodiscard]] std::error_code bind_and_listen(int port) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (socket_fd == -1) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - + sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(uint16_t(port)); - + if (::bind(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - + if (::listen(socket_fd, SOMAXCONN) == -1) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - + set_non_blocking(); if (not no_delay()) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - + return {}; } - - [[nodiscard]] std::error_code receive(std::string& buffer) + + template + [[nodiscard]] std::error_code receive(Header& header, std::string& buffer) { // first receive the header - Header header{}; size_t total_bytes{}; while (total_bytes < sizeof(Header)) { ssize_t bytes = ::recv(socket_fd, &header + total_bytes, size_t(sizeof(Header) - total_bytes), 0); @@ -413,11 +416,17 @@ namespace glz else if (bytes == 0) { return {ip_error::client_disconnected, ip_error_category::instance()}; } - + total_bytes += bytes; } - - buffer.resize(header.body_size); + + if constexpr (std::same_as) { + buffer.resize(header); + } + else { + buffer.resize(header.body_size); + } + total_bytes = 0; const auto n = size_t(header.body_size); while (total_bytes < n) { @@ -436,12 +445,12 @@ namespace glz else if (bytes == 0) { return {ip_error::client_disconnected, ip_error_category::instance()}; } - + total_bytes += bytes; } return {}; } - + [[nodiscard]] std::error_code send(const std::string_view buffer) { const size_t size = buffer.size(); @@ -457,49 +466,11 @@ namespace glz return {ip_error::send_failed, ip_error_category::instance()}; } } - + total_bytes += bytes; } return {}; } - - template - [[nodiscard]] std::error_code read_value(T&& value) - { - static thread_local std::string buffer{}; // TODO: use a buffer pool - - if (auto ec = receive(buffer)) { - return ec; - } - - if (auto ec = glz::read_binary(std::forward(value), buffer)) { - return {ip_error::receive_failed, ip_error_category::instance()}; - } - - return {}; - } - - template - [[nodiscard]] std::error_code write_value(T&& value) - { - static thread_local std::string buffer{}; // TODO: use a buffer pool - - if (auto ec = glz::write_binary(std::forward(value), buffer)) { - return {ip_error::send_failed, ip_error_category::instance()}; - } - - Header header{.body_size = int64_t(buffer.size())}; - - if (auto ec = send(sv{(char*)(&header), sizeof(Header)})) { - return ec; - } - - if (auto ec = send(buffer)) { - return ec; - } - - return {}; - } }; namespace detail @@ -517,6 +488,45 @@ namespace glz threads.end()); } } + + template + [[nodiscard]] std::error_code receive(socket& sckt, T&& value) + { + static thread_local std::string buffer{}; // TODO: use a buffer pool + + Header header{}; + if (auto ec = sckt.receive(header, buffer)) { + return ec; + } + + if (auto ec = glz::read(std::forward(value), buffer)) { + return {ip_error::receive_failed, ip_error_category::instance()}; + } + + return {}; + } + + template + [[nodiscard]] std::error_code send(socket& sckt, T&& value) + { + static thread_local std::string buffer{}; // TODO: use a buffer pool + + if (auto ec = glz::write(std::forward(value), buffer)) { + return {ip_error::send_failed, ip_error_category::instance()}; + } + + Header header{.body_size = int64_t(buffer.size())}; + + if (auto ec = sckt.send(sv{reinterpret_cast(&header), sizeof(Header)})) { + return ec; + } + + if (auto ec = sckt.send(buffer)) { + return ec; + } + + return {}; + } struct server final { diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 489cca72b2..341d5301b5 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -36,14 +36,14 @@ suite make_server = [] { const auto ec = server.accept([](glz::socket&& client, auto& active) { std::cout << "New client connected!\n"; - if (auto ec = client.write_value("Welcome!")) { + if (auto ec = glz::send(client, "Welcome!")) { std::cerr << ec.message() << '\n'; return; } while (active) { std::string received{}; - if (auto ec = client.read_value(received)) { + if (auto ec = glz::receive(client, received)) { std::cerr << ec.message() << '\n'; return; } @@ -74,7 +74,7 @@ suite socket_test = [] { } else { std::string received{}; - if (auto ec = socket.read_value(received)) { + if (auto ec = glz::receive(socket, received)) { std::cerr << ec.message() << '\n'; return; } @@ -82,7 +82,7 @@ suite socket_test = [] { size_t tick{}; while (tick < 3) { - if (auto ec = socket.write_value(std::format("Client {}, {}", id, tick))) { + if (auto ec = glz::send(socket, std::format("Client {}, {}", id, tick))) { std::cerr << ec.message() << '\n'; return; } From 493a0de90510f141d96d58e1a4e2de1e2f9dae45 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 09:09:30 -0500 Subject: [PATCH 083/309] undef macros --- include/glaze/socket/socket.hpp | 39 +++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index be53f2157f..710551bec4 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -4,20 +4,21 @@ #pragma once #ifdef _WIN32 +#define GLZ_CLOSE_SOCKET closesocket +#define GLZ_EVENT_CLOSE WSACloseEvent +#define GLZ_INVALID_EVENT WSA_INVALID_EVENT #define GLZ_INVALID_SOCKET INVALID_SOCKET #define GLZ_SOCKET SOCKET -#define GLZ_INVALID_EVENT WSA_INVALID_EVENT -#define GLZ_WAIT_RESULT_TYPE DWORD +#define GLZ_SOCKET_ERROR GLZ_SOCKET_ERROR +#define GLZ_SOCKET_ERROR_CODE WSAGetLastError() #define GLZ_WAIT_FAILED WSA_WAIT_FAILED +#define GLZ_WAIT_RESULT_TYPE DWORD #define NOMINMAX #include #include #include #pragma comment(lib, "Ws2_32.lib") -#define GLZ_CLOSESOCKET closesocket -#define GLZ_EVENT_CLOSE WSACloseEvent -#define GLZ_SOCKET_ERROR_CODE WSAGetLastError() using ssize_t = int64_t; #else #include @@ -31,15 +32,15 @@ using ssize_t = int64_t; #include #include -#define GLZ_CLOSESOCKET ::close +#define GLZ_CLOSE_SOCKET ::close #define GLZ_EVENT_CLOSE ::close -#define GLZ_WAIT_RESULT_TYPE int -#define GLZ_WAIT_FAILED (-1) #define GLZ_INVALID_EVENT (-1) -#define GLZ_SOCKET_ERROR_CODE errno -#define GLZ_SOCKET int -#define SOCKET_ERROR (-1) #define GLZ_INVALID_SOCKET (-1) +#define GLZ_SOCKET int +#define GLZ_SOCKET_ERROR (-1) +#define GLZ_SOCKET_ERROR_CODE errno +#define GLZ_WAIT_FAILED (-1) +#define GLZ_WAIT_RESULT_TYPE int #endif #if defined(__APPLE__) @@ -333,7 +334,7 @@ namespace glz void close() { if (socket_fd != -1) { - GLZ_CLOSESOCKET(socket_fd); + GLZ_CLOSE_SOCKET(socket_fd); } } @@ -569,7 +570,7 @@ namespace glz ev.data.fd = accept_socket.socket_fd; event_setup_failed = epoll_ctl(event_fd, EPOLL_CTL_ADD, accept_socket.socket_fd, &ev) == -1; #elif defined(_WIN32) - event_setup_failed = WSAEventSelect(accept_socket.socket_fd, event_fd, FD_ACCEPT) == SOCKET_ERROR; + event_setup_failed = WSAEventSelect(accept_socket.socket_fd, event_fd, FD_ACCEPT) == GLZ_SOCKET_ERROR; #endif if (event_setup_failed) { @@ -631,7 +632,7 @@ namespace glz #else // Windows WSANETWORKEVENTS events; - if (WSAEnumNetworkEvents(accept_socket.socket_fd, event_fd, &events) == SOCKET_ERROR) { + if (WSAEnumNetworkEvents(accept_socket.socket_fd, event_fd, &events) == GLZ_SOCKET_ERROR) { WSACloseEvent(event_fd); return {ip_error::event_enum_failed, ip_error_category::instance()}; } @@ -651,3 +652,13 @@ namespace glz } }; } + +#undef GLZ_CLOSE_SOCKET +#undef GLZ_EVENT_CLOSE +#undef GLZ_INVALID_EVENT +#undef GLZ_INVALID_SOCKET +#undef GLZ_SOCKET +#undef GLZ_SOCKET_ERROR +#undef GLZ_SOCKET_ERROR_CODE +#undef GLZ_WAIT_FAILED +#undef GLZ_WAIT_RESULT_TYPE From 7c79cb77eedc84d812a5806e2975ca67c11db6ec Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 09:22:09 -0500 Subject: [PATCH 084/309] reverse iterate and erase threads --- include/glaze/socket/socket.hpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 710551bec4..599297e36b 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -478,15 +478,17 @@ namespace glz { inline void server_thread_cleanup(std::vector>& threads) { - threads.erase(std::partition(threads.begin(), threads.end(), - [](auto& future) { - if (auto status = future.wait_for(std::chrono::milliseconds(0)); - status == std::future_status::ready) { - return false; - } - return true; - }), - threads.end()); + for (auto rit = threads.rbegin(); rit < threads.rend();) + { + auto& future = *rit; + if (auto status = future.wait_for(std::chrono::milliseconds(0)); + status == std::future_status::ready) { + rit = std::reverse_iterator(threads.erase(std::next(rit).base())); + } + else { + ++rit; + } + } } } From e557ae1a77f32eead0f46e37e33f9c8537bf986e Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 09:26:37 -0500 Subject: [PATCH 085/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 599297e36b..e89239cdc3 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -495,7 +495,7 @@ namespace glz template [[nodiscard]] std::error_code receive(socket& sckt, T&& value) { - static thread_local std::string buffer{}; // TODO: use a buffer pool + static thread_local std::string buffer{}; Header header{}; if (auto ec = sckt.receive(header, buffer)) { @@ -512,7 +512,7 @@ namespace glz template [[nodiscard]] std::error_code send(socket& sckt, T&& value) { - static thread_local std::string buffer{}; // TODO: use a buffer pool + static thread_local std::string buffer{}; if (auto ec = glz::write(std::forward(value), buffer)) { return {ip_error::send_failed, ip_error_category::instance()}; From d9b2e816d67c2731567eefa9fe88748d6426402b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 09:30:16 -0500 Subject: [PATCH 086/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index e89239cdc3..f8e96478c2 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -481,13 +481,15 @@ namespace glz for (auto rit = threads.rbegin(); rit < threads.rend();) { auto& future = *rit; - if (auto status = future.wait_for(std::chrono::milliseconds(0)); - status == std::future_status::ready) { - rit = std::reverse_iterator(threads.erase(std::next(rit).base())); - } - else { - ++rit; + if (future.valid()) { + if (auto status = future.wait_for(std::chrono::milliseconds(0)); + status == std::future_status::ready) { + rit = std::reverse_iterator(threads.erase(std::next(rit).base())); + } + continue; } + + ++rit; } } } From d1c2b9e8967914222d70a35f8a696703b10fdb82 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 09:39:26 -0500 Subject: [PATCH 087/309] using a deque for futures --- include/glaze/socket/socket.hpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index f8e96478c2..f457500fd2 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -476,20 +476,18 @@ namespace glz namespace detail { - inline void server_thread_cleanup(std::vector>& threads) + inline void server_thread_cleanup(std::deque>& threads) { for (auto rit = threads.rbegin(); rit < threads.rend();) { auto& future = *rit; - if (future.valid()) { - if (auto status = future.wait_for(std::chrono::milliseconds(0)); - status == std::future_status::ready) { - rit = std::reverse_iterator(threads.erase(std::next(rit).base())); - } - continue; + if (auto status = future.wait_for(std::chrono::milliseconds(0)); + status == std::future_status::ready) { + rit = std::reverse_iterator(threads.erase(std::next(rit).base())); + } + else { + ++rit; } - - ++rit; } } } @@ -536,7 +534,7 @@ namespace glz struct server final { int port{}; - std::vector> threads{}; + std::deque> threads{}; std::atomic active = true; ~server() { active = false; } From 8e6fcafb26d5706ae7aaaf15c29f0955a13c6940 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 09:54:02 -0500 Subject: [PATCH 088/309] fix sever_thread destruction order --- include/glaze/socket/socket.hpp | 24 +++++++++++------------- tests/socket_test/socket_test.cpp | 3 +-- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index f457500fd2..68acfb5971 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -476,19 +476,17 @@ namespace glz namespace detail { - inline void server_thread_cleanup(std::deque>& threads) + inline void server_thread_cleanup(std::vector>& threads) { - for (auto rit = threads.rbegin(); rit < threads.rend();) - { - auto& future = *rit; - if (auto status = future.wait_for(std::chrono::milliseconds(0)); - status == std::future_status::ready) { - rit = std::reverse_iterator(threads.erase(std::next(rit).base())); - } - else { - ++rit; - } - } + threads.erase(std::partition(threads.begin(), threads.end(), + [](auto& future) { + if (auto status = future.wait_for(std::chrono::milliseconds(0)); + status == std::future_status::ready) { + return false; + } + return true; + }), + threads.end()); } } @@ -534,7 +532,7 @@ namespace glz struct server final { int port{}; - std::deque> threads{}; + std::vector> threads{}; std::atomic active = true; ~server() { active = false; } diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 341d5301b5..e54639e9be 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -15,8 +15,6 @@ using namespace ut; constexpr bool user_input = false; -std::future server_thread{}; - constexpr auto n_clients = 10; constexpr auto service_0_port{8080}; constexpr auto service_0_ip{"127.0.0.1"}; @@ -28,6 +26,7 @@ static std::atomic_int working_clients{n_clients}; glz::windows_socket_startup_t<> wsa; // wsa_startup (ignored on macOS and Linux) glz::server server{service_0_port}; +std::future server_thread{}; suite make_server = [] { server_thread = std::async([&] { From bef970e9000883e7a98ac57d613236f828852d62 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 10:12:13 -0500 Subject: [PATCH 089/309] async_accept --- include/glaze/socket/socket.hpp | 9 +++++++ tests/socket_test/socket_test.cpp | 39 ++++++++++++++----------------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 68acfb5971..efbdbdbfa3 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -534,8 +534,17 @@ namespace glz int port{}; std::vector> threads{}; std::atomic active = true; + std::shared_future async_accept_thread{}; ~server() { active = false; } + + template + std::shared_future async_accept(AcceptCallback&& callback) { + async_accept_thread = {std::async([this, callback = std::forward(callback)] { + return accept(callback); + })}; + return async_accept_thread; + } template [[nodiscard]] std::error_code accept(AcceptCallback&& callback) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index e54639e9be..e12b98dbab 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -26,37 +26,32 @@ static std::atomic_int working_clients{n_clients}; glz::windows_socket_startup_t<> wsa; // wsa_startup (ignored on macOS and Linux) glz::server server{service_0_port}; -std::future server_thread{}; suite make_server = [] { - server_thread = std::async([&] { - std::cout << std::format("Server started on port: {}\n", server.port); - - const auto ec = server.accept([](glz::socket&& client, auto& active) { - std::cout << "New client connected!\n"; + std::cout << std::format("Server started on port: {}\n", server.port); + + const auto future = server.async_accept([](glz::socket&& client, auto& active) { + std::cout << "New client connected!\n"; - if (auto ec = glz::send(client, "Welcome!")) { + if (auto ec = glz::send(client, "Welcome!")) { + std::cerr << ec.message() << '\n'; + return; + } + + while (active) { + std::string received{}; + if (auto ec = glz::receive(client, received)) { std::cerr << ec.message() << '\n'; return; } - while (active) { - std::string received{}; - if (auto ec = glz::receive(client, received)) { - std::cerr << ec.message() << '\n'; - return; - } - - std::cout << std::format("Server: {}\n", received); - } - }); - - if (ec) { - std::cerr << ec.message() << '\n'; + std::cout << std::format("Server: {}\n", received); } }); - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (future.wait_for(std::chrono::milliseconds(10)) == std::future_status::ready) { + std::cerr << future.get().message() << '\n'; + } }; suite socket_test = [] { From 30f83344d6e87fe066c893a85e6e1943a3ae629b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 10:13:58 -0500 Subject: [PATCH 090/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index efbdbdbfa3..c8eba35cf4 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -9,7 +9,7 @@ #define GLZ_INVALID_EVENT WSA_INVALID_EVENT #define GLZ_INVALID_SOCKET INVALID_SOCKET #define GLZ_SOCKET SOCKET -#define GLZ_SOCKET_ERROR GLZ_SOCKET_ERROR +#define GLZ_SOCKET_ERROR SOCKET_ERROR #define GLZ_SOCKET_ERROR_CODE WSAGetLastError() #define GLZ_WAIT_FAILED WSA_WAIT_FAILED #define GLZ_WAIT_RESULT_TYPE DWORD From 2548f601a2ebfc30b82226921b93b690a58cc45b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 10:15:21 -0500 Subject: [PATCH 091/309] windows fix --- include/glaze/socket/socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index c8eba35cf4..7e84f93d0a 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -402,7 +402,7 @@ namespace glz // first receive the header size_t total_bytes{}; while (total_bytes < sizeof(Header)) { - ssize_t bytes = ::recv(socket_fd, &header + total_bytes, size_t(sizeof(Header) - total_bytes), 0); + ssize_t bytes = ::recv(socket_fd, reinterpret_cast(&header) + total_bytes, size_t(sizeof(Header) - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); From 3f29343111fcf1e494df2641c478ab3c5fcc1bf1 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 10:24:30 -0500 Subject: [PATCH 092/309] GLZ_EWOULDBLOCK --- include/glaze/socket/socket.hpp | 41 ++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 7e84f93d0a..faadebeb98 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -6,6 +6,7 @@ #ifdef _WIN32 #define GLZ_CLOSE_SOCKET closesocket #define GLZ_EVENT_CLOSE WSACloseEvent +#define GLZ_EWOULDBLOCK WSAEWOULDBLOCK #define GLZ_INVALID_EVENT WSA_INVALID_EVENT #define GLZ_INVALID_SOCKET INVALID_SOCKET #define GLZ_SOCKET SOCKET @@ -34,6 +35,7 @@ using ssize_t = int64_t; #include #define GLZ_CLOSE_SOCKET ::close #define GLZ_EVENT_CLOSE ::close +#define GLZ_EWOULDBLOCK EWOULDBLOCK #define GLZ_INVALID_EVENT (-1) #define GLZ_INVALID_SOCKET (-1) #define GLZ_SOCKET int @@ -404,7 +406,7 @@ namespace glz while (total_bytes < sizeof(Header)) { ssize_t bytes = ::recv(socket_fd, reinterpret_cast(&header) + total_bytes, size_t(sizeof(Header) - total_bytes), 0); if (bytes == -1) { - if (GLZ_SOCKET_ERROR_CODE == EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + if (GLZ_SOCKET_ERROR_CODE == GLZ_EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } @@ -433,7 +435,7 @@ namespace glz while (total_bytes < n) { ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); if (bytes == -1) { - if (GLZ_SOCKET_ERROR_CODE == EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + if (GLZ_SOCKET_ERROR_CODE == GLZ_EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } @@ -459,7 +461,7 @@ namespace glz while (total_bytes < size) { ssize_t bytes = ::send(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); if (bytes == -1) { - if (GLZ_SOCKET_ERROR_CODE == EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + if (GLZ_SOCKET_ERROR_CODE == GLZ_EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::yield(); continue; } @@ -473,22 +475,6 @@ namespace glz return {}; } }; - - namespace detail - { - inline void server_thread_cleanup(std::vector>& threads) - { - threads.erase(std::partition(threads.begin(), threads.end(), - [](auto& future) { - if (auto status = future.wait_for(std::chrono::milliseconds(0)); - status == std::future_status::ready) { - return false; - } - return true; - }), - threads.end()); - } - } template [[nodiscard]] std::error_code receive(socket& sckt, T&& value) @@ -528,6 +514,22 @@ namespace glz return {}; } + + namespace detail + { + inline void server_thread_cleanup(std::vector>& threads) + { + threads.erase(std::partition(threads.begin(), threads.end(), + [](auto& future) { + if (auto status = future.wait_for(std::chrono::milliseconds(0)); + status == std::future_status::ready) { + return false; + } + return true; + }), + threads.end()); + } + } struct server final { @@ -664,6 +666,7 @@ namespace glz #undef GLZ_CLOSE_SOCKET #undef GLZ_EVENT_CLOSE +#undef GLZ_EWOULDBLOCK #undef GLZ_INVALID_EVENT #undef GLZ_INVALID_SOCKET #undef GLZ_SOCKET From f7f43da7b5fe64e2d7ea2829bc40adcecbe4c16c Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 10:28:22 -0500 Subject: [PATCH 093/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index faadebeb98..49a1028e86 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -423,15 +423,17 @@ namespace glz total_bytes += bytes; } + size_t n{}; if constexpr (std::same_as) { - buffer.resize(header); + n = header; } else { - buffer.resize(header.body_size); + n = size_t(header.body_size); } + buffer.resize(n); + total_bytes = 0; - const auto n = size_t(header.body_size); while (total_bytes < n) { ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); if (bytes == -1) { @@ -481,7 +483,7 @@ namespace glz { static thread_local std::string buffer{}; - Header header{}; + uint64_t header{}; if (auto ec = sckt.receive(header, buffer)) { return ec; } @@ -502,9 +504,9 @@ namespace glz return {ip_error::send_failed, ip_error_category::instance()}; } - Header header{.body_size = int64_t(buffer.size())}; + uint64_t header = uint64_t(buffer.size()); - if (auto ec = sckt.send(sv{reinterpret_cast(&header), sizeof(Header)})) { + if (auto ec = sckt.send(sv{reinterpret_cast(&header), sizeof(header)})) { return ec; } From 8c00c79ec7d314cbfe10f3a716ed45640d4de6bb Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 10:33:22 -0500 Subject: [PATCH 094/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 49a1028e86..030de173e6 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -519,7 +519,7 @@ namespace glz namespace detail { - inline void server_thread_cleanup(std::vector>& threads) + inline void server_thread_cleanup(auto& threads) { threads.erase(std::partition(threads.begin(), threads.end(), [](auto& future) { @@ -536,7 +536,7 @@ namespace glz struct server final { int port{}; - std::vector> threads{}; + std::vector> threads{}; std::atomic active = true; std::shared_future async_accept_thread{}; From d9730328e84a80156ea6a4e7f6980b45f6fd0974 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 10:38:09 -0500 Subject: [PATCH 095/309] Update socket_test.cpp --- tests/socket_test/socket_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index e12b98dbab..ffb3b35c2f 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -97,6 +97,8 @@ suite socket_test = [] { std::cout << "\nFinished! Press any key to exit."; std::cin.get(); } + + server.active = false; }; int main() { @@ -105,7 +107,5 @@ int main() { std::exit(0); }); - server.active = false; - return 0; } From 5567fee3548a2aa829b4fe3686ec216cc38a9ed6 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 10:46:34 -0500 Subject: [PATCH 096/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 030de173e6..6f1a863661 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -290,6 +290,8 @@ namespace glz { using enum ip_error; switch (static_cast(ec)) { + case none: + return "none"; case queue_create_failed: return "queue_create_failed"; case event_ctl_failed: From 7813b6b0efde61ae8b497b75ac0a0e8c380040f6 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 10:46:54 -0500 Subject: [PATCH 097/309] formatting --- include/glaze/socket/socket.hpp | 89 +++++++++++++++++---------------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 6f1a863661..0da509d93c 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -272,7 +272,7 @@ namespace glz receive_failed, client_disconnected }; - + template concept ip_header = std::same_as || requires { T::body_size; }; @@ -290,8 +290,8 @@ namespace glz { using enum ip_error; switch (static_cast(ec)) { - case none: - return "none"; + case none: + return "none"; case queue_create_failed: return "queue_create_failed"; case event_ctl_failed: @@ -319,7 +319,7 @@ namespace glz struct socket { GLZ_SOCKET socket_fd{GLZ_INVALID_SOCKET}; - + void set_non_blocking() { #ifdef _WIN32 @@ -330,83 +330,84 @@ namespace glz fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); #endif } - + socket() = default; - + socket(GLZ_SOCKET fd) : socket_fd(fd) { set_non_blocking(); } - + void close() { if (socket_fd != -1) { GLZ_CLOSE_SOCKET(socket_fd); } } - + ~socket() { close(); } - + [[nodiscard]] std::error_code connect(const std::string& address, const int port) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (socket_fd == -1) { return {ip_error::socket_connect_failed, ip_error_category::instance()}; } - + sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(uint16_t(port)); inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); - + if (::connect(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { return {ip_error::socket_connect_failed, ip_error_category::instance()}; } - + set_non_blocking(); - + return {}; } - + [[nodiscard]] bool no_delay() { int flag = 1; int result = setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int)); return result == 0; } - + [[nodiscard]] std::error_code bind_and_listen(int port) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); if (socket_fd == -1) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - + sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(uint16_t(port)); - + if (::bind(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - + if (::listen(socket_fd, SOMAXCONN) == -1) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - + set_non_blocking(); if (not no_delay()) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } - + return {}; } - + template [[nodiscard]] std::error_code receive(Header& header, std::string& buffer) { // first receive the header size_t total_bytes{}; while (total_bytes < sizeof(Header)) { - ssize_t bytes = ::recv(socket_fd, reinterpret_cast(&header) + total_bytes, size_t(sizeof(Header) - total_bytes), 0); + ssize_t bytes = ::recv(socket_fd, reinterpret_cast(&header) + total_bytes, + size_t(sizeof(Header) - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == GLZ_EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -421,10 +422,10 @@ namespace glz else if (bytes == 0) { return {ip_error::client_disconnected, ip_error_category::instance()}; } - + total_bytes += bytes; } - + size_t n{}; if constexpr (std::same_as) { n = header; @@ -432,9 +433,9 @@ namespace glz else { n = size_t(header.body_size); } - + buffer.resize(n); - + total_bytes = 0; while (total_bytes < n) { ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); @@ -452,12 +453,12 @@ namespace glz else if (bytes == 0) { return {ip_error::client_disconnected, ip_error_category::instance()}; } - + total_bytes += bytes; } return {}; } - + [[nodiscard]] std::error_code send(const std::string_view buffer) { const size_t size = buffer.size(); @@ -473,13 +474,13 @@ namespace glz return {ip_error::send_failed, ip_error_category::instance()}; } } - + total_bytes += bytes; } return {}; } }; - + template [[nodiscard]] std::error_code receive(socket& sckt, T&& value) { @@ -518,20 +519,20 @@ namespace glz return {}; } - + namespace detail { inline void server_thread_cleanup(auto& threads) { threads.erase(std::partition(threads.begin(), threads.end(), - [](auto& future) { - if (auto status = future.wait_for(std::chrono::milliseconds(0)); - status == std::future_status::ready) { - return false; - } - return true; - }), - threads.end()); + [](auto& future) { + if (auto status = future.wait_for(std::chrono::milliseconds(0)); + status == std::future_status::ready) { + return false; + } + return true; + }), + threads.end()); } } @@ -543,12 +544,12 @@ namespace glz std::shared_future async_accept_thread{}; ~server() { active = false; } - + template - std::shared_future async_accept(AcceptCallback&& callback) { - async_accept_thread = {std::async([this, callback = std::forward(callback)] { - return accept(callback); - })}; + std::shared_future async_accept(AcceptCallback&& callback) + { + async_accept_thread = { + std::async([this, callback = std::forward(callback)] { return accept(callback); })}; return async_accept_thread; } From 015a68ef653c4d3e85ae1a81694e6bbacf2eff49 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 11:01:21 -0500 Subject: [PATCH 098/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 0da509d93c..c10b0b0789 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -539,11 +539,16 @@ namespace glz struct server final { int port{}; + std::shared_future async_accept_thread{}; std::vector> threads{}; std::atomic active = true; - std::shared_future async_accept_thread{}; - - ~server() { active = false; } + + ~server() { + active = false; + for (auto& f : threads) { + f.get(); + } + } template std::shared_future async_accept(AcceptCallback&& callback) From 8ed17ad0e0de830ab6f9717f452d11d3d0b638f5 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 11:02:18 -0500 Subject: [PATCH 099/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index c10b0b0789..9b28b06af2 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -539,9 +539,9 @@ namespace glz struct server final { int port{}; + std::atomic active = true; std::shared_future async_accept_thread{}; std::vector> threads{}; - std::atomic active = true; ~server() { active = false; From 6b2c4897f11318e77adbed502057ebacbb799883 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 11:23:40 -0500 Subject: [PATCH 100/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 9b28b06af2..fa81268ab1 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -278,10 +278,9 @@ namespace glz struct ip_error_category : public std::error_category { - static const ip_error_category& instance() + static ip_error_category instance() { - static ip_error_category instance{}; - return instance; + return {}; } const char* name() const noexcept override { return "ip_error_category"; } From f249526dfb246f5c0a63789f1e34655a7c2e84cb Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 11:24:21 -0500 Subject: [PATCH 101/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index fa81268ab1..946d765852 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -278,6 +278,7 @@ namespace glz struct ip_error_category : public std::error_category { + // MSVC deadlocks if this returns a static instance static ip_error_category instance() { return {}; From 4ddf85786d84cf3767d6ba21ee3cbfb18c3ee5dd Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 11:29:23 -0500 Subject: [PATCH 102/309] Back to std::future --- include/glaze/socket/socket.hpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 946d765852..9a10895829 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -541,13 +541,10 @@ namespace glz int port{}; std::atomic active = true; std::shared_future async_accept_thread{}; - std::vector> threads{}; + std::vector> threads{}; ~server() { active = false; - for (auto& f : threads) { - f.get(); - } } template From 2ff297340936efbefa758dd7de012e4d073abf98 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 11:46:41 -0500 Subject: [PATCH 103/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 9a10895829..0143eb4fcf 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -426,18 +426,18 @@ namespace glz total_bytes += bytes; } - size_t n{}; + size_t size{}; if constexpr (std::same_as) { - n = header; + size = header; } else { - n = size_t(header.body_size); + size = size_t(header.body_size); } - buffer.resize(n); + buffer.resize(size); total_bytes = 0; - while (total_bytes < n) { + while (total_bytes < size) { ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == GLZ_EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { @@ -445,7 +445,6 @@ namespace glz continue; } else { - // error buffer.clear(); return {ip_error::receive_failed, ip_error_category::instance()}; } From 13242df838c7166343c2509d09f55b09c48464d3 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 11:54:42 -0500 Subject: [PATCH 104/309] Update socket_test.cpp --- tests/socket_test/socket_test.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index ffb3b35c2f..80a88d0373 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -107,5 +107,8 @@ int main() { std::exit(0); }); + // GCC needs this sleep + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + return 0; } From e06e9d83ab9555dfae482b2ad96fee7c5f371109 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 12:03:45 -0500 Subject: [PATCH 105/309] formatting --- include/glaze/socket/socket.hpp | 11 +++-------- tests/socket_test/socket_test.cpp | 30 ++++++++++++++---------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index 0143eb4fcf..edb0d97c3e 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -279,10 +279,7 @@ namespace glz struct ip_error_category : public std::error_category { // MSVC deadlocks if this returns a static instance - static ip_error_category instance() - { - return {}; - } + static ip_error_category instance() { return {}; } const char* name() const noexcept override { return "ip_error_category"; } @@ -541,10 +538,8 @@ namespace glz std::atomic active = true; std::shared_future async_accept_thread{}; std::vector> threads{}; - - ~server() { - active = false; - } + + ~server() { active = false; } template std::shared_future async_accept(AcceptCallback&& callback) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 80a88d0373..c7f2d1fac4 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -20,7 +20,7 @@ constexpr auto service_0_port{8080}; constexpr auto service_0_ip{"127.0.0.1"}; // std::latch is broken on MSVC: -//std::latch working_clients{n_clients}; +// std::latch working_clients{n_clients}; static std::atomic_int working_clients{n_clients}; glz::windows_socket_startup_t<> wsa; // wsa_startup (ignored on macOS and Linux) @@ -29,7 +29,7 @@ glz::server server{service_0_port}; suite make_server = [] { std::cout << std::format("Server started on port: {}\n", server.port); - + const auto future = server.async_accept([](glz::socket&& client, auto& active) { std::cout << "New client connected!\n"; @@ -37,14 +37,14 @@ suite make_server = [] { std::cerr << ec.message() << '\n'; return; } - + while (active) { std::string received{}; if (auto ec = glz::receive(client, received)) { std::cerr << ec.message() << '\n'; return; } - + std::cout << std::format("Server: {}\n", received); } }); @@ -55,12 +55,10 @@ suite make_server = [] { }; suite socket_test = [] { - std::vector sockets(n_clients); std::vector> threads(n_clients); - for (size_t id{}; id < n_clients; ++id) - { - threads.emplace_back(std::async([id, &sockets]{ + for (size_t id{}; id < n_clients; ++id) { + threads.emplace_back(std::async([id, &sockets] { glz::socket& socket = sockets[id]; if (socket.connect(service_0_ip, service_0_port)) { @@ -83,32 +81,32 @@ suite socket_test = [] { std::this_thread::sleep_for(std::chrono::seconds(2)); ++tick; } - //working_clients.count_down(); + // working_clients.count_down(); --working_clients; - } })); } - //working_clients.arrive_and_wait(); + // working_clients.arrive_and_wait(); while (working_clients) std::this_thread::sleep_for(std::chrono::milliseconds(1)); if constexpr (user_input) { std::cout << "\nFinished! Press any key to exit."; std::cin.get(); } - + server.active = false; }; -int main() { - std::signal(SIGINT, [](int){ +int main() +{ + std::signal(SIGINT, [](int) { server.active = false; std::exit(0); }); - + // GCC needs this sleep std::this_thread::sleep_for(std::chrono::milliseconds(100)); - + return 0; } From bed5b5e645c1e3aa9d0e97331ee4236700bcb3ec Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 12:16:19 -0500 Subject: [PATCH 106/309] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6552995399..c64deab04f 100644 --- a/README.md +++ b/README.md @@ -172,11 +172,11 @@ auto ec = glz::write_file_json(obj, "./obj.json", std::string{}); - Only tested on 64bit systems, but should run on 32bit systems - Only supports little-endian systems -[Actions](https://github.com/stephenberry/glaze/actions) build and test with [Clang](https://clang.llvm.org) (15+), [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/) (2022), and [GCC](https://gcc.gnu.org) (12+) on apple, windows, and linux. +[Actions](https://github.com/stephenberry/glaze/actions) build and test with [Clang](https://clang.llvm.org) (15+), [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/) (2022), and [GCC](https://gcc.gnu.org) (13+) on apple, windows, and linux. ![clang build](https://github.com/stephenberry/glaze/actions/workflows/clang.yml/badge.svg) ![gcc build](https://github.com/stephenberry/glaze/actions/workflows/gcc.yml/badge.svg) ![msvc build](https://github.com/stephenberry/glaze/actions/workflows/msvc.yml/badge.svg) -> Glaze seeks to maintain compatibility with the latest three versions of GCC and Clang, as well as the latest version of MSVC and Apple Clang. +> Glaze seeks to maintain compatibility with the latest three versions of GCC and Clang, as well as the latest version of MSVC and Apple Clang. As an exception, GCC 12 is not supported due to lack of `std::format`. ## How To Use Glaze From a285bc83629416b13e929e8a9dcbb3e670e9fd86 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 12:16:47 -0500 Subject: [PATCH 107/309] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index c64deab04f..ed69583f50 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ # Glaze One of the fastest JSON libraries in the world. Glaze reads and writes from object memory, simplifying interfaces and offering incredible performance. -> [!IMPORTANT] -> -> Version 2.8.0 adds write error handling which matches the read API. [See v2.8.0 Release notes](https://github.com/stephenberry/glaze/releases/tag/v2.8.0) - Glaze also supports: - [BEVE](https://github.com/beve-org/beve) (binary efficient versatile encoding) From 55bcf207ffa26eb764b65749d78d1efa0cf30b06 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 12:18:30 -0500 Subject: [PATCH 108/309] Update socket.hpp --- include/glaze/socket/socket.hpp | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/socket/socket.hpp index edb0d97c3e..e8d33b8bf1 100644 --- a/include/glaze/socket/socket.hpp +++ b/include/glaze/socket/socket.hpp @@ -64,32 +64,6 @@ using ssize_t = int64_t; #include "glaze/rpc/repe.hpp" -// New REPE header -// TOD0: update the REPE code -namespace glz -{ - struct Header - { - static constexpr size_t max_method_size = 256; - - uint8_t version = 1; // the REPE version - bool error{}; // whether an error has occurred - bool notify{}; // whether this message does not require a response - bool has_body{}; // whether a body is provided - uint32_t reserved1{}; - // --- - uint64_t id{}; // identifier - int64_t body_size = -1; // the total size of the body - uint32_t reserved2{}; - uint16_t reserved3{}; - // --- - uint16_t method_size{}; // the size of the method string - char method[max_method_size]{}; // the method name - }; - - static_assert((sizeof(Header) - Header::max_method_size) == 32); -} - namespace glz { namespace ip From 2f7c46e161c9cd220676cfb889b9c2c0b655c6f9 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 12:21:31 -0500 Subject: [PATCH 109/309] remove gcc 12 from workflow --- .github/workflows/gcc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gcc.yml b/.github/workflows/gcc.yml index 414a59568a..cef7582672 100644 --- a/.github/workflows/gcc.yml +++ b/.github/workflows/gcc.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - gcc: [12, 13, 14] + gcc: [13, 14] build_type: [Debug] std: [20, 23] From 35faddecba665dca46306f5435600d766494b7d2 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 12:26:22 -0500 Subject: [PATCH 110/309] socket -> network folder --- include/glaze/{socket => network}/socket.hpp | 0 tests/socket_test/socket_test.cpp | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename include/glaze/{socket => network}/socket.hpp (100%) diff --git a/include/glaze/socket/socket.hpp b/include/glaze/network/socket.hpp similarity index 100% rename from include/glaze/socket/socket.hpp rename to include/glaze/network/socket.hpp diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index c7f2d1fac4..16a140699d 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -3,7 +3,7 @@ #define UT_RUN_TIME_ONLY -#include "glaze/socket/socket.hpp" +#include "glaze/network/socket.hpp" #include #include From f3493ca3b47c2a88a769225f08b52b9638c589ce Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 12:34:15 -0500 Subject: [PATCH 111/309] server.hpp --- include/glaze/network/server.hpp | 213 ++++++++++++++++++++++++++++++ include/glaze/network/socket.hpp | 153 +-------------------- tests/socket_test/socket_test.cpp | 2 +- 3 files changed, 217 insertions(+), 151 deletions(-) create mode 100644 include/glaze/network/server.hpp diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp new file mode 100644 index 0000000000..a6cb4b550e --- /dev/null +++ b/include/glaze/network/server.hpp @@ -0,0 +1,213 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +#include "glaze/network/socket.hpp" + +#ifdef _WIN32 +#define GLZ_CLOSE_SOCKET closesocket +#define GLZ_EVENT_CLOSE WSACloseEvent +#define GLZ_EWOULDBLOCK WSAEWOULDBLOCK +#define GLZ_INVALID_EVENT WSA_INVALID_EVENT +#define GLZ_INVALID_SOCKET INVALID_SOCKET +#define GLZ_SOCKET SOCKET +#define GLZ_SOCKET_ERROR SOCKET_ERROR +#define GLZ_SOCKET_ERROR_CODE WSAGetLastError() +#define GLZ_WAIT_FAILED WSA_WAIT_FAILED +#define GLZ_WAIT_RESULT_TYPE DWORD +#define NOMINMAX + +#include +using ssize_t = int64_t; +#else +#include +#include +#if __has_include() +#include +#endif +#include +#include +#include +#include + +#include +#define GLZ_CLOSE_SOCKET ::close +#define GLZ_EVENT_CLOSE ::close +#define GLZ_EWOULDBLOCK EWOULDBLOCK +#define GLZ_INVALID_EVENT (-1) +#define GLZ_INVALID_SOCKET (-1) +#define GLZ_SOCKET int +#define GLZ_SOCKET_ERROR (-1) +#define GLZ_SOCKET_ERROR_CODE errno +#define GLZ_WAIT_FAILED (-1) +#define GLZ_WAIT_RESULT_TYPE int +#endif + +#if defined(__APPLE__) +#include // for kqueue on macOS +#elif defined(__linux__) +#include // for epoll on Linux +#endif + +namespace glz +{ + namespace detail + { + inline void server_thread_cleanup(auto& threads) + { + threads.erase(std::partition(threads.begin(), threads.end(), + [](auto& future) { + if (auto status = future.wait_for(std::chrono::milliseconds(0)); + status == std::future_status::ready) { + return false; + } + return true; + }), + threads.end()); + } + } + + struct server final + { + int port{}; + std::atomic active = true; + std::shared_future async_accept_thread{}; + std::vector> threads{}; + + ~server() { active = false; } + + template + std::shared_future async_accept(AcceptCallback&& callback) + { + async_accept_thread = { + std::async([this, callback = std::forward(callback)] { return accept(callback); })}; + return async_accept_thread; + } + + template + [[nodiscard]] std::error_code accept(AcceptCallback&& callback) + { + glz::socket accept_socket{}; + + const auto ec = accept_socket.bind_and_listen(port); + if (ec) { + return {ip_error::socket_bind_failed, ip_error_category::instance()}; + } + +#if defined(__APPLE__) + int event_fd = ::kqueue(); +#elif defined(__linux__) + int event_fd = ::epoll_create1(0); +#elif defined(_WIN32) + HANDLE event_fd = WSACreateEvent(); +#endif + + if (event_fd == GLZ_INVALID_EVENT) { + return {ip_error::queue_create_failed, ip_error_category::instance()}; + } + + bool event_setup_failed = false; +#if defined(__APPLE__) + struct kevent change; + EV_SET(&change, accept_socket.socket_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, nullptr); + event_setup_failed = ::kevent(event_fd, &change, 1, nullptr, 0, nullptr) == -1; +#elif defined(__linux__) + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = accept_socket.socket_fd; + event_setup_failed = epoll_ctl(event_fd, EPOLL_CTL_ADD, accept_socket.socket_fd, &ev) == -1; +#elif defined(_WIN32) + event_setup_failed = WSAEventSelect(accept_socket.socket_fd, event_fd, FD_ACCEPT) == GLZ_SOCKET_ERROR; +#endif + + if (event_setup_failed) { + GLZ_EVENT_CLOSE(event_fd); + return {ip_error::event_ctl_failed, ip_error_category::instance()}; + } + +#if defined(__APPLE__) + std::vector events(16); +#elif defined(__linux__) + std::vector epoll_events(16); +#endif + + while (active) { + GLZ_WAIT_RESULT_TYPE n{}; + +#if defined(__APPLE__) + struct timespec timeout + { + 0, 10000000 + }; // 10ms + n = ::kevent(event_fd, nullptr, 0, events.data(), static_cast(events.size()), &timeout); +#elif defined(__linux__) + n = ::epoll_wait(event_fd, epoll_events.data(), static_cast(epoll_events.size()), 10); +#elif defined(_WIN32) + n = WSAWaitForMultipleEvents(1, &event_fd, FALSE, 10, FALSE); +#endif + + if (n == GLZ_WAIT_FAILED) { +#if defined(__APPLE__) || defined(__linux__) + if (errno == EINTR) continue; +#else + if (n == WSA_WAIT_TIMEOUT) continue; +#endif + GLZ_EVENT_CLOSE(event_fd); + return {ip_error::event_wait_failed, ip_error_category::instance()}; + } + + auto spawn_socket = [&] { + sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); + if (client_fd != GLZ_INVALID_SOCKET) { + threads.emplace_back( + std::async([this, callback, client_fd] { callback(socket{client_fd}, active); })); + } + }; + +#if defined(__APPLE__) || defined(__linux__) + for (int i = 0; i < n; ++i) { +#if defined(__APPLE__) + if (events[i].ident == uintptr_t(accept_socket.socket_fd) && events[i].filter == EVFILT_READ) { +#elif defined(__linux__) + if (epoll_events[i].data.fd == accept_socket.socket_fd && epoll_events[i].events & EPOLLIN) { +#endif + spawn_socket(); + } + } + +#else // Windows + WSANETWORKEVENTS events; + if (WSAEnumNetworkEvents(accept_socket.socket_fd, event_fd, &events) == GLZ_SOCKET_ERROR) { + WSACloseEvent(event_fd); + return {ip_error::event_enum_failed, ip_error_category::instance()}; + } + + if (events.lNetworkEvents & FD_ACCEPT) { + if (events.iErrorCode[FD_ACCEPT_BIT] == 0) { + spawn_socket(); + } + } +#endif + + detail::server_thread_cleanup(threads); + } + + GLZ_EVENT_CLOSE(event_fd); + return {}; + } + }; +} + +#undef GLZ_CLOSE_SOCKET +#undef GLZ_EVENT_CLOSE +#undef GLZ_EWOULDBLOCK +#undef GLZ_INVALID_EVENT +#undef GLZ_INVALID_SOCKET +#undef GLZ_SOCKET +#undef GLZ_SOCKET_ERROR +#undef GLZ_SOCKET_ERROR_CODE +#undef GLZ_WAIT_FAILED +#undef GLZ_WAIT_RESULT_TYPE diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index e8d33b8bf1..e33d8a7630 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -3,6 +3,9 @@ #pragma once +#include "glaze/glaze.hpp" +#include "glaze/glaze.hpp" + #ifdef _WIN32 #define GLZ_CLOSE_SOCKET closesocket #define GLZ_EVENT_CLOSE WSACloseEvent @@ -62,8 +65,6 @@ using ssize_t = int64_t; #include #include -#include "glaze/rpc/repe.hpp" - namespace glz { namespace ip @@ -489,154 +490,6 @@ namespace glz return {}; } - - namespace detail - { - inline void server_thread_cleanup(auto& threads) - { - threads.erase(std::partition(threads.begin(), threads.end(), - [](auto& future) { - if (auto status = future.wait_for(std::chrono::milliseconds(0)); - status == std::future_status::ready) { - return false; - } - return true; - }), - threads.end()); - } - } - - struct server final - { - int port{}; - std::atomic active = true; - std::shared_future async_accept_thread{}; - std::vector> threads{}; - - ~server() { active = false; } - - template - std::shared_future async_accept(AcceptCallback&& callback) - { - async_accept_thread = { - std::async([this, callback = std::forward(callback)] { return accept(callback); })}; - return async_accept_thread; - } - - template - [[nodiscard]] std::error_code accept(AcceptCallback&& callback) - { - glz::socket accept_socket{}; - - const auto ec = accept_socket.bind_and_listen(port); - if (ec) { - return {ip_error::socket_bind_failed, ip_error_category::instance()}; - } - -#if defined(__APPLE__) - int event_fd = ::kqueue(); -#elif defined(__linux__) - int event_fd = ::epoll_create1(0); -#elif defined(_WIN32) - HANDLE event_fd = WSACreateEvent(); -#endif - - if (event_fd == GLZ_INVALID_EVENT) { - return {ip_error::queue_create_failed, ip_error_category::instance()}; - } - - bool event_setup_failed = false; -#if defined(__APPLE__) - struct kevent change; - EV_SET(&change, accept_socket.socket_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, nullptr); - event_setup_failed = ::kevent(event_fd, &change, 1, nullptr, 0, nullptr) == -1; -#elif defined(__linux__) - struct epoll_event ev; - ev.events = EPOLLIN; - ev.data.fd = accept_socket.socket_fd; - event_setup_failed = epoll_ctl(event_fd, EPOLL_CTL_ADD, accept_socket.socket_fd, &ev) == -1; -#elif defined(_WIN32) - event_setup_failed = WSAEventSelect(accept_socket.socket_fd, event_fd, FD_ACCEPT) == GLZ_SOCKET_ERROR; -#endif - - if (event_setup_failed) { - GLZ_EVENT_CLOSE(event_fd); - return {ip_error::event_ctl_failed, ip_error_category::instance()}; - } - -#if defined(__APPLE__) - std::vector events(16); -#elif defined(__linux__) - std::vector epoll_events(16); -#endif - - while (active) { - GLZ_WAIT_RESULT_TYPE n{}; - -#if defined(__APPLE__) - struct timespec timeout - { - 0, 10000000 - }; // 10ms - n = ::kevent(event_fd, nullptr, 0, events.data(), static_cast(events.size()), &timeout); -#elif defined(__linux__) - n = ::epoll_wait(event_fd, epoll_events.data(), static_cast(epoll_events.size()), 10); -#elif defined(_WIN32) - n = WSAWaitForMultipleEvents(1, &event_fd, FALSE, 10, FALSE); -#endif - - if (n == GLZ_WAIT_FAILED) { -#if defined(__APPLE__) || defined(__linux__) - if (errno == EINTR) continue; -#else - if (n == WSA_WAIT_TIMEOUT) continue; -#endif - GLZ_EVENT_CLOSE(event_fd); - return {ip_error::event_wait_failed, ip_error_category::instance()}; - } - - auto spawn_socket = [&] { - sockaddr_in client_addr; - socklen_t client_len = sizeof(client_addr); - auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); - if (client_fd != GLZ_INVALID_SOCKET) { - threads.emplace_back( - std::async([this, callback, client_fd] { callback(socket{client_fd}, active); })); - } - }; - -#if defined(__APPLE__) || defined(__linux__) - for (int i = 0; i < n; ++i) { -#if defined(__APPLE__) - if (events[i].ident == uintptr_t(accept_socket.socket_fd) && events[i].filter == EVFILT_READ) { -#elif defined(__linux__) - if (epoll_events[i].data.fd == accept_socket.socket_fd && epoll_events[i].events & EPOLLIN) { -#endif - spawn_socket(); - } - } - -#else // Windows - WSANETWORKEVENTS events; - if (WSAEnumNetworkEvents(accept_socket.socket_fd, event_fd, &events) == GLZ_SOCKET_ERROR) { - WSACloseEvent(event_fd); - return {ip_error::event_enum_failed, ip_error_category::instance()}; - } - - if (events.lNetworkEvents & FD_ACCEPT) { - if (events.iErrorCode[FD_ACCEPT_BIT] == 0) { - spawn_socket(); - } - } -#endif - - detail::server_thread_cleanup(threads); - } - - GLZ_EVENT_CLOSE(event_fd); - return {}; - } - }; } #undef GLZ_CLOSE_SOCKET diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 16a140699d..3569645a03 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -3,7 +3,7 @@ #define UT_RUN_TIME_ONLY -#include "glaze/network/socket.hpp" +#include "glaze/network/server.hpp" #include #include From 4d0ebe7d0986af57ae111026ce29c21107838318 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 12:39:20 -0500 Subject: [PATCH 112/309] cleanup --- include/glaze/network/server.hpp | 4 ---- include/glaze/network/socket.hpp | 23 +++-------------------- tests/socket_test/socket_test.cpp | 1 + 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index a6cb4b550e..4a0574960e 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -16,10 +16,6 @@ #define GLZ_SOCKET_ERROR_CODE WSAGetLastError() #define GLZ_WAIT_FAILED WSA_WAIT_FAILED #define GLZ_WAIT_RESULT_TYPE DWORD -#define NOMINMAX - -#include -using ssize_t = int64_t; #else #include #include diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index e33d8a7630..c2c257e9f6 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -3,27 +3,20 @@ #pragma once -#include "glaze/glaze.hpp" #include "glaze/glaze.hpp" #ifdef _WIN32 #define GLZ_CLOSE_SOCKET closesocket -#define GLZ_EVENT_CLOSE WSACloseEvent #define GLZ_EWOULDBLOCK WSAEWOULDBLOCK -#define GLZ_INVALID_EVENT WSA_INVALID_EVENT #define GLZ_INVALID_SOCKET INVALID_SOCKET #define GLZ_SOCKET SOCKET #define GLZ_SOCKET_ERROR SOCKET_ERROR #define GLZ_SOCKET_ERROR_CODE WSAGetLastError() -#define GLZ_WAIT_FAILED WSA_WAIT_FAILED -#define GLZ_WAIT_RESULT_TYPE DWORD -#define NOMINMAX #include #include #include #pragma comment(lib, "Ws2_32.lib") -using ssize_t = int64_t; #else #include #include @@ -37,15 +30,11 @@ using ssize_t = int64_t; #include #define GLZ_CLOSE_SOCKET ::close -#define GLZ_EVENT_CLOSE ::close #define GLZ_EWOULDBLOCK EWOULDBLOCK -#define GLZ_INVALID_EVENT (-1) #define GLZ_INVALID_SOCKET (-1) #define GLZ_SOCKET int #define GLZ_SOCKET_ERROR (-1) #define GLZ_SOCKET_ERROR_CODE errno -#define GLZ_WAIT_FAILED (-1) -#define GLZ_WAIT_RESULT_TYPE int #endif #if defined(__APPLE__) @@ -58,9 +47,7 @@ using ssize_t = int64_t; #include #include #include -#include #include -#include #include #include #include @@ -378,7 +365,7 @@ namespace glz // first receive the header size_t total_bytes{}; while (total_bytes < sizeof(Header)) { - ssize_t bytes = ::recv(socket_fd, reinterpret_cast(&header) + total_bytes, + auto bytes = ::recv(socket_fd, reinterpret_cast(&header) + total_bytes, size_t(sizeof(Header) - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == GLZ_EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { @@ -410,7 +397,7 @@ namespace glz total_bytes = 0; while (total_bytes < size) { - ssize_t bytes = ::recv(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); + auto bytes = ::recv(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == GLZ_EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -435,7 +422,7 @@ namespace glz const size_t size = buffer.size(); size_t total_bytes{}; while (total_bytes < size) { - ssize_t bytes = ::send(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); + auto bytes = ::send(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == GLZ_EWOULDBLOCK || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::yield(); @@ -493,12 +480,8 @@ namespace glz } #undef GLZ_CLOSE_SOCKET -#undef GLZ_EVENT_CLOSE #undef GLZ_EWOULDBLOCK -#undef GLZ_INVALID_EVENT #undef GLZ_INVALID_SOCKET #undef GLZ_SOCKET #undef GLZ_SOCKET_ERROR #undef GLZ_SOCKET_ERROR_CODE -#undef GLZ_WAIT_FAILED -#undef GLZ_WAIT_RESULT_TYPE diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 3569645a03..035e08c772 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -5,6 +5,7 @@ #include "glaze/network/server.hpp" +#include #include #include #include From 10fd19859594eed7ef44bb0270ce2e7217a247b9 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 12:41:25 -0500 Subject: [PATCH 113/309] Reverting repe.hpp In the future the REPE header will be updated. --- include/glaze/rpc/repe.hpp | 3 +- tests/repe_test/repe_test.cpp | 88 +++++++++++++++++------------------ 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/include/glaze/rpc/repe.hpp b/include/glaze/rpc/repe.hpp index 014e3b7316..2081aa980a 100644 --- a/include/glaze/rpc/repe.hpp +++ b/include/glaze/rpc/repe.hpp @@ -22,7 +22,6 @@ namespace glz::repe std::variant id{}; // an identifier static constexpr uint8_t version = 0; // the REPE version uint8_t error = 0; // 0 denotes no error (boolean: 0 or 1) - int64_t size = -1; // -1 denotes no size provided // Action: These booleans are packed into the a uint8_t action in the REPE header bool notify{}; // no response returned @@ -49,7 +48,7 @@ struct glz::meta return action; }; static constexpr auto value = - glz::array(&T::version, &T::error, &T::size, custom, &T::method, &T::id); + glz::array(&T::version, &T::error, custom, &T::method, &T::id); }; namespace glz::repe diff --git a/tests/repe_test/repe_test.cpp b/tests/repe_test/repe_test.cpp index a939843ee1..5c1193e7f4 100644 --- a/tests/repe_test/repe_test.cpp +++ b/tests/repe_test/repe_test.cpp @@ -80,28 +80,28 @@ suite structs_of_functions = [] { response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,0,"/i",null],55])") << response->value(); + expect(response->value() == R"([[0,0,0,"/i",null],55])") << response->value(); { auto request = repe::request_json({.method = "/i"}, 42); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,2,"/i",null],null])") << response->value(); + expect(response->value() == R"([[0,0,2,"/i",null],null])") << response->value(); { auto request = repe::request_json({"/hello"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,0,"/hello",null],"Hello"])"); + expect(response->value() == R"([[0,0,0,"/hello",null],"Hello"])"); { auto request = repe::request_json({"/get_number"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,0,"/get_number",null],42])"); + expect(response->value() == R"([[0,0,0,"/get_number",null],42])"); }; "nested_structs_of_functions"_test = [] { @@ -118,42 +118,42 @@ suite structs_of_functions = [] { response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,2,"/my_functions/void_func",null],null])") << response->value(); + expect(response->value() == R"([[0,0,2,"/my_functions/void_func",null],null])") << response->value(); { auto request = repe::request_json({"/my_functions/hello"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,0,"/my_functions/hello",null],"Hello"])"); + expect(response->value() == R"([[0,0,0,"/my_functions/hello",null],"Hello"])"); { auto request = repe::request_json({"/meta_functions/hello"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,0,"/meta_functions/hello",null],"Hello"])"); + expect(response->value() == R"([[0,0,0,"/meta_functions/hello",null],"Hello"])"); { auto request = repe::request_json({"/append_awesome"}, "you are"); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,0,"/append_awesome",null],"you are awesome!"])"); + expect(response->value() == R"([[0,0,0,"/append_awesome",null],"you are awesome!"])"); { auto request = repe::request_json({"/my_string"}, "Howdy!"); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,2,"/my_string",null],null])"); + expect(response->value() == R"([[0,0,2,"/my_string",null],null])"); { auto request = repe::request_json({"/my_string"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,0,"/my_string",null],"Howdy!"])") << response->value(); + expect(response->value() == R"([[0,0,0,"/my_string",null],"Howdy!"])") << response->value(); obj.my_string.clear(); @@ -163,14 +163,14 @@ suite structs_of_functions = [] { } // we expect an empty string returned because we cleared it - expect(response->value() == R"([[0,0,-1,0,"/my_string",null],""])"); + expect(response->value() == R"([[0,0,0,"/my_string",null],""])"); { auto request = repe::request_json({"/my_functions/max"}, std::vector{1.1, 3.3, 2.25}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,0,"/my_functions/max",null],3.3])") << response->value(); + expect(response->value() == R"([[0,0,0,"/my_functions/max",null],3.3])") << response->value(); { auto request = repe::request_json({"/my_functions"}); @@ -179,7 +179,7 @@ suite structs_of_functions = [] { expect( response->value() == - R"([[0,0,-1,0,"/my_functions",null],{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"}])") + R"([[0,0,0,"/my_functions",null],{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"}])") << response->value(); { @@ -189,7 +189,7 @@ suite structs_of_functions = [] { expect( response->value() == - R"([[0,0,-1,0,"",null],{"my_functions":{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"},"meta_functions":{"hello":"std::function","world":"std::function","get_number":"std::function"},"append_awesome":"std::function","my_string":""}])") + R"([[0,0,0,"",null],{"my_functions":{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"},"meta_functions":{"hello":"std::function","world":"std::function","get_number":"std::function"},"append_awesome":"std::function","my_string":""}])") << response->value(); }; @@ -207,14 +207,14 @@ suite structs_of_functions = [] { response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,2,"/name",null],null])") << response->value(); + expect(response->value() == R"([[0,0,2,"/name",null],null])") << response->value(); { auto request = repe::request_json({"/get_name"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,0,"/get_name",null],"Susan"])") << response->value(); + expect(response->value() == R"([[0,0,0,"/get_name",null],"Susan"])") << response->value(); { auto request = repe::request_json({"/get_name"}, "Bob"); @@ -222,7 +222,7 @@ suite structs_of_functions = [] { } expect(obj.name == "Susan"); // we expect the name to not have changed because this function take no inputs - expect(response->value() == R"([[0,0,-1,0,"/get_name",null],"Susan"])") << response->value(); + expect(response->value() == R"([[0,0,0,"/get_name",null],"Susan"])") << response->value(); { auto request = repe::request_json({"/set_name"}, "Bob"); @@ -230,7 +230,7 @@ suite structs_of_functions = [] { } expect(obj.name == "Bob"); - expect(response->value() == R"([[0,0,-1,2,"/set_name",null],null])") << response->value(); + expect(response->value() == R"([[0,0,2,"/set_name",null],null])") << response->value(); { auto request = repe::request_json({"/custom_name"}, "Alice"); @@ -238,7 +238,7 @@ suite structs_of_functions = [] { } expect(obj.name == "Alice"); - expect(response->value() == R"([[0,0,-1,2,"/custom_name",null],null])") << response->value(); + expect(response->value() == R"([[0,0,2,"/custom_name",null],null])") << response->value(); }; }; @@ -261,7 +261,7 @@ suite structs_of_functions_binary = [] { std::string res{}; expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,0,"/i",null],55])") << res; + expect(res == R"([[0,0,0,"/i",null],55])") << res; { auto request = repe::request_binary({.method = "/i"}, 42); @@ -269,7 +269,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,2,"/i",null],null])") << res; + expect(res == R"([[0,0,2,"/i",null],null])") << res; { auto request = repe::request_binary({"/hello"}); @@ -277,7 +277,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,0,"/hello",null],"Hello"])"); + expect(res == R"([[0,0,0,"/hello",null],"Hello"])"); { auto request = repe::request_binary({"/get_number"}); @@ -285,7 +285,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,0,"/get_number",null],42])"); + expect(res == R"([[0,0,0,"/get_number",null],42])"); }; "nested_structs_of_functions"_test = [] { @@ -304,7 +304,7 @@ suite structs_of_functions_binary = [] { std::string res{}; expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,2,"/my_functions/void_func",null],null])") << response; + expect(res == R"([[0,0,2,"/my_functions/void_func",null],null])") << response; { auto request = repe::request_binary({"/my_functions/hello"}); @@ -312,7 +312,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,0,"/my_functions/hello",null],"Hello"])"); + expect(res == R"([[0,0,0,"/my_functions/hello",null],"Hello"])"); { auto request = repe::request_binary({"/meta_functions/hello"}); @@ -320,7 +320,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,0,"/meta_functions/hello",null],"Hello"])"); + expect(res == R"([[0,0,0,"/meta_functions/hello",null],"Hello"])"); { auto request = repe::request_binary({"/append_awesome"}, "you are"); @@ -328,7 +328,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,0,"/append_awesome",null],"you are awesome!"])"); + expect(res == R"([[0,0,0,"/append_awesome",null],"you are awesome!"])"); { auto request = repe::request_binary({"/my_string"}, "Howdy!"); @@ -336,7 +336,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,2,"/my_string",null],null])"); + expect(res == R"([[0,0,2,"/my_string",null],null])"); { auto request = repe::request_binary({"/my_string"}); @@ -344,7 +344,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,0,"/my_string",null],"Howdy!"])") << response; + expect(res == R"([[0,0,0,"/my_string",null],"Howdy!"])") << response; obj.my_string.clear(); @@ -355,7 +355,7 @@ suite structs_of_functions_binary = [] { expect(!glz::beve_to_json(response->value(), res)); // we expect an empty string returned because we cleared it - expect(res == R"([[0,0,-1,0,"/my_string",null],""])"); + expect(res == R"([[0,0,0,"/my_string",null],""])"); { auto request = repe::request_binary({"/my_functions/max"}, std::vector{1.1, 3.3, 2.25}); @@ -363,7 +363,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,0,"/my_functions/max",null],3.3])") << response; + expect(res == R"([[0,0,0,"/my_functions/max",null],3.3])") << response; { auto request = repe::request_binary({"/my_functions"}); @@ -373,7 +373,7 @@ suite structs_of_functions_binary = [] { expect(!glz::beve_to_json(response->value(), res)); expect( res == - R"([[0,0,-1,0,"/my_functions",null],{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"}])") + R"([[0,0,0,"/my_functions",null],{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"}])") << res; { @@ -384,7 +384,7 @@ suite structs_of_functions_binary = [] { expect(!glz::beve_to_json(response->value(), res)); expect( res == - R"([[0,0,-1,0,"",null],{"my_functions":{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"},"meta_functions":{"hello":"std::function","world":"std::function","get_number":"std::function"},"append_awesome":"std::function","my_string":""}])") + R"([[0,0,0,"",null],{"my_functions":{"i":0,"hello":"std::function","world":"std::function","get_number":"std::function","void_func":"std::function","max":"std::function&)>"},"meta_functions":{"hello":"std::function","world":"std::function","get_number":"std::function"},"append_awesome":"std::function","my_string":""}])") << res; }; @@ -404,7 +404,7 @@ suite structs_of_functions_binary = [] { std::string res{}; expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,2,"/name",null],null])") << response; + expect(res == R"([[0,0,2,"/name",null],null])") << response; { auto request = repe::request_binary({"/get_name"}); @@ -412,7 +412,7 @@ suite structs_of_functions_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,0,"/get_name",null],"Susan"])") << response; + expect(res == R"([[0,0,0,"/get_name",null],"Susan"])") << response; { auto request = repe::request_binary({"/get_name"}, "Bob"); @@ -421,7 +421,7 @@ suite structs_of_functions_binary = [] { expect(!glz::beve_to_json(response->value(), res)); expect(obj.name == "Susan"); // we expect the name to not have changed because this function take no inputs - expect(res == R"([[0,0,-1,0,"/get_name",null],"Susan"])") << res; + expect(res == R"([[0,0,0,"/get_name",null],"Susan"])") << res; { auto request = repe::request_binary({"/set_name"}, "Bob"); @@ -430,7 +430,7 @@ suite structs_of_functions_binary = [] { expect(!glz::beve_to_json(response->value(), res)); expect(obj.name == "Bob"); - expect(res == R"([[0,0,-1,2,"/set_name",null],null])") << response; + expect(res == R"([[0,0,2,"/set_name",null],null])") << response; { auto request = repe::request_binary({"/custom_name"}, "Alice"); @@ -439,7 +439,7 @@ suite structs_of_functions_binary = [] { expect(!glz::beve_to_json(response->value(), res)); expect(obj.name == "Alice"); - expect(res == R"([[0,0,-1,2,"/custom_name",null],null])") << response; + expect(res == R"([[0,0,2,"/custom_name",null],null])") << response; }; }; @@ -470,14 +470,14 @@ suite wrapper_tests = [] { response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,2,"/sub/my_functions/void_func",null],null])") << response->value(); + expect(response->value() == R"([[0,0,2,"/sub/my_functions/void_func",null],null])") << response->value(); { auto request = repe::request_json({"/sub/my_functions/hello"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,0,"/sub/my_functions/hello",null],"Hello"])"); + expect(response->value() == R"([[0,0,0,"/sub/my_functions/hello",null],"Hello"])"); }; }; @@ -496,14 +496,14 @@ suite root_tests = [] { response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,2,"/sub/my_functions/void_func",null],null])") << response->value(); + expect(response->value() == R"([[0,0,2,"/sub/my_functions/void_func",null],null])") << response->value(); { auto request = repe::request_json({"/sub/my_functions/hello"}); response = server.call(request.value()); } - expect(response->value() == R"([[0,0,-1,0,"/sub/my_functions/hello",null],"Hello"])"); + expect(response->value() == R"([[0,0,0,"/sub/my_functions/hello",null],"Hello"])"); }; }; @@ -525,7 +525,7 @@ suite wrapper_tests_binary = [] { std::string res{}; expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,2,"/sub/my_functions/void_func",null],null])") << response; + expect(res == R"([[0,0,2,"/sub/my_functions/void_func",null],null])") << response; { auto request = repe::request_binary({"/sub/my_functions/hello"}); @@ -533,7 +533,7 @@ suite wrapper_tests_binary = [] { } expect(!glz::beve_to_json(response->value(), res)); - expect(res == R"([[0,0,-1,0,"/sub/my_functions/hello",null],"Hello"])"); + expect(res == R"([[0,0,0,"/sub/my_functions/hello",null],"Hello"])"); }; }; From c64a3c0803b82dba83eef57db449ef29365f8d3b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 13:43:42 -0500 Subject: [PATCH 114/309] repe_server and repe_client --- include/glaze/network/repe_client.hpp | 159 +++++++++++++++++++++++++ include/glaze/network/repe_server.hpp | 73 ++++++++++++ include/glaze/network/socket.hpp | 9 +- tests/asio_repe/client/repe_client.cpp | 10 +- tests/asio_repe/server/repe_server.cpp | 6 +- 5 files changed, 244 insertions(+), 13 deletions(-) create mode 100644 include/glaze/network/repe_client.hpp create mode 100644 include/glaze/network/repe_server.hpp diff --git a/include/glaze/network/repe_client.hpp b/include/glaze/network/repe_client.hpp new file mode 100644 index 0000000000..09d5591cab --- /dev/null +++ b/include/glaze/network/repe_client.hpp @@ -0,0 +1,159 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +#include "glaze/network/socket.hpp" +#include "glaze/rpc/repe.hpp" + +namespace glz +{ + template + struct repe_client + { + std::string hostname{"127.0.0.1"}; + uint16_t port{}; + glz::socket socket{}; + + struct glaze + { + using T = repe_client; + static constexpr auto value = glz::object(&T::hostname, &T::port); + }; + + std::shared_ptr buffer_pool = std::make_shared(); + + [[nodiscard]] std::error_code init() + { + if (auto ec = socket.connect(hostname, port)) { + return ec; + } + + if (not socket.no_delay()) { + return {ip_error::socket_bind_failed, ip_error_category::instance()}; + } + + return {}; + } + + template + [[nodiscard]] repe::error_t notify(repe::header&& header, Params&& params) + { + repe::unique_buffer ubuffer{buffer_pool.get()}; + auto& buffer = ubuffer.value(); + + header.notify = true; + const auto ec = repe::request(std::move(header), std::forward(params), buffer); + if (bool(ec)) [[unlikely]] { + return {repe::error_e::invalid_params, glz::format_error(ec, buffer)}; + } + + if (auto ec = send(socket, buffer)) { + return {ec.value(), ec.message()}; + } + return {}; + } + + template + [[nodiscard]] repe::error_t get(repe::header&& header, Result&& result) + { + repe::unique_buffer ubuffer{buffer_pool.get()}; + auto& buffer = ubuffer.value(); + + header.notify = false; + header.empty = true; // no params + const auto ec = repe::request(std::move(header), nullptr, buffer); + if (bool(ec)) [[unlikely]] { + return {repe::error_e::invalid_params, glz::format_error(ec, buffer)}; + } + + if (auto ec = send(socket, buffer)) { + return {ec.value(), ec.message()}; + } + if (auto ec = receive(socket, buffer)) { + return {ec.value(), ec.message()}; + } + + return repe::decode_response(std::forward(result), buffer); + } + + template + [[nodiscard]] glz::expected get(repe::header&& header) + { + std::decay_t result{}; + const auto error = get(std::move(header), result); + if (error) { + return glz::unexpected(error); + } + else { + return {result}; + } + } + + template + [[nodiscard]] repe::error_t set(repe::header&& header, Params&& params) + { + repe::unique_buffer ubuffer{buffer_pool.get()}; + auto& buffer = ubuffer.value(); + + header.notify = false; + const auto ec = repe::request(std::move(header), std::forward(params), buffer); + if (bool(ec)) [[unlikely]] { + return {repe::error_e::invalid_params, glz::format_error(ec, buffer)}; + } + + if (auto ec = send(socket, buffer)) { + return {ec.value(), ec.message()}; + } + if (auto ec = receive(socket, buffer)) { + return {ec.value(), ec.message()}; + } + + return repe::decode_response(buffer); + } + + template + [[nodiscard]] repe::error_t call(repe::header&& header, Params&& params, Result&& result) + { + repe::unique_buffer ubuffer{buffer_pool.get()}; + auto& buffer = ubuffer.value(); + + header.notify = false; + const auto ec = repe::request(std::move(header), std::forward(params), buffer); + if (bool(ec)) [[unlikely]] { + return {repe::error_e::invalid_params, glz::format_error(ec, buffer)}; + } + + if (auto ec = send(socket, buffer)) { + return {ec.value(), ec.message()}; + } + if (auto ec = receive(socket, buffer)) { + return {ec.value(), ec.message()}; + } + + return repe::decode_response(std::forward(result), buffer); + } + + [[nodiscard]] repe::error_t call(repe::header&& header) + { + repe::unique_buffer ubuffer{buffer_pool.get()}; + auto& buffer = ubuffer.value(); + + header.notify = false; + header.empty = true; // because no value provided + const auto ec = glz::write_json(std::forward_as_tuple(std::move(header), nullptr), buffer); + if (bool(ec)) [[unlikely]] { + return {repe::error_e::invalid_params, glz::format_error(ec, buffer)}; + } + + if (auto ec = send(socket, buffer)) { + return {ec.value(), ec.message()}; + } + if (auto ec = receive(socket, buffer)) { + return {ec.value(), ec.message()}; + } + + return repe::decode_response(buffer); + } + }; +} diff --git a/include/glaze/network/repe_server.hpp b/include/glaze/network/repe_server.hpp new file mode 100644 index 0000000000..162ce2e504 --- /dev/null +++ b/include/glaze/network/repe_server.hpp @@ -0,0 +1,73 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +#include "glaze/network/server.hpp" +#include "glaze/rpc/repe.hpp" + +namespace glz +{ + template + struct repe_server + { + uint16_t port{}; + glz::server server{}; + + struct glaze + { + using T = repe_server; + static constexpr auto value = glz::object(&T::port); + }; + + repe::registry registry{}; + + void clear_registry() { registry.clear(); } + + template + requires(glz::detail::glaze_object_t || glz::detail::reflectable) + void on(T& value) + { + registry.template on(value); + } + + void run() + { + auto ec = server.accept([this](socket&& socket, auto& active) { + // TODO: handle potential error + std::ignore = socket.no_delay(); + + std::string buffer{}; + + try { + while (active) { + if (auto ec = receive(socket, buffer)) { + std::fprintf(stderr, "%s\n", ec.message().c_str()); + } + else { + auto response = registry.call(buffer); + if (response) { + if (auto ec = send(socket, buffer)) { + std::fprintf(stderr, "%s\n", ec.message().c_str()); + } + } + } + } + } + catch (const std::exception& e) { + std::fprintf(stderr, "%s\n", e.what()); + } + }); + + if (ec) { + std::fprintf(stderr, "%s\n", ec.message().c_str()); + } + } + + // stop the server + void stop() + { + server.active = false; + } + }; +} diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index c2c257e9f6..93e15bbc4d 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -228,8 +228,8 @@ namespace glz event_ctl_failed, event_wait_failed, event_enum_failed, - socket_connect_failed = 1001, - socket_bind_failed = 1002, + socket_connect_failed, + socket_bind_failed, send_failed, receive_failed, client_disconnected @@ -241,7 +241,10 @@ namespace glz struct ip_error_category : public std::error_category { // MSVC deadlocks if this returns a static instance - static ip_error_category instance() { return {}; } + static const ip_error_category& instance() { + static ip_error_category instance{}; + return instance; + } const char* name() const noexcept override { return "ip_error_category"; } diff --git a/tests/asio_repe/client/repe_client.cpp b/tests/asio_repe/client/repe_client.cpp index 6295935451..b6e5bb7fe6 100644 --- a/tests/asio_repe/client/repe_client.cpp +++ b/tests/asio_repe/client/repe_client.cpp @@ -3,22 +3,20 @@ #include -#include "glaze/ext/glaze_asio.hpp" -#include "glaze/glaze.hpp" -#include "glaze/rpc/repe.hpp" +#include "glaze/network/repe_client.hpp" void asio_client_test() { try { - constexpr auto N = 100; - std::vector> clients; + constexpr auto N = 1; + std::vector> clients; clients.reserve(N); std::vector> threads; threads.reserve(N); for (size_t i = 0; i < N; ++i) { - clients.emplace_back(glz::asio_client<>{"localhost", "8080"}); + clients.emplace_back(glz::repe_client<>{"127.0.0.1", 8080}); } for (size_t i = 0; i < N; ++i) { diff --git a/tests/asio_repe/server/repe_server.cpp b/tests/asio_repe/server/repe_server.cpp index 89056171c0..117adb47e5 100644 --- a/tests/asio_repe/server/repe_server.cpp +++ b/tests/asio_repe/server/repe_server.cpp @@ -1,9 +1,7 @@ // Glaze Library // For the license information refer to glaze.hpp -#include "glaze/ext/glaze_asio.hpp" -#include "glaze/glaze.hpp" -#include "glaze/rpc/repe.hpp" +#include "glaze/network/repe_server.hpp" struct api { @@ -20,7 +18,7 @@ void run_server() std::cout << "Server active...\n"; try { - glz::asio_server<> server{.port = 8080}; + glz::repe_server<> server{.port = 8080}; api methods{}; server.on(methods); server.run(); From 94b7ed95796246ba4cdb339f2dfc62f8086d7c50 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 13:45:35 -0500 Subject: [PATCH 115/309] Update socket.hpp --- include/glaze/network/socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 93e15bbc4d..8a3252f9c1 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -299,7 +299,7 @@ namespace glz void close() { - if (socket_fd != -1) { + if (socket_fd != GLZ_INVALID_SOCKET) { GLZ_CLOSE_SOCKET(socket_fd); } } From 75a3e1c94c280315be29c1bf5311617b8d56e9d5 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 13:48:26 -0500 Subject: [PATCH 116/309] Update socket.hpp --- include/glaze/network/socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 8a3252f9c1..3f5d2ad145 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -337,7 +337,7 @@ namespace glz [[nodiscard]] std::error_code bind_and_listen(int port) { socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd == -1) { + if (socket_fd == GLZ_INVALID_SOCKET) { return {ip_error::socket_bind_failed, ip_error_category::instance()}; } From 5902ff50265d20acd113c5b8ebb971c73b7c3fe8 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 13:56:22 -0500 Subject: [PATCH 117/309] MSVC fix --- tests/asio_repe/server/repe_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/asio_repe/server/repe_server.cpp b/tests/asio_repe/server/repe_server.cpp index 117adb47e5..2c402d91f8 100644 --- a/tests/asio_repe/server/repe_server.cpp +++ b/tests/asio_repe/server/repe_server.cpp @@ -8,7 +8,7 @@ struct api std::function& vec)> sum = [](std::vector& vec) { return std::reduce(vec.begin(), vec.end()); }; - std::function& vec)> max = [](std::vector& vec) { return std::ranges::max(vec); }; + std::function& vec)> maximum = [](std::vector& vec) { return (std::ranges::max)(vec); }; }; #include From 8284e7fa5f5ae645fea6f2c4b16852cc8900b581 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 14:13:51 -0500 Subject: [PATCH 118/309] Fix repe_server port assignment --- include/glaze/network/repe_server.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/glaze/network/repe_server.hpp b/include/glaze/network/repe_server.hpp index 162ce2e504..120afb3096 100644 --- a/include/glaze/network/repe_server.hpp +++ b/include/glaze/network/repe_server.hpp @@ -33,6 +33,8 @@ namespace glz void run() { + server.port = port; + auto ec = server.accept([this](socket&& socket, auto& active) { // TODO: handle potential error std::ignore = socket.no_delay(); From f085f83d3a0efe3c456ccbf3c7351391468daf64 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 14:24:29 -0500 Subject: [PATCH 119/309] repe_server close client when disconnected --- include/glaze/network/repe_server.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/glaze/network/repe_server.hpp b/include/glaze/network/repe_server.hpp index 120afb3096..8a392f3f8d 100644 --- a/include/glaze/network/repe_server.hpp +++ b/include/glaze/network/repe_server.hpp @@ -45,6 +45,9 @@ namespace glz while (active) { if (auto ec = receive(socket, buffer)) { std::fprintf(stderr, "%s\n", ec.message().c_str()); + if (ec.value() == ip_error::client_disconnected) { + return; + } } else { auto response = registry.call(buffer); From abe983ad50255c2476a9e7bd6c14315d70155a69 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 15:21:40 -0500 Subject: [PATCH 120/309] send/receive --- include/glaze/network/socket.hpp | 32 +++++++++++++++++++++++++++++-- tests/socket_test/socket_test.cpp | 8 ++++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 3f5d2ad145..bf9cb93fac 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -441,9 +441,35 @@ namespace glz return {}; } }; + + template + [[nodiscard]] std::error_code receive(socket& sckt, Buffer& buffer) + { + uint64_t header{}; + if (auto ec = sckt.receive(header, buffer)) { + return ec; + } + return {}; + } + + template + [[nodiscard]] std::error_code send(socket& sckt, Buffer& buffer) + { + uint64_t header = uint64_t(buffer.size()); + + if (auto ec = sckt.send(sv{reinterpret_cast(&header), sizeof(header)})) { + return ec; + } + + if (auto ec = sckt.send(buffer)) { + return ec; + } + + return {}; + } template - [[nodiscard]] std::error_code receive(socket& sckt, T&& value) + [[nodiscard]] std::error_code receive_value(socket& sckt, T&& value) { static thread_local std::string buffer{}; @@ -453,6 +479,7 @@ namespace glz } if (auto ec = glz::read(std::forward(value), buffer)) { + std::fprintf(stderr, "%s\n", glz::format_error(ec, buffer).c_str()); return {ip_error::receive_failed, ip_error_category::instance()}; } @@ -460,11 +487,12 @@ namespace glz } template - [[nodiscard]] std::error_code send(socket& sckt, T&& value) + [[nodiscard]] std::error_code send_value(socket& sckt, T&& value) { static thread_local std::string buffer{}; if (auto ec = glz::write(std::forward(value), buffer)) { + std::fprintf(stderr, "%s\n", glz::format_error(ec, buffer).c_str()); return {ip_error::send_failed, ip_error_category::instance()}; } diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 035e08c772..4cd3eaab50 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -34,14 +34,14 @@ suite make_server = [] { const auto future = server.async_accept([](glz::socket&& client, auto& active) { std::cout << "New client connected!\n"; - if (auto ec = glz::send(client, "Welcome!")) { + if (auto ec = glz::send_value(client, "Welcome!")) { std::cerr << ec.message() << '\n'; return; } while (active) { std::string received{}; - if (auto ec = glz::receive(client, received)) { + if (auto ec = glz::receive_value(client, received)) { std::cerr << ec.message() << '\n'; return; } @@ -67,7 +67,7 @@ suite socket_test = [] { } else { std::string received{}; - if (auto ec = glz::receive(socket, received)) { + if (auto ec = glz::receive_value(socket, received)) { std::cerr << ec.message() << '\n'; return; } @@ -75,7 +75,7 @@ suite socket_test = [] { size_t tick{}; while (tick < 3) { - if (auto ec = glz::send(socket, std::format("Client {}, {}", id, tick))) { + if (auto ec = glz::send_value(socket, std::format("Client {}, {}", id, tick))) { std::cerr << ec.message() << '\n'; return; } From 060a6201558eaa655c0d82f6692b0edb6b66b12f Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 16:14:15 -0500 Subject: [PATCH 121/309] fixed repe_server response --- include/glaze/network/repe_server.hpp | 2 +- tests/asio_repe/client/repe_client.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/glaze/network/repe_server.hpp b/include/glaze/network/repe_server.hpp index 8a392f3f8d..8b2fb8002b 100644 --- a/include/glaze/network/repe_server.hpp +++ b/include/glaze/network/repe_server.hpp @@ -52,7 +52,7 @@ namespace glz else { auto response = registry.call(buffer); if (response) { - if (auto ec = send(socket, buffer)) { + if (auto ec = send(socket, response->value())) { std::fprintf(stderr, "%s\n", ec.message().c_str()); } } diff --git a/tests/asio_repe/client/repe_client.cpp b/tests/asio_repe/client/repe_client.cpp index b6e5bb7fe6..89356f02ef 100644 --- a/tests/asio_repe/client/repe_client.cpp +++ b/tests/asio_repe/client/repe_client.cpp @@ -36,7 +36,7 @@ void asio_client_test() } int sum{}; - if (auto e_call = client.call({"/sum"}, data, sum); e_call) { + if (auto e_call = client.call({"/sum"}, data, sum)) { std::cerr << glz::write_json(e_call).value_or("error") << '\n'; } else { From b2dbbb9be2f3b72bbb72a8f9f9f1bbded3a95103 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 16:15:11 -0500 Subject: [PATCH 122/309] running 100 clients again --- tests/asio_repe/client/repe_client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/asio_repe/client/repe_client.cpp b/tests/asio_repe/client/repe_client.cpp index 89356f02ef..2abf9b6b11 100644 --- a/tests/asio_repe/client/repe_client.cpp +++ b/tests/asio_repe/client/repe_client.cpp @@ -8,7 +8,7 @@ void asio_client_test() { try { - constexpr auto N = 1; + constexpr auto N = 100; std::vector> clients; clients.reserve(N); From 4ddf4b30392aec7d73d25c515a50d8578cf1f12f Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 16:20:26 -0500 Subject: [PATCH 123/309] Update repe_client.cpp --- tests/asio_repe/client/repe_client.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/asio_repe/client/repe_client.cpp b/tests/asio_repe/client/repe_client.cpp index 2abf9b6b11..40e1907d15 100644 --- a/tests/asio_repe/client/repe_client.cpp +++ b/tests/asio_repe/client/repe_client.cpp @@ -18,6 +18,9 @@ void asio_client_test() for (size_t i = 0; i < N; ++i) { clients.emplace_back(glz::repe_client<>{"127.0.0.1", 8080}); } + + std::mutex mtx{}; + std::vector results{}; for (size_t i = 0; i < N; ++i) { threads.emplace_back(std::async([&, i] { @@ -40,7 +43,9 @@ void asio_client_test() std::cerr << glz::write_json(e_call).value_or("error") << '\n'; } else { + std::unique_lock lock{mtx}; std::cout << "i: " << i << ", " << sum << '\n'; + results.emplace_back(sum); } })); } @@ -48,6 +53,12 @@ void asio_client_test() for (auto& t : threads) { t.get(); } + + for (auto v : results) { + if (v != 4950) { + std::abort(); + } + } } catch (const std::exception& e) { std::cerr << e.what() << '\n'; From 152eca7fa6c1506a3c7e77a47f15f2e3f8c0629b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 16:35:49 -0500 Subject: [PATCH 124/309] updated --- include/glaze/network/repe_server.hpp | 9 +++++++-- tests/asio_repe/server/repe_server.cpp | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/include/glaze/network/repe_server.hpp b/include/glaze/network/repe_server.hpp index 8b2fb8002b..edbd883129 100644 --- a/include/glaze/network/repe_server.hpp +++ b/include/glaze/network/repe_server.hpp @@ -12,6 +12,7 @@ namespace glz struct repe_server { uint16_t port{}; + bool print_errors = false; glz::server server{}; struct glaze @@ -44,7 +45,9 @@ namespace glz try { while (active) { if (auto ec = receive(socket, buffer)) { - std::fprintf(stderr, "%s\n", ec.message().c_str()); + if (print_errors) { + std::fprintf(stderr, "%s\n", ec.message().c_str()); + } if (ec.value() == ip_error::client_disconnected) { return; } @@ -53,7 +56,9 @@ namespace glz auto response = registry.call(buffer); if (response) { if (auto ec = send(socket, response->value())) { - std::fprintf(stderr, "%s\n", ec.message().c_str()); + if (print_errors) { + std::fprintf(stderr, "%s\n", ec.message().c_str()); + } } } } diff --git a/tests/asio_repe/server/repe_server.cpp b/tests/asio_repe/server/repe_server.cpp index 2c402d91f8..4380aaf063 100644 --- a/tests/asio_repe/server/repe_server.cpp +++ b/tests/asio_repe/server/repe_server.cpp @@ -18,7 +18,7 @@ void run_server() std::cout << "Server active...\n"; try { - glz::repe_server<> server{.port = 8080}; + glz::repe_server<> server{.port = 8080, .print_errors = true}; api methods{}; server.on(methods); server.run(); From d0f5ca1cf8439d2972c0f0e5da878b10d1a5b81d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 16:52:59 -0500 Subject: [PATCH 125/309] Update repe_server.cpp --- tests/asio_repe/server/repe_server.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/asio_repe/server/repe_server.cpp b/tests/asio_repe/server/repe_server.cpp index 4380aaf063..12a731d55c 100644 --- a/tests/asio_repe/server/repe_server.cpp +++ b/tests/asio_repe/server/repe_server.cpp @@ -13,7 +13,7 @@ struct api #include -void run_server() +int main() { std::cout << "Server active...\n"; @@ -28,11 +28,6 @@ void run_server() } std::cout << "Server closed...\n"; -} - -int main() -{ - run_server(); return 0; } From c3bfaffe62c7d3936432449c3548cbbd8dd2890f Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 16:55:03 -0500 Subject: [PATCH 126/309] Update repe_server.hpp --- include/glaze/network/repe_server.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/glaze/network/repe_server.hpp b/include/glaze/network/repe_server.hpp index edbd883129..691828be87 100644 --- a/include/glaze/network/repe_server.hpp +++ b/include/glaze/network/repe_server.hpp @@ -37,8 +37,10 @@ namespace glz server.port = port; auto ec = server.accept([this](socket&& socket, auto& active) { - // TODO: handle potential error - std::ignore = socket.no_delay(); + if (not socket.no_delay()) { + std::printf("%s", "no_delay failed"); + return; + } std::string buffer{}; From a06c96c0c64219d91bdf2d70236709fcd1290d0d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Fri, 21 Jun 2024 17:01:25 -0500 Subject: [PATCH 127/309] Removing asio --- tests/CMakeLists.txt | 2 +- tests/asio_repe/CMakeLists.txt | 15 --------------- tests/repe_server_client/CMakeLists.txt | 4 ++++ .../client/CMakeLists.txt | 2 +- .../client/repe_client.cpp | 0 .../server/CMakeLists.txt | 2 +- .../server/repe_server.cpp | 0 7 files changed, 7 insertions(+), 18 deletions(-) delete mode 100644 tests/asio_repe/CMakeLists.txt create mode 100644 tests/repe_server_client/CMakeLists.txt rename tests/{asio_repe => repe_server_client}/client/CMakeLists.txt (67%) rename tests/{asio_repe => repe_server_client}/client/repe_client.cpp (100%) rename tests/{asio_repe => repe_server_client}/server/CMakeLists.txt (67%) rename tests/{asio_repe => repe_server_client}/server/repe_server.cpp (100%) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 81eab1981d..838d003ea7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -52,7 +52,7 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") target_compile_options(glz_test_exceptions INTERFACE /W4 /wd4459 /wd4805) endif() -add_subdirectory(asio_repe) +add_subdirectory(repe_server_client) add_subdirectory(api_test) add_subdirectory(binary_test) add_subdirectory(cli_menu_test) diff --git a/tests/asio_repe/CMakeLists.txt b/tests/asio_repe/CMakeLists.txt deleted file mode 100644 index 70417b38f8..0000000000 --- a/tests/asio_repe/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -project(asio_repe) - -FetchContent_Declare( - asio - GIT_REPOSITORY https://github.com/chriskohlhoff/asio.git - GIT_TAG asio-1-30-1 - GIT_SHALLOW TRUE -) -FetchContent_GetProperties(asio) -if(NOT asio_POPULATED) - FetchContent_Populate(asio) -endif() - -add_subdirectory(server) -add_subdirectory(client) diff --git a/tests/repe_server_client/CMakeLists.txt b/tests/repe_server_client/CMakeLists.txt new file mode 100644 index 0000000000..ff08e8207a --- /dev/null +++ b/tests/repe_server_client/CMakeLists.txt @@ -0,0 +1,4 @@ +project(repe_server_client) + +add_subdirectory(server) +add_subdirectory(client) diff --git a/tests/asio_repe/client/CMakeLists.txt b/tests/repe_server_client/client/CMakeLists.txt similarity index 67% rename from tests/asio_repe/client/CMakeLists.txt rename to tests/repe_server_client/client/CMakeLists.txt index 67ac43d3d4..19f9c0b70b 100644 --- a/tests/asio_repe/client/CMakeLists.txt +++ b/tests/repe_server_client/client/CMakeLists.txt @@ -2,7 +2,7 @@ project(repe_client) add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp) -target_include_directories(${PROJECT_NAME} PRIVATE include ${asio_SOURCE_DIR}/asio/include) +target_include_directories(${PROJECT_NAME} PRIVATE include) target_link_libraries(${PROJECT_NAME} PRIVATE glz_test_exceptions) target_code_coverage(${PROJECT_NAME} AUTO ALL) \ No newline at end of file diff --git a/tests/asio_repe/client/repe_client.cpp b/tests/repe_server_client/client/repe_client.cpp similarity index 100% rename from tests/asio_repe/client/repe_client.cpp rename to tests/repe_server_client/client/repe_client.cpp diff --git a/tests/asio_repe/server/CMakeLists.txt b/tests/repe_server_client/server/CMakeLists.txt similarity index 67% rename from tests/asio_repe/server/CMakeLists.txt rename to tests/repe_server_client/server/CMakeLists.txt index 0ee53d4889..50dfb91273 100644 --- a/tests/asio_repe/server/CMakeLists.txt +++ b/tests/repe_server_client/server/CMakeLists.txt @@ -2,7 +2,7 @@ project(repe_server) add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp) -target_include_directories(${PROJECT_NAME} PRIVATE include ${asio_SOURCE_DIR}/asio/include) +target_include_directories(${PROJECT_NAME} PRIVATE include) target_link_libraries(${PROJECT_NAME} PRIVATE glz_test_exceptions) target_code_coverage(${PROJECT_NAME} AUTO ALL) \ No newline at end of file diff --git a/tests/asio_repe/server/repe_server.cpp b/tests/repe_server_client/server/repe_server.cpp similarity index 100% rename from tests/asio_repe/server/repe_server.cpp rename to tests/repe_server_client/server/repe_server.cpp From 908661bba5d18d544346970579beb40c834240e5 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 24 Jun 2024 12:45:17 -0500 Subject: [PATCH 128/309] Delete glaze_asio.hpp --- include/glaze/ext/glaze_asio.hpp | 368 ------------------------------- 1 file changed, 368 deletions(-) delete mode 100644 include/glaze/ext/glaze_asio.hpp diff --git a/include/glaze/ext/glaze_asio.hpp b/include/glaze/ext/glaze_asio.hpp deleted file mode 100644 index 887e3ea32f..0000000000 --- a/include/glaze/ext/glaze_asio.hpp +++ /dev/null @@ -1,368 +0,0 @@ -// Glaze Library -// For the license information refer to glaze.hpp - -#pragma once - -#if __has_include() && !defined(GLZ_USE_BOOST_ASIO) -#include -#elif __has_include() -#ifndef GLZ_USING_BOOST_ASIO -#define GLZ_USING_BOOST_ASIO -#endif -#include -#else -static_assert(false, "standalone asio must be included to use glaze/ext/glaze_asio.hpp"); -#endif - -#include -#include -#include - -#include "glaze/rpc/repe.hpp" - -namespace glz -{ -#if defined(GLZ_USING_BOOST_ASIO) - namespace asio = boost::asio; -#endif - inline void send_buffer(asio::ip::tcp::socket& socket, const std::string_view str) - { - const uint64_t size = str.size(); - std::array buffers{asio::buffer(&size, sizeof(uint64_t)), asio::buffer(str)}; - - asio::write(socket, buffers, asio::transfer_exactly(sizeof(uint64_t) + size)); - } - - inline void receive_buffer(asio::ip::tcp::socket& socket, std::string& str) - { - uint64_t size; - asio::read(socket, asio::buffer(&size, sizeof(size)), asio::transfer_exactly(sizeof(uint64_t))); - str.resize(size); - asio::read(socket, asio::buffer(str), asio::transfer_exactly(size)); - } - - inline asio::awaitable co_send_buffer(asio::ip::tcp::socket& socket, const std::string_view str) - { - const uint64_t size = str.size(); - std::array buffers{asio::buffer(&size, sizeof(uint64_t)), asio::buffer(str)}; - - co_await asio::async_write(socket, buffers, asio::transfer_exactly(sizeof(uint64_t) + size), asio::use_awaitable); - } - - inline asio::awaitable co_receive_buffer(asio::ip::tcp::socket& socket, std::string& str) - { - uint64_t size; - co_await asio::async_read(socket, asio::buffer(&size, sizeof(size)), asio::transfer_exactly(sizeof(uint64_t)), - asio::use_awaitable); - str.resize(size); - co_await asio::async_read(socket, asio::buffer(str), asio::transfer_exactly(size), asio::use_awaitable); - } - - inline asio::awaitable call_rpc(asio::ip::tcp::socket& socket, std::string& buffer) - { - co_await co_send_buffer(socket, buffer); - co_await co_receive_buffer(socket, buffer); - } - - template - struct func_traits; - - template - struct func_traits - { - using result_type = Result; - using params_type = void; - using std_func_sig = std::function; - }; - - template - struct func_traits - { - using result_type = Result; - using params_type = Params; - using std_func_sig = std::function; - }; - - template - using func_result_t = typename func_traits::result_type; - - template - using func_params_t = typename func_traits::params_type; - - template - using std_func_sig_t = typename func_traits::std_func_sig; - - template - struct asio_client - { - std::string host{"localhost"}; // host name - std::string service{""}; // often the port - uint32_t concurrency{1}; // how many threads to use - - struct glaze - { - using T = asio_client; - static constexpr auto value = glz::object(&T::host, &T::service, &T::concurrency); - }; - - std::shared_ptr ctx{}; - std::shared_ptr socket{}; - - std::shared_ptr buffer_pool = std::make_shared(); - - [[nodiscard]] std::error_code init() - { - ctx = std::make_shared(concurrency); - socket = std::make_shared(*ctx); - asio::ip::tcp::resolver resolver{*ctx}; - const auto endpoint = resolver.resolve(host, service); -#if !defined(GLZ_USING_BOOST_ASIO) - std::error_code ec{}; -#else - boost::system::error_code ec{}; -#endif - asio::connect(*socket, endpoint, ec); - if (ec) { - return ec; - } - return socket->set_option(asio::ip::tcp::no_delay(true), ec); - } - - template - [[nodiscard]] repe::error_t notify(repe::header&& header, Params&& params) - { - repe::unique_buffer ubuffer{buffer_pool.get()}; - auto& buffer = ubuffer.value(); - - header.notify = true; - const auto ec = repe::request(std::move(header), std::forward(params), buffer); - if (bool(ec)) [[unlikely]] { - return {repe::error_e::invalid_params, glz::format_error(ec, buffer)}; - } - - send_buffer(*socket, buffer); - return {}; - } - - template - [[nodiscard]] repe::error_t get(repe::header&& header, Result&& result) - { - repe::unique_buffer ubuffer{buffer_pool.get()}; - auto& buffer = ubuffer.value(); - - header.notify = false; - header.empty = true; // no params - const auto ec = repe::request(std::move(header), nullptr, buffer); - if (bool(ec)) [[unlikely]] { - return {repe::error_e::invalid_params, glz::format_error(ec, buffer)}; - } - - send_buffer(*socket, buffer); - receive_buffer(*socket, buffer); - - return repe::decode_response(std::forward(result), buffer); - } - - template - [[nodiscard]] glz::expected get(repe::header&& header) - { - std::decay_t result{}; - const auto error = get(std::move(header), result); - if (error) { - return glz::unexpected(error); - } - else { - return {result}; - } - } - - template - [[nodiscard]] repe::error_t set(repe::header&& header, Params&& params) - { - repe::unique_buffer ubuffer{buffer_pool.get()}; - auto& buffer = ubuffer.value(); - - header.notify = false; - const auto ec = repe::request(std::move(header), std::forward(params), buffer); - if (bool(ec)) [[unlikely]] { - return {repe::error_e::invalid_params, glz::format_error(ec, buffer)}; - } - - send_buffer(*socket, buffer); - receive_buffer(*socket, buffer); - - return repe::decode_response(buffer); - } - - template - [[nodiscard]] repe::error_t call(repe::header&& header, Params&& params, Result&& result) - { - repe::unique_buffer ubuffer{buffer_pool.get()}; - auto& buffer = ubuffer.value(); - - header.notify = false; - const auto ec = repe::request(std::move(header), std::forward(params), buffer); - if (bool(ec)) [[unlikely]] { - return {repe::error_e::invalid_params, glz::format_error(ec, buffer)}; - } - - send_buffer(*socket, buffer); - receive_buffer(*socket, buffer); - - return repe::decode_response(std::forward(result), buffer); - } - - [[nodiscard]] repe::error_t call(repe::header&& header) - { - repe::unique_buffer ubuffer{buffer_pool.get()}; - auto& buffer = ubuffer.value(); - - header.notify = false; - header.empty = true; // because no value provided - const auto ec = glz::write_json(std::forward_as_tuple(std::move(header), nullptr), buffer); - if (bool(ec)) [[unlikely]] { - return {repe::error_e::invalid_params, glz::format_error(ec, buffer)}; - } - - send_buffer(*socket, buffer); - receive_buffer(*socket, buffer); - - return repe::decode_response(buffer); - } - - template - [[nodiscard]] std_func_sig_t callable(repe::header&& header) - { - using Params = func_params_t; - using Result = func_result_t; - if constexpr (std::same_as) { - header.empty = true; - return [this, h = std::move(header)]() mutable -> Result { - std::decay_t result{}; - const auto e = call(repe::header{h}, result); - if (e) { - throw std::runtime_error(glz::write_json(e)); - } - return result; - }; - } - else { - header.empty = false; - return [this, h = std::move(header)](Params params) mutable -> Result { - std::decay_t result{}; - const auto e = call(repe::header{h}, params, result); - if (e) { - throw std::runtime_error(e.message); - } - return result; - }; - } - } - - template - [[deprecated("We use a buffer pool now, so this would cause allocations")]] [[nodiscard]] std::string call_raw( - repe::header&& header, Params&& params, repe::error_t& error) - { - std::string buffer{}; - - header.notify = false; - const auto ec = repe::request(std::move(header), std::forward(params), buffer); - if (bool(ec)) [[unlikely]] { - error = {repe::error_e::invalid_params, glz::format_error(ec, buffer)}; - return buffer; - } - - send_buffer(*socket, buffer); - receive_buffer(*socket, buffer); - return buffer; - } - }; - - template - struct asio_server - { - uint16_t port{}; - uint32_t concurrency{1}; // how many threads to use - - struct glaze - { - using T = asio_server; - static constexpr auto value = glz::object(&T::port, &T::concurrency); - }; - - std::shared_ptr ctx{}; - std::shared_ptr signals{}; - - repe::registry registry{}; - - void clear_registry() { registry.clear(); } - - template - requires(glz::detail::glaze_object_t || glz::detail::reflectable) - void on(T& value) - { - registry.template on(value); - } - - bool initialized = false; - - void init() - { - if (!initialized) { - ctx = std::make_shared(concurrency); - signals = std::make_shared(*ctx, SIGINT, SIGTERM); - } - initialized = true; - } - - void run() - { - if (!initialized) { - init(); - } - - signals->async_wait([&](auto, auto) { ctx->stop(); }); - - asio::co_spawn(*ctx, listener(), asio::detached); - - ctx->run(); - } - - // stop the server - void stop() - { - if (ctx) { - ctx->stop(); - } - } - - asio::awaitable run_instance(asio::ip::tcp::socket socket) - { - socket.set_option(asio::ip::tcp::no_delay(true)); - std::string buffer{}; - - try { - while (true) { - co_await co_receive_buffer(socket, buffer); - auto response = registry.call(buffer); - if (response) { - co_await co_send_buffer(socket, response->value()); - } - } - } - catch (const std::exception& e) { - std::fprintf(stderr, "%s\n", e.what()); - } - } - - asio::awaitable listener() - { - auto executor = co_await asio::this_coro::executor; - asio::ip::tcp::acceptor acceptor(executor, {asio::ip::tcp::v6(), port}); - while (true) { - auto socket = co_await acceptor.async_accept(asio::use_awaitable); - asio::co_spawn(executor, run_instance(std::move(socket)), asio::detached); - } - } - }; -} From b7bc4f3c487a17fd32f7e0e3d722618b99fcad60 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 25 Jun 2024 10:18:18 -0500 Subject: [PATCH 129/309] socket_io.hpp --- include/glaze/network/socket.hpp | 67 ------------------------- include/glaze/network/socket_io.hpp | 77 +++++++++++++++++++++++++++++ tests/socket_test/socket_test.cpp | 1 + 3 files changed, 78 insertions(+), 67 deletions(-) create mode 100644 include/glaze/network/socket_io.hpp diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index bf9cb93fac..235bcabde0 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -441,73 +441,6 @@ namespace glz return {}; } }; - - template - [[nodiscard]] std::error_code receive(socket& sckt, Buffer& buffer) - { - uint64_t header{}; - if (auto ec = sckt.receive(header, buffer)) { - return ec; - } - return {}; - } - - template - [[nodiscard]] std::error_code send(socket& sckt, Buffer& buffer) - { - uint64_t header = uint64_t(buffer.size()); - - if (auto ec = sckt.send(sv{reinterpret_cast(&header), sizeof(header)})) { - return ec; - } - - if (auto ec = sckt.send(buffer)) { - return ec; - } - - return {}; - } - - template - [[nodiscard]] std::error_code receive_value(socket& sckt, T&& value) - { - static thread_local std::string buffer{}; - - uint64_t header{}; - if (auto ec = sckt.receive(header, buffer)) { - return ec; - } - - if (auto ec = glz::read(std::forward(value), buffer)) { - std::fprintf(stderr, "%s\n", glz::format_error(ec, buffer).c_str()); - return {ip_error::receive_failed, ip_error_category::instance()}; - } - - return {}; - } - - template - [[nodiscard]] std::error_code send_value(socket& sckt, T&& value) - { - static thread_local std::string buffer{}; - - if (auto ec = glz::write(std::forward(value), buffer)) { - std::fprintf(stderr, "%s\n", glz::format_error(ec, buffer).c_str()); - return {ip_error::send_failed, ip_error_category::instance()}; - } - - uint64_t header = uint64_t(buffer.size()); - - if (auto ec = sckt.send(sv{reinterpret_cast(&header), sizeof(header)})) { - return ec; - } - - if (auto ec = sckt.send(buffer)) { - return ec; - } - - return {}; - } } #undef GLZ_CLOSE_SOCKET diff --git a/include/glaze/network/socket_io.hpp b/include/glaze/network/socket_io.hpp new file mode 100644 index 0000000000..6f22133529 --- /dev/null +++ b/include/glaze/network/socket_io.hpp @@ -0,0 +1,77 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +#include "glaze/glaze.hpp" +#include "glaze/network/socket.hpp" + +namespace glz +{ + template + [[nodiscard]] std::error_code receive(socket& sckt, Buffer& buffer) + { + uint64_t header{}; + if (auto ec = sckt.receive(header, buffer)) { + return ec; + } + return {}; + } + + template + [[nodiscard]] std::error_code send(socket& sckt, Buffer& buffer) + { + uint64_t header = uint64_t(buffer.size()); + + if (auto ec = sckt.send(sv{reinterpret_cast(&header), sizeof(header)})) { + return ec; + } + + if (auto ec = sckt.send(buffer)) { + return ec; + } + + return {}; + } + + template + [[nodiscard]] std::error_code receive_value(socket& sckt, T&& value) + { + static thread_local std::string buffer{}; + + uint64_t header{}; + if (auto ec = sckt.receive(header, buffer)) { + return ec; + } + + if (auto ec = glz::read(std::forward(value), buffer)) { + std::fprintf(stderr, "%s\n", glz::format_error(ec, buffer).c_str()); + return {ip_error::receive_failed, ip_error_category::instance()}; + } + + return {}; + } + + template + [[nodiscard]] std::error_code send_value(socket& sckt, T&& value) + { + static thread_local std::string buffer{}; + + if (auto ec = glz::write(std::forward(value), buffer)) { + std::fprintf(stderr, "%s\n", glz::format_error(ec, buffer).c_str()); + return {ip_error::send_failed, ip_error_category::instance()}; + } + + uint64_t header = uint64_t(buffer.size()); + + if (auto ec = sckt.send(sv{reinterpret_cast(&header), sizeof(header)})) { + return ec; + } + + if (auto ec = sckt.send(buffer)) { + return ec; + } + + return {}; + } +} diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 4cd3eaab50..11e7ae405c 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -4,6 +4,7 @@ #define UT_RUN_TIME_ONLY #include "glaze/network/server.hpp" +#include "glaze/network/socket_io.hpp" #include #include From 892c1c5d44530babc4310f45acf7627f1da8a8be Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 25 Jun 2024 10:26:59 -0500 Subject: [PATCH 130/309] Update socket.hpp --- include/glaze/network/socket.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 235bcabde0..fc9eda7dc5 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -240,7 +240,6 @@ namespace glz struct ip_error_category : public std::error_category { - // MSVC deadlocks if this returns a static instance static const ip_error_category& instance() { static ip_error_category instance{}; return instance; From cfc43c448ce11039a75010724f42a8c04e19e523 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 25 Jun 2024 10:39:23 -0500 Subject: [PATCH 131/309] added glz::error_category --- include/glaze/core/error.hpp | 28 ++++++++++++++++++++++++++++ include/glaze/network/socket_io.hpp | 7 +++---- 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 include/glaze/core/error.hpp diff --git a/include/glaze/core/error.hpp b/include/glaze/core/error.hpp new file mode 100644 index 0000000000..f991b15316 --- /dev/null +++ b/include/glaze/core/error.hpp @@ -0,0 +1,28 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +#include "glaze/core/context.hpp" +#include "glaze/core/common.hpp" + +#include + +namespace glz +{ + struct error_category : public std::error_category + { + static const error_category& instance() { + static error_category instance{}; + return instance; + } + + const char* name() const noexcept override { return "glz::error_category"; } + + std::string message(int ec) const override + { + static constexpr auto arr = detail::make_enum_to_string_array(); + return std::string{arr[ec]}; + } + }; +} diff --git a/include/glaze/network/socket_io.hpp b/include/glaze/network/socket_io.hpp index 6f22133529..f168475bc7 100644 --- a/include/glaze/network/socket_io.hpp +++ b/include/glaze/network/socket_io.hpp @@ -5,6 +5,7 @@ #include "glaze/glaze.hpp" #include "glaze/network/socket.hpp" +#include "glaze/core/error.hpp" namespace glz { @@ -45,8 +46,7 @@ namespace glz } if (auto ec = glz::read(std::forward(value), buffer)) { - std::fprintf(stderr, "%s\n", glz::format_error(ec, buffer).c_str()); - return {ip_error::receive_failed, ip_error_category::instance()}; + return {int(ec.ec), error_category::instance()}; } return {}; @@ -58,8 +58,7 @@ namespace glz static thread_local std::string buffer{}; if (auto ec = glz::write(std::forward(value), buffer)) { - std::fprintf(stderr, "%s\n", glz::format_error(ec, buffer).c_str()); - return {ip_error::send_failed, ip_error_category::instance()}; + return {int(ec.ec), error_category::instance()}; } uint64_t header = uint64_t(buffer.size()); From 7f8ebb39904e1964202b6e258bd1945c8b5b0044 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 25 Jun 2024 10:41:56 -0500 Subject: [PATCH 132/309] add socket_io.hpp includes --- include/glaze/network/repe_client.hpp | 1 + include/glaze/network/repe_server.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/include/glaze/network/repe_client.hpp b/include/glaze/network/repe_client.hpp index 09d5591cab..9c1cf42875 100644 --- a/include/glaze/network/repe_client.hpp +++ b/include/glaze/network/repe_client.hpp @@ -4,6 +4,7 @@ #pragma once #include "glaze/network/socket.hpp" +#include "glaze/network/socket_io.hpp" #include "glaze/rpc/repe.hpp" namespace glz diff --git a/include/glaze/network/repe_server.hpp b/include/glaze/network/repe_server.hpp index 691828be87..49b7c95554 100644 --- a/include/glaze/network/repe_server.hpp +++ b/include/glaze/network/repe_server.hpp @@ -4,6 +4,7 @@ #pragma once #include "glaze/network/server.hpp" +#include "glaze/network/socket_io.hpp" #include "glaze/rpc/repe.hpp" namespace glz From 418abc8323d92727cecd9989a00f75ca51e140b2 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 25 Jun 2024 10:55:05 -0500 Subject: [PATCH 133/309] Adding libfork dependency --- include/glaze/network/server.hpp | 5 +++++ tests/socket_test/CMakeLists.txt | 11 ++++++++++- tests/socket_test/socket_test.cpp | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index 4a0574960e..3740c52dbf 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -5,6 +5,11 @@ #include "glaze/network/socket.hpp" +#if __has_include() +#else +static_assert("libfork is required for using glaze network code. The library must be included by the developer."); +#endif + #ifdef _WIN32 #define GLZ_CLOSE_SOCKET closesocket #define GLZ_EVENT_CLOSE WSACloseEvent diff --git a/tests/socket_test/CMakeLists.txt b/tests/socket_test/CMakeLists.txt index 8685725812..b7cf8d62a4 100644 --- a/tests/socket_test/CMakeLists.txt +++ b/tests/socket_test/CMakeLists.txt @@ -1,8 +1,17 @@ project(socket_test) +FetchContent_Declare( + libfork + GIT_REPOSITORY https://github.com/conorwilliams/libfork.git + GIT_TAG v3.8.0 + GIT_SHALLOW TRUE +) + +FetchContent_MakeAvailable(libfork) + add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp) -target_link_libraries(${PROJECT_NAME} PRIVATE glz_test_common) +target_link_libraries(${PROJECT_NAME} PRIVATE glz_test_common libfork::libfork) add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 11e7ae405c..cc44e1b02e 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "ut/ut.hpp" From 9db13bbee77e0183302bea6722906f4a287ce06b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 25 Jun 2024 16:26:39 -0500 Subject: [PATCH 134/309] Revert "Adding libfork dependency" This reverts commit 418abc8323d92727cecd9989a00f75ca51e140b2. --- include/glaze/network/server.hpp | 5 ----- tests/socket_test/CMakeLists.txt | 11 +---------- tests/socket_test/socket_test.cpp | 1 - 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index 3740c52dbf..4a0574960e 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -5,11 +5,6 @@ #include "glaze/network/socket.hpp" -#if __has_include() -#else -static_assert("libfork is required for using glaze network code. The library must be included by the developer."); -#endif - #ifdef _WIN32 #define GLZ_CLOSE_SOCKET closesocket #define GLZ_EVENT_CLOSE WSACloseEvent diff --git a/tests/socket_test/CMakeLists.txt b/tests/socket_test/CMakeLists.txt index b7cf8d62a4..8685725812 100644 --- a/tests/socket_test/CMakeLists.txt +++ b/tests/socket_test/CMakeLists.txt @@ -1,17 +1,8 @@ project(socket_test) -FetchContent_Declare( - libfork - GIT_REPOSITORY https://github.com/conorwilliams/libfork.git - GIT_TAG v3.8.0 - GIT_SHALLOW TRUE -) - -FetchContent_MakeAvailable(libfork) - add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp) -target_link_libraries(${PROJECT_NAME} PRIVATE glz_test_common libfork::libfork) +target_link_libraries(${PROJECT_NAME} PRIVATE glz_test_common) add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index cc44e1b02e..11e7ae405c 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include "ut/ut.hpp" From 0737c560f6a5ea7928160947e9a0de1757591b97 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 06:19:28 -0500 Subject: [PATCH 135/309] coroutine headers --- include/glaze/coroutine.hpp | 9 + include/glaze/coroutine/awaitable.hpp | 106 ++++++++ include/glaze/coroutine/delete.hpp | 9 + include/glaze/coroutine/generator.hpp | 185 +++++++++++++ include/glaze/coroutine/sync_wait.hpp | 330 +++++++++++++++++++++++ include/glaze/coroutine/task.hpp | 334 ++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/coroutine_test/CMakeLists.txt | 11 + tests/coroutine_test/coroutine_test.cpp | 46 ++++ 9 files changed, 1031 insertions(+) create mode 100644 include/glaze/coroutine.hpp create mode 100644 include/glaze/coroutine/awaitable.hpp create mode 100644 include/glaze/coroutine/delete.hpp create mode 100644 include/glaze/coroutine/generator.hpp create mode 100644 include/glaze/coroutine/sync_wait.hpp create mode 100644 include/glaze/coroutine/task.hpp create mode 100644 tests/coroutine_test/CMakeLists.txt create mode 100644 tests/coroutine_test/coroutine_test.cpp diff --git a/include/glaze/coroutine.hpp b/include/glaze/coroutine.hpp new file mode 100644 index 0000000000..9a6b28bb63 --- /dev/null +++ b/include/glaze/coroutine.hpp @@ -0,0 +1,9 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +#include "glaze/coroutine/awaitable.hpp" +#include "glaze/coroutine/generator.hpp" +#include "glaze/coroutine/sync_wait.hpp" +#include "glaze/coroutine/task.hpp" diff --git a/include/glaze/coroutine/awaitable.hpp b/include/glaze/coroutine/awaitable.hpp new file mode 100644 index 0000000000..e65c0c31e4 --- /dev/null +++ b/include/glaze/coroutine/awaitable.hpp @@ -0,0 +1,106 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include +#include +#include +#include + +namespace glz +{ + template + concept in_types = (std::same_as || ...); + + /** + * This concept declares a type that is required to meet the c++20 coroutine operator co_await() + * retun type. It requires the following three member functions: + * await_ready() -> bool + * await_suspend(std::coroutine_handle<>) -> void|bool|std::coroutine_handle<> + * await_resume() -> decltype(auto) + * Where the return type on await_resume is the requested return of the awaitable. + */ + template + concept awaiter = requires(type t, std::coroutine_handle<> c) { + { + t.await_ready() + } -> std::same_as; + { + t.await_suspend(c) + } -> in_types>; + { + t.await_resume() + }; + }; + + template + concept member_co_await_awaitable = requires(type t) { + { + t.operator co_await() + } -> awaiter; + }; + + template + concept global_co_await_awaitable = requires(type t) { + { + operator co_await(t) + } -> awaiter; + }; + + /** + * This concept declares a type that can be operator co_await()'ed and returns an awaiter_type. + */ + template + concept awaitable = member_co_await_awaitable || global_co_await_awaitable || awaiter; + + template + concept awaiter_void = awaiter && requires(type t) { + { + t.await_resume() + } -> std::same_as; + }; + + template + concept member_co_await_awaitable_void = requires(type t) { + { + t.operator co_await() + } -> awaiter_void; + }; + + template + concept global_co_await_awaitable_void = requires(type t) { + { + operator co_await(t) + } -> awaiter_void; + }; + + template + concept awaitable_void = + member_co_await_awaitable_void || global_co_await_awaitable_void || awaiter_void; + + template + struct awaitable_traits + {}; + + template + static auto get_awaiter(awaitable&& value) + { + if constexpr (member_co_await_awaitable) + return std::forward(value).operator co_await(); + else if constexpr (global_co_await_awaitable) + return operator co_await(std::forward(value)); + else if constexpr (awaiter) { + return std::forward(value); + } + } + + template + struct awaitable_traits + { + using awaiter_type = decltype(get_awaiter(std::declval())); + using awaiter_return_type = decltype(std::declval().await_resume()); + }; +} diff --git a/include/glaze/coroutine/delete.hpp b/include/glaze/coroutine/delete.hpp new file mode 100644 index 0000000000..c319309bf6 --- /dev/null +++ b/include/glaze/coroutine/delete.hpp @@ -0,0 +1,9 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include "glaze/csv/read.hpp" +#include "glaze/csv/write.hpp" diff --git a/include/glaze/coroutine/generator.hpp b/include/glaze/coroutine/generator.hpp new file mode 100644 index 0000000000..4d67d5de10 --- /dev/null +++ b/include/glaze/coroutine/generator.hpp @@ -0,0 +1,185 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include +#include +#include +#include + +namespace glz +{ + template + struct generator; + + namespace detail + { + template + struct generator_promise + { + using value_type = std::remove_reference_t; + using reference_type = std::conditional_t, T, T&>; + using pointer_type = value_type*; + + generator_promise() = default; + + auto get_return_object() noexcept -> generator; + + auto initial_suspend() const { return std::suspend_always{}; } + + auto final_suspend() const noexcept(true) { return std::suspend_always{}; } + + template ::value, int> = 0> + auto yield_value(std::remove_reference_t& value) noexcept + { + m_value = std::addressof(value); + return std::suspend_always{}; + } + + auto yield_value(std::remove_reference_t&& value) noexcept + { + m_value = std::addressof(value); + return std::suspend_always{}; + } + + auto unhandled_exception() -> void { m_exception = std::current_exception(); } + + auto return_void() noexcept -> void {} + + auto value() const noexcept -> reference_type { return static_cast(*m_value); } + + template + auto await_transform(U&& value) -> std::suspend_never = delete; + + auto rethrow_if_exception() -> void + { + if (m_exception) { + std::rethrow_exception(m_exception); + } + } + + private: + pointer_type m_value{nullptr}; + std::exception_ptr m_exception; + }; + + struct generator_sentinel + {}; + + template + struct generator_iterator + { + using coroutine_handle = std::coroutine_handle>; + + using iterator_category = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = typename generator_promise::value_type; + using reference = typename generator_promise::reference_type; + using pointer = typename generator_promise::pointer_type; + + generator_iterator() noexcept {} + + explicit generator_iterator(coroutine_handle coroutine) noexcept : m_coroutine(coroutine) {} + + friend auto operator==(const generator_iterator& it, generator_sentinel) noexcept -> bool + { + return it.m_coroutine == nullptr || it.m_coroutine.done(); + } + + friend auto operator!=(const generator_iterator& it, generator_sentinel s) noexcept -> bool + { + return !(it == s); + } + + friend auto operator==(generator_sentinel s, const generator_iterator& it) noexcept -> bool + { + return (it == s); + } + + friend auto operator!=(generator_sentinel s, const generator_iterator& it) noexcept -> bool { return it != s; } + + generator_iterator& operator++() + { + m_coroutine.resume(); + if (m_coroutine.done()) { + m_coroutine.promise().rethrow_if_exception(); + } + + return *this; + } + + auto operator++(int) -> void { (void)operator++(); } + + reference operator*() const noexcept { return m_coroutine.promise().value(); } + + pointer operator->() const noexcept { return std::addressof(operator*()); } + + private: + coroutine_handle m_coroutine{nullptr}; + }; + + } // namespace detail + + template + struct generator : public std::ranges::view_base + { + using promise_type = detail::generator_promise; + using iterator = detail::generator_iterator; + using sentinel = detail::generator_sentinel; + + generator() noexcept : m_coroutine(nullptr) {} + + generator(const generator&) = delete; + generator(generator&& other) noexcept : m_coroutine(other.m_coroutine) { other.m_coroutine = nullptr; } + + auto operator=(const generator&) = delete; + auto operator=(generator&& other) noexcept -> generator& + { + m_coroutine = other.m_coroutine; + other.m_coroutine = nullptr; + + return *this; + } + + ~generator() + { + if (m_coroutine) { + m_coroutine.destroy(); + } + } + + auto begin() -> iterator + { + if (m_coroutine != nullptr) { + m_coroutine.resume(); + if (m_coroutine.done()) { + m_coroutine.promise().rethrow_if_exception(); + } + } + + return iterator{m_coroutine}; + } + + auto end() noexcept -> sentinel { return sentinel{}; } + + private: + friend struct detail::generator_promise; + + explicit generator(std::coroutine_handle coroutine) noexcept : m_coroutine(coroutine) {} + + std::coroutine_handle m_coroutine; + }; + + namespace detail + { + template + auto generator_promise::get_return_object() noexcept -> generator + { + return generator{std::coroutine_handle>::from_promise(*this)}; + } + + } +} diff --git a/include/glaze/coroutine/sync_wait.hpp b/include/glaze/coroutine/sync_wait.hpp new file mode 100644 index 0000000000..02936ee22d --- /dev/null +++ b/include/glaze/coroutine/sync_wait.hpp @@ -0,0 +1,330 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#ifndef GLZ_THROW_OR_ABORT +#if __cpp_exceptions +#define GLZ_THROW_OR_ABORT(EXC) (throw(EXC)) +#include +#else +#define GLZ_THROW_OR_ABORT(EXC) (std::abort()) +#endif +#endif + +#include +#include +#include +#include + +#include "glaze/coroutine/awaitable.hpp" + +namespace glz +{ + namespace detail + { + struct unset_return_value + { + unset_return_value() {} + unset_return_value(unset_return_value&&) = delete; + unset_return_value(const unset_return_value&) = delete; + auto operator=(unset_return_value&&) = delete; + auto operator=(const unset_return_value&) = delete; + }; + + struct sync_wait_event + { + sync_wait_event(bool initially_set = false) : m_set(initially_set) {} + sync_wait_event(const sync_wait_event&) = delete; + sync_wait_event(sync_wait_event&&) = delete; + auto operator=(const sync_wait_event&) -> sync_wait_event& = delete; + auto operator=(sync_wait_event&&) -> sync_wait_event& = delete; + ~sync_wait_event() = default; + + void set() noexcept { + m_set.exchange(true, std::memory_order::release); + m_cv.notify_all(); + } + + void reset() noexcept { + m_set.exchange(false, std::memory_order::release); + } + + void wait() noexcept { + std::unique_lock lk{m_mutex}; + m_cv.wait(lk, [this] { return m_set.load(std::memory_order::acquire); }); + } + + private: + std::mutex m_mutex; + std::condition_variable m_cv; + std::atomic m_set{false}; + }; + + struct sync_wait_task_promise_base + { + sync_wait_task_promise_base() noexcept = default; + + auto initial_suspend() noexcept -> std::suspend_always { return {}; } + + auto unhandled_exception() -> void { m_exception = std::current_exception(); } + + protected: + sync_wait_event* m_event{nullptr}; + std::exception_ptr m_exception; + + ~sync_wait_task_promise_base() = default; + }; + + template + struct sync_wait_task_promise : public sync_wait_task_promise_base + { + using coroutine_type = std::coroutine_handle>; + + static constexpr bool return_type_is_reference = std::is_reference_v; + using stored_type = std::conditional_t*, + std::remove_const_t>; + using variant_type = std::variant; + + sync_wait_task_promise() noexcept = default; + sync_wait_task_promise(const sync_wait_task_promise&) = delete; + sync_wait_task_promise(sync_wait_task_promise&&) = delete; + auto operator=(const sync_wait_task_promise&) -> sync_wait_task_promise& = delete; + auto operator=(sync_wait_task_promise&&) -> sync_wait_task_promise& = delete; + ~sync_wait_task_promise() = default; + + auto start(sync_wait_event& event) + { + m_event = &event; + coroutine_type::from_promise(*this).resume(); + } + + auto get_return_object() noexcept { return coroutine_type::from_promise(*this); } + + template + requires(return_type_is_reference and std::is_constructible_v) or + (not return_type_is_reference and std::is_constructible_v) + auto return_value(value_type&& value) -> void + { + if constexpr (return_type_is_reference) { + return_type ref = static_cast(value); + m_storage.template emplace(std::addressof(ref)); + } + else { + m_storage.template emplace(std::forward(value)); + } + } + + auto return_value(stored_type value) -> void + requires(not return_type_is_reference) + { + if constexpr (std::is_move_constructible_v) { + m_storage.template emplace(std::move(value)); + } + else { + m_storage.template emplace(value); + } + } + + auto final_suspend() noexcept + { + struct completion_notifier + { + auto await_ready() const noexcept { return false; } + auto await_suspend(coroutine_type coroutine) const noexcept { coroutine.promise().m_event->set(); } + auto await_resume() noexcept {}; + }; + + return completion_notifier{}; + } + + auto result() & -> decltype(auto) + { + if (std::holds_alternative(m_storage)) { + if constexpr (return_type_is_reference) { + return static_cast(*std::get(m_storage)); + } + else { + return static_cast(std::get(m_storage)); + } + } + else if (std::holds_alternative(m_storage)) { + std::rethrow_exception(std::get(m_storage)); + } + else { + GLZ_THROW_OR_ABORT(std::runtime_error{"The return value was never set, did you execute the coroutine?"}); + } + } + + auto result() const& -> decltype(auto) + { + if (std::holds_alternative(m_storage)) { + if constexpr (return_type_is_reference) { + return static_cast>(*std::get(m_storage)); + } + else { + return static_cast(std::get(m_storage)); + } + } + else if (std::holds_alternative(m_storage)) { + std::rethrow_exception(std::get(m_storage)); + } + else { + GLZ_THROW_OR_ABORT(std::runtime_error{"The return value was never set, did you execute the coroutine?"}); + } + } + + auto result() && -> decltype(auto) + { + if (std::holds_alternative(m_storage)) { + if constexpr (return_type_is_reference) { + return static_cast(*std::get(m_storage)); + } + else if constexpr (std::is_assignable_v) { + return static_cast(std::get(m_storage)); + } + else { + return static_cast(std::get(m_storage)); + } + } + else if (std::holds_alternative(m_storage)) { + std::rethrow_exception(std::get(m_storage)); + } + else { + GLZ_THROW_OR_ABORT(std::runtime_error{"The return value was never set, did you execute the coroutine?"}); + } + } + + private: + variant_type m_storage{}; + }; + + template <> + struct sync_wait_task_promise : public sync_wait_task_promise_base + { + using coroutine_type = std::coroutine_handle>; + + sync_wait_task_promise() noexcept = default; + ~sync_wait_task_promise() = default; + + auto start(sync_wait_event& event) + { + m_event = &event; + coroutine_type::from_promise(*this).resume(); + } + + auto get_return_object() noexcept { return coroutine_type::from_promise(*this); } + + auto final_suspend() noexcept + { + struct completion_notifier + { + auto await_ready() const noexcept { return false; } + auto await_suspend(coroutine_type coroutine) const noexcept { coroutine.promise().m_event->set(); } + auto await_resume() noexcept {}; + }; + + return completion_notifier{}; + } + + auto return_void() noexcept -> void {} + + auto result() -> void + { + if (m_exception) { + std::rethrow_exception(m_exception); + } + } + }; + + template + struct sync_wait_task + { + using promise_type = sync_wait_task_promise; + using coroutine_type = std::coroutine_handle; + + sync_wait_task(coroutine_type coroutine) noexcept : m_coroutine(coroutine) {} + + sync_wait_task(const sync_wait_task&) = delete; + sync_wait_task(sync_wait_task&& other) noexcept + : m_coroutine(std::exchange(other.m_coroutine, coroutine_type{})) + {} + auto operator=(const sync_wait_task&) -> sync_wait_task& = delete; + auto operator=(sync_wait_task&& other) -> sync_wait_task& + { + if (std::addressof(other) != this) { + m_coroutine = std::exchange(other.m_coroutine, coroutine_type{}); + } + + return *this; + } + + ~sync_wait_task() + { + if (m_coroutine) { + m_coroutine.destroy(); + } + } + + auto promise() & -> promise_type& { return m_coroutine.promise(); } + auto promise() const& -> const promise_type& { return m_coroutine.promise(); } + auto promise() && -> promise_type&& { return std::move(m_coroutine.promise()); } + + private: + coroutine_type m_coroutine; + }; + + template ::awaiter_return_type> + static auto make_sync_wait_task(awaitable_type&& a) -> sync_wait_task; + + template + static auto make_sync_wait_task(awaitable_type&& a) -> sync_wait_task + { + if constexpr (std::is_void_v) { + co_await std::forward(a); + co_return; + } + else { + co_return co_await std::forward(a); + } + } + } + + template ::awaiter_return_type> + auto sync_wait(awaitable_type&& a) -> decltype(auto) + { + detail::sync_wait_event e{}; + auto task = detail::make_sync_wait_task(std::forward(a)); + task.promise().start(e); + e.wait(); + + if constexpr (std::is_void_v) { + task.promise().result(); + return; + } + else if constexpr (std::is_reference_v) { + return task.promise().result(); + } + else if constexpr (std::is_move_assignable_v) { + // issue-242 + // For non-trivial types (or possibly types that don't fit in a register) + // the compiler will end up calling the ~return_type() when the promise + // is destructed at the end of sync_wait(). This causes the return_type + // object to also be destructed causingn the final return/move from + // sync_wait() to be a 'use after free' bug. To work around this the result + // must be moved off the promise object before the promise is destructed. + // Other solutions could be heap allocating the return_type but that has + // other downsides, for now it is determined that a double move is an + // acceptable solution to work around this bug. + auto result = std::move(task).promise().result(); + return result; + } + else { + return task.promise().result(); + } + } +} diff --git a/include/glaze/coroutine/task.hpp b/include/glaze/coroutine/task.hpp new file mode 100644 index 0000000000..a61129737d --- /dev/null +++ b/include/glaze/coroutine/task.hpp @@ -0,0 +1,334 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#ifndef GLZ_THROW_OR_ABORT +#if __cpp_exceptions +#define GLZ_THROW_OR_ABORT(EXC) (throw(EXC)) +#include +#else +#define GLZ_THROW_OR_ABORT(EXC) (std::abort()) +#endif +#endif + +#include +#include +#include + +namespace glz +{ + template + struct task; + + namespace detail + { + struct promise_base + { + friend struct final_awaitable; + struct final_awaitable + { + auto await_ready() const noexcept -> bool { return false; } + + template + auto await_suspend(std::coroutine_handle coroutine) noexcept -> std::coroutine_handle<> + { + // If there is a continuation call it, otherwise this is the end of the line. + auto& promise = coroutine.promise(); + if (promise.m_continuation != nullptr) { + return promise.m_continuation; + } + else { + return std::noop_coroutine(); + } + } + + auto await_resume() noexcept -> void + { + // no-op + } + }; + + promise_base() noexcept = default; + ~promise_base() = default; + + auto initial_suspend() noexcept { return std::suspend_always{}; } + + auto final_suspend() noexcept { return final_awaitable{}; } + + auto continuation(std::coroutine_handle<> continuation) noexcept -> void { m_continuation = continuation; } + + protected: + std::coroutine_handle<> m_continuation{nullptr}; + }; + + template + struct promise final : public promise_base + { + private: + struct unset_return_value + { + unset_return_value() {} + unset_return_value(unset_return_value&&) = delete; + unset_return_value(const unset_return_value&) = delete; + auto operator=(unset_return_value&&) = delete; + auto operator=(const unset_return_value&) = delete; + }; + + public: + using task_type = task; + using coroutine_handle = std::coroutine_handle>; + static constexpr bool return_type_is_reference = std::is_reference_v; + using stored_type = std::conditional_t*, + std::remove_const_t>; + using variant_type = std::variant; + + promise() noexcept {} + promise(const promise&) = delete; + promise(promise&& other) = delete; + promise& operator=(const promise&) = delete; + promise& operator=(promise&& other) = delete; + ~promise() = default; + + auto get_return_object() noexcept -> task_type; + + template + requires(return_type_is_reference and std::is_constructible_v) or + (not return_type_is_reference and std::is_constructible_v) + auto return_value(value_type&& value) -> void + { + if constexpr (return_type_is_reference) { + return_type ref = static_cast(value); + m_storage.template emplace(std::addressof(ref)); + } + else { + m_storage.template emplace(std::forward(value)); + } + } + + auto return_value(stored_type value) -> void + requires(not return_type_is_reference) + { + if constexpr (std::is_move_constructible_v) { + m_storage.template emplace(std::move(value)); + } + else { + m_storage.template emplace(value); + } + } + + auto unhandled_exception() noexcept -> void { new (&m_storage) variant_type(std::current_exception()); } + + auto result() & -> decltype(auto) + { + if (std::holds_alternative(m_storage)) { + if constexpr (return_type_is_reference) { + return static_cast(*std::get(m_storage)); + } + else { + return static_cast(std::get(m_storage)); + } + } + else if (std::holds_alternative(m_storage)) { + std::rethrow_exception(std::get(m_storage)); + } + else { + GLZ_THROW_OR_ABORT(std::runtime_error{"The return value was never set, did you execute the coroutine?"}); + } + } + + auto result() const& -> decltype(auto) + { + if (std::holds_alternative(m_storage)) { + if constexpr (return_type_is_reference) { + return static_cast>(*std::get(m_storage)); + } + else { + return static_cast(std::get(m_storage)); + } + } + else if (std::holds_alternative(m_storage)) { + std::rethrow_exception(std::get(m_storage)); + } + else { + GLZ_THROW_OR_ABORT(std::runtime_error{"The return value was never set, did you execute the coroutine?"}); + } + } + + auto result() && -> decltype(auto) + { + if (std::holds_alternative(m_storage)) { + if constexpr (return_type_is_reference) { + return static_cast(*std::get(m_storage)); + } + else if constexpr (std::is_move_constructible_v) { + return static_cast(std::get(m_storage)); + } + else { + return static_cast(std::get(m_storage)); + } + } + else if (std::holds_alternative(m_storage)) { + std::rethrow_exception(std::get(m_storage)); + } + else { + GLZ_THROW_OR_ABORT(std::runtime_error{"The return value was never set, did you execute the coroutine?"}); + } + } + + private: + variant_type m_storage{}; + }; + + template <> + struct promise : public promise_base + { + using task_type = task; + using coroutine_handle = std::coroutine_handle>; + + promise() noexcept = default; + promise(const promise&) = delete; + promise(promise&& other) = delete; + promise& operator=(const promise&) = delete; + promise& operator=(promise&& other) = delete; + ~promise() = default; + + auto get_return_object() noexcept -> task_type; + + auto return_void() noexcept -> void {} + + auto unhandled_exception() noexcept -> void { m_exception_ptr = std::current_exception(); } + + auto result() -> void + { + if (m_exception_ptr) { + std::rethrow_exception(m_exception_ptr); + } + } + + private: + std::exception_ptr m_exception_ptr{nullptr}; + }; + + } // namespace detail + + template + struct [[nodiscard]] task + { + using task_type = task; + using promise_type = detail::promise; + using coroutine_handle = std::coroutine_handle; + + struct awaitable_base + { + awaitable_base(coroutine_handle coroutine) noexcept : m_coroutine(coroutine) {} + + auto await_ready() const noexcept -> bool { return !m_coroutine || m_coroutine.done(); } + + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> std::coroutine_handle<> + { + m_coroutine.promise().continuation(awaiting_coroutine); + return m_coroutine; + } + + std::coroutine_handle m_coroutine{nullptr}; + }; + + task() noexcept : m_coroutine(nullptr) {} + + explicit task(coroutine_handle handle) : m_coroutine(handle) {} + task(const task&) = delete; + task(task&& other) noexcept : m_coroutine(std::exchange(other.m_coroutine, nullptr)) {} + + ~task() + { + if (m_coroutine != nullptr) { + m_coroutine.destroy(); + } + } + + auto operator=(const task&) -> task& = delete; + + auto operator=(task&& other) noexcept -> task& + { + if (std::addressof(other) != this) { + if (m_coroutine != nullptr) { + m_coroutine.destroy(); + } + + m_coroutine = std::exchange(other.m_coroutine, nullptr); + } + + return *this; + } + + /** + * @return True if the task is in its final suspend or if the task has been destroyed. + */ + auto is_ready() const noexcept -> bool { return m_coroutine == nullptr || m_coroutine.done(); } + + auto resume() -> bool + { + if (!m_coroutine.done()) { + m_coroutine.resume(); + } + return !m_coroutine.done(); + } + + auto destroy() -> bool + { + if (m_coroutine != nullptr) { + m_coroutine.destroy(); + m_coroutine = nullptr; + return true; + } + + return false; + } + + auto operator co_await() const& noexcept + { + struct awaitable : public awaitable_base + { + auto await_resume() -> decltype(auto) { return this->m_coroutine.promise().result(); } + }; + + return awaitable{m_coroutine}; + } + + auto operator co_await() const&& noexcept + { + struct awaitable : public awaitable_base + { + auto await_resume() -> decltype(auto) { return std::move(this->m_coroutine.promise()).result(); } + }; + + return awaitable{m_coroutine}; + } + + auto promise() & -> promise_type& { return m_coroutine.promise(); } + auto promise() const& -> const promise_type& { return m_coroutine.promise(); } + auto promise() && -> promise_type&& { return std::move(m_coroutine.promise()); } + + auto handle() -> coroutine_handle { return m_coroutine; } + + private: + coroutine_handle m_coroutine{nullptr}; + }; + + namespace detail + { + template + inline auto promise::get_return_object() noexcept -> task + { + return task{coroutine_handle::from_promise(*this)}; + } + + inline auto promise::get_return_object() noexcept -> task<> + { + return task<>{coroutine_handle::from_promise(*this)}; + } + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6e93dde410..6709d990e6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,6 +57,7 @@ add_subdirectory(api_test) add_subdirectory(binary_test) add_subdirectory(cli_menu_test) add_subdirectory(compare_test) +add_subdirectory(coroutine_test) add_subdirectory(csv_test) add_subdirectory(eigen_test) add_subdirectory(exceptions_test) diff --git a/tests/coroutine_test/CMakeLists.txt b/tests/coroutine_test/CMakeLists.txt new file mode 100644 index 0000000000..5d5bafd5f5 --- /dev/null +++ b/tests/coroutine_test/CMakeLists.txt @@ -0,0 +1,11 @@ +project(coroutine_test) + +add_compile_definitions(CURRENT_DIRECTORY="${CMAKE_CURRENT_SOURCE_DIR}") + +add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp) + +target_link_libraries(${PROJECT_NAME} PRIVATE glz_test_common) + +add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) + +target_code_coverage(${PROJECT_NAME} AUTO ALL) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp new file mode 100644 index 0000000000..16a3124c18 --- /dev/null +++ b/tests/coroutine_test/coroutine_test.cpp @@ -0,0 +1,46 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#define UT_RUN_TIME_ONLY + +#include "glaze/coroutine.hpp" +#include "ut/ut.hpp" + +using namespace ut; + +suite generator = [] { + std::atomic result{}; + auto task = [&](uint64_t count_to) -> glz::task + { + // Create a generator function that will yield and incrementing + // number each time its called. + auto gen = []() -> glz::generator + { + uint64_t i = 0; + while (true) + { + co_yield i; + ++i; + } + }; + + // Generate the next number until its greater than count to. + for (auto val : gen()) + { + std::cout << val << ", "; + result += val; + + if (val >= count_to) + { + break; + } + } + co_return; + }; + + glz::sync_wait(task(100)); + + expect(result == 5050) << result; +}; + +int main() { return 0; } From 2627136abd196ee57eaec6c8e0edde24015fd7cb Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 06:45:59 -0500 Subject: [PATCH 136/309] more coroutines --- include/glaze/coroutine.hpp | 1 + include/glaze/coroutine/concepts.hpp | 47 ++++ include/glaze/coroutine/event.hpp | 155 +++++++++++ include/glaze/coroutine/thread_pool.hpp | 351 ++++++++++++++++++++++++ tests/coroutine_test/coroutine_test.cpp | 81 ++++-- 5 files changed, 607 insertions(+), 28 deletions(-) create mode 100644 include/glaze/coroutine/concepts.hpp create mode 100644 include/glaze/coroutine/event.hpp create mode 100644 include/glaze/coroutine/thread_pool.hpp diff --git a/include/glaze/coroutine.hpp b/include/glaze/coroutine.hpp index 9a6b28bb63..5d04ca5906 100644 --- a/include/glaze/coroutine.hpp +++ b/include/glaze/coroutine.hpp @@ -7,3 +7,4 @@ #include "glaze/coroutine/generator.hpp" #include "glaze/coroutine/sync_wait.hpp" #include "glaze/coroutine/task.hpp" +#include "glaze/coroutine/thread_pool.hpp" diff --git a/include/glaze/coroutine/concepts.hpp b/include/glaze/coroutine/concepts.hpp new file mode 100644 index 0000000000..7cacac5d9c --- /dev/null +++ b/include/glaze/coroutine/concepts.hpp @@ -0,0 +1,47 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include +#include + +#include "glaze/coroutine/awaitable.hpp" + +namespace glz +{ + /** + * Concept to require that the range contains a specific type of value. + */ + template + concept range_of = std::ranges::range && std::is_same_v>; + + /** + * Concept to require that a sized range contains a specific type of value. + */ + template + concept sized_range_of = std::ranges::sized_range && std::is_same_v>; + + template + concept executor = requires(T t, std::coroutine_handle<> c) { + { + t.schedule() + } -> awaiter; + { + t.yield() + } -> awaiter; + { + t.resume(c) + } -> std::same_as; + }; + + template + concept io_exceutor = executor;/* and requires(T t, std::coroutine_handle<> c, fd_t fd, glz::poll_op op, + std::chrono::milliseconds timeout) { + { + t.poll(fd, op, timeout) + } -> std::same_as>; + };*/ +} diff --git a/include/glaze/coroutine/event.hpp b/include/glaze/coroutine/event.hpp new file mode 100644 index 0000000000..3e014dc47d --- /dev/null +++ b/include/glaze/coroutine/event.hpp @@ -0,0 +1,155 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include +#include + +#include "glaze/coroutine/concepts.hpp" + +namespace glz +{ + enum class resume_order_policy { + /// Last in first out, this is the default policy and will execute the fastest + /// if you do not need the first waiter to execute first upon the event being set. + lifo, + /// First in first out, this policy has an extra overhead to reverse the order of + /// the waiters but will guarantee the ordering is fifo. + fifo + }; + + /** + * Event is a manully triggered thread safe signal that can be co_await()'ed by multiple awaiters. + * Each awaiter should co_await the event and upon the event being set each awaiter will have their + * coroutine resumed. + * + * The event can be manually reset to the un-set state to be re-used. + * \code + t1: glz::event e; + ... + t2: func(glz::event& e) { ... co_await e; ... } + ... + t1: do_work(); + t1: e.set(); + ... + t2: resume() + * \endcode + */ + class event + { + public: + struct awaiter + { + /** + * @param e The event to wait for it to be set. + */ + awaiter(const event& e) noexcept : m_event(e) {} + + /** + * @return True if the event is already set, otherwise false to suspend this coroutine. + */ + auto await_ready() const noexcept -> bool { return m_event.is_set(); } + + /** + * Adds this coroutine to the list of awaiters in a thread safe fashion. If the event + * is set while attempting to add this coroutine to the awaiters then this will return false + * to resume execution immediately. + * @return False if the event is already set, otherwise true to suspend this coroutine. + */ + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool; + + /** + * Nothing to do on resume. + */ + auto await_resume() noexcept {} + + /// Refernce to the event that this awaiter is waiting on. + const event& m_event; + /// The awaiting continuation coroutine handle. + std::coroutine_handle<> m_awaiting_coroutine; + /// The next awaiter in line for this event, nullptr if this is the end. + awaiter* m_next{nullptr}; + }; + + /** + * Creates an event with the given initial state of being set or not set. + * @param initially_set By default all events start as not set, but if needed this parameter can + * set the event to already be triggered. + */ + explicit event(bool initially_set = false) noexcept; + ~event() = default; + + event(const event&) = delete; + event(event&&) = delete; + auto operator=(const event&) -> event& = delete; + auto operator=(event&&) -> event& = delete; + + /** + * @return True if this event is currently in the set state. + */ + auto is_set() const noexcept -> bool { return m_state.load(std::memory_order_acquire) == this; } + + /** + * Sets this event and resumes all awaiters. Note that all waiters will be resumed onto this + * thread of execution. + * @param policy The order in which the waiters should be resumed, defaults to LIFO since it + * is more efficient, FIFO requires reversing the order of the waiters first. + */ + auto set(resume_order_policy policy = resume_order_policy::lifo) noexcept -> void; + + /** + * Sets this event and resumes all awaiters onto the given executor. This will distribute + * the waiters across the executor's threads. + */ + template + auto set(executor_type& e, resume_order_policy policy = resume_order_policy::lifo) noexcept -> void + { + void* old_value = m_state.exchange(this, std::memory_order::acq_rel); + if (old_value != this) { + // If FIFO has been requsted then reverse the order upon resuming. + if (policy == resume_order_policy::fifo) { + old_value = reverse(static_cast(old_value)); + } + // else lifo nothing to do + + auto* waiters = static_cast(old_value); + while (waiters != nullptr) { + auto* next = waiters->m_next; + e.resume(waiters->m_awaiting_coroutine); + waiters = next; + } + } + } + + /** + * @return An awaiter struct to suspend and resume this coroutine for when the event is set. + */ + auto operator co_await() const noexcept -> awaiter { return awaiter(*this); } + + /** + * Resets the event from set to not set so it can be re-used. If the event is not currently + * set then this function has no effect. + */ + auto reset() noexcept -> void; + + protected: + /// For access to m_state. + friend struct awaiter; + /// The state of the event, nullptr is not set with zero awaiters. Set to an awaiter* there are + /// coroutines awaiting the event to be set, and set to this the event has triggered. + /// 1) nullptr == not set + /// 2) awaiter* == linked list of awaiters waiting for the event to trigger. + /// 3) this == The event is triggered and all awaiters are resumed. + mutable std::atomic m_state; + + private: + /** + * Reverses the set of waiters from LIFO->FIFO and returns the new head. + */ + auto reverse(awaiter* head) -> awaiter*; + }; + +} // namespace coro diff --git a/include/glaze/coroutine/thread_pool.hpp b/include/glaze/coroutine/thread_pool.hpp new file mode 100644 index 0000000000..a7fd25e70a --- /dev/null +++ b/include/glaze/coroutine/thread_pool.hpp @@ -0,0 +1,351 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#ifndef GLZ_THROW_OR_ABORT +#if __cpp_exceptions +#define GLZ_THROW_OR_ABORT(EXC) (throw(EXC)) +#include +#else +#define GLZ_THROW_OR_ABORT(EXC) (std::abort()) +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "glaze/coroutine/concepts.hpp" +#include "glaze/coroutine/event.hpp" +#include "glaze/coroutine/task.hpp" + +namespace glz +{ + /** + * Creates a thread pool that executes arbitrary coroutine tasks in a FIFO scheduler policy. + * The thread pool by default will create an execution thread per available core on the system. + * + * When shutting down, either by the thread pool destructing or by manually calling shutdown() + * the thread pool will stop accepting new tasks but will complete all tasks that were scheduled + * prior to the shutdown request. + */ + class thread_pool + { + public: + /** + * An operation is an awaitable type with a coroutine to resume the task scheduled on one of + * the executor threads. + */ + class operation + { + friend class thread_pool; + /** + * Only thread_pools can create operations when a task is being scheduled. + * @param tp The thread pool that created this operation. + */ + explicit operation(thread_pool& tp) noexcept : m_thread_pool(tp) {} + + public: + /** + * Operations always pause so the executing thread can be switched. + */ + auto await_ready() noexcept -> bool { return false; } + + /** + * Suspending always returns to the caller (using void return of await_suspend()) and + * stores the coroutine internally for the executing thread to resume from. + */ + void await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept + { + m_awaiting_coroutine = awaiting_coroutine; + m_thread_pool.schedule_impl(m_awaiting_coroutine); + + // void return on await_suspend suspends the _this_ coroutine, which is now scheduled on the + // thread pool and returns control to the caller. They could be sync_wait'ing or go do + // something else while this coroutine gets picked up by the thread pool. + } + + /** + * no-op as this is the function called first by the thread pool's executing thread. + */ + auto await_resume() noexcept -> void {} + + private: + /// The thread pool that this operation will execute on. + thread_pool& m_thread_pool; + /// The coroutine awaiting execution. + std::coroutine_handle<> m_awaiting_coroutine{nullptr}; + }; + + struct options + { + /// The number of executor threads for this thread pool. Uses the hardware concurrency + /// value by default. + uint32_t thread_count = std::thread::hardware_concurrency(); + /// Functor to call on each executor thread upon starting execution. The parameter is the + /// thread's ID assigned to it by the thread pool. + std::function on_thread_start_functor = nullptr; + /// Functor to call on each executor thread upon stopping execution. The parameter is the + /// thread's ID assigned to it by the thread pool. + std::function on_thread_stop_functor = nullptr; + }; + + /** + * @param opts Thread pool configuration options. + */ + explicit thread_pool(options opts = options{.thread_count = std::thread::hardware_concurrency(), + .on_thread_start_functor = nullptr, + .on_thread_stop_functor = nullptr}) : m_opts(std::move(opts)) + { + m_threads.reserve(m_opts.thread_count); + + for (uint32_t i = 0; i < m_opts.thread_count; ++i) + { + m_threads.emplace_back([this, i]() { executor(i); }); + } + } + + thread_pool(const thread_pool&) = delete; + thread_pool(thread_pool&&) = delete; + auto operator=(const thread_pool&) -> thread_pool& = delete; + auto operator=(thread_pool&&) -> thread_pool& = delete; + + virtual ~thread_pool() { + shutdown(); + } + + /** + * @return The number of executor threads for processing tasks. + */ + auto thread_count() const noexcept -> size_t { return m_threads.size(); } + + /** + * Schedules the currently executing coroutine to be run on this thread pool. This must be + * called from within the coroutines function body to schedule the coroutine on the thread pool. + * @throw std::runtime_error If the thread pool is `shutdown()` scheduling new tasks is not permitted. + * @return The operation to switch from the calling scheduling thread to the executor thread + * pool thread. + */ + [[nodiscard]] auto schedule() -> operation + { + if (!m_shutdown_requested.load(std::memory_order::acquire)) + { + m_size.fetch_add(1, std::memory_order::release); + return operation{*this}; + } + + GLZ_THROW_OR_ABORT(std::runtime_error("glz::thread_pool is shutting down, unable to schedule new tasks.")); + } + + /** + * @throw std::runtime_error If the thread pool is `shutdown()` scheduling new tasks is not permitted. + * @param f The function to execute on the thread pool. + * @param args The arguments to call the functor with. + * @return A task that wraps the given functor to be executed on the thread pool. + */ + template + [[nodiscard]] auto schedule(functor&& f, arguments... args) -> task(args)...))> + { + co_await schedule(); + + if constexpr (std::is_same_v(args)...))>) { + f(std::forward(args)...); + co_return; + } + else { + co_return f(std::forward(args)...); + } + } + + /** + * Schedules any coroutine handle that is ready to be resumed. + * @param handle The coroutine handle to schedule. + */ + void resume(std::coroutine_handle<> handle) noexcept + { + if (handle == nullptr) + { + return; + } + + m_size.fetch_add(1, std::memory_order::release); + schedule_impl(handle); + } + + /** + * Schedules the set of coroutine handles that are ready to be resumed. + * @param handles The coroutine handles to schedule. + */ + template > range_type> + auto resume(const range_type& handles) noexcept -> void + { + m_size.fetch_add(std::size(handles), std::memory_order::release); + + size_t null_handles{0}; + + { + std::scoped_lock lk{m_wait_mutex}; + for (const auto& handle : handles) { + if (handle != nullptr) [[likely]] { + m_queue.emplace_back(handle); + } + else { + ++null_handles; + } + } + } + + if (null_handles > 0) { + m_size.fetch_sub(null_handles, std::memory_order::release); + } + + m_wait_cv.notify_one(); + } + + /** + * Immediately yields the current task and places it at the end of the queue of tasks waiting + * to be processed. This will immediately be picked up again once it naturally goes through the + * FIFO task queue. This function is useful to yielding long processing tasks to let other tasks + * get processing time. + */ + [[nodiscard]] auto yield() -> operation { return schedule(); } + + /** + * Shutsdown the thread pool. This will finish any tasks scheduled prior to calling this + * function but will prevent the thread pool from scheduling any new tasks. This call is + * blocking and will wait until all inflight tasks are completed before returnin. + */ + void shutdown() noexcept + { + // Only allow shutdown to occur once. + if (m_shutdown_requested.exchange(true, std::memory_order::acq_rel) == false) + { + { + // There is a race condition if we are not holding the lock with the executors + // to always receive this. std::jthread stop token works without this properly. + std::unique_lock lk{m_wait_mutex}; + m_wait_cv.notify_all(); + } + + for (auto& thread : m_threads) + { + if (thread.joinable()) + { + thread.join(); + } + } + } + } + + /** + * @return The number of tasks waiting in the task queue + the executing tasks. + */ + auto size() const noexcept -> std::size_t { return m_size.load(std::memory_order::acquire); } + + /** + * @return True if the task queue is empty and zero tasks are currently executing. + */ + auto empty() const noexcept -> bool { return size() == 0; } + + /** + * @return The number of tasks waiting in the task queue to be executed. + */ + auto queue_size() const noexcept -> std::size_t + { + std::atomic_thread_fence(std::memory_order::acquire); + return m_queue.size(); + } + + /** + * @return True if the task queue is currently empty. + */ + auto queue_empty() const noexcept -> bool { return queue_size() == 0; } + + private: + /// The configuration options. + options m_opts; + /// The background executor threads. + std::vector m_threads; + /// Mutex for executor threads to sleep on the condition variable. + std::mutex m_wait_mutex; + /// Condition variable for each executor thread to wait on when no tasks are available. + std::condition_variable_any m_wait_cv; + /// FIFO queue of tasks waiting to be executed. + std::deque> m_queue; + /** + * Each background thread runs from this function. + * @param idx The executor's idx for internal data structure accesses. + */ + void executor(std::size_t idx) + { + if (m_opts.on_thread_start_functor != nullptr) + { + m_opts.on_thread_start_functor(idx); + } + + // Process until shutdown is requested and the total number of tasks reaches zero. + while (!m_shutdown_requested.load(std::memory_order::acquire) || m_size.load(std::memory_order::acquire) > 0) + { + std::unique_lock lk{m_wait_mutex}; + m_wait_cv.wait( + lk, + [&] { + return m_size.load(std::memory_order::acquire) > 0 || + m_shutdown_requested.load(std::memory_order::acquire); + }); + // Process this batch until the queue is empty. + while (!m_queue.empty()) + { + auto handle = m_queue.front(); + m_queue.pop_front(); + + // Release the lock while executing the coroutine. + lk.unlock(); + handle.resume(); + + m_size.fetch_sub(1, std::memory_order::release); + lk.lock(); + } + } + + if (m_opts.on_thread_stop_functor != nullptr) + { + m_opts.on_thread_stop_functor(idx); + } + } + /** + * @param handle Schedules the given coroutine to be executed upon the first available thread. + */ + void schedule_impl(std::coroutine_handle<> handle) noexcept + { + if (handle == nullptr) + { + return; + } + + { + std::scoped_lock lk{m_wait_mutex}; + m_queue.emplace_back(handle); + } + + m_wait_cv.notify_one(); + } + + /// The number of tasks in the queue + currently executing. + std::atomic m_size{0}; + /// Has the thread pool been requested to shut down? + std::atomic m_shutdown_requested{false}; + }; + +} // namespace coro diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 16a3124c18..d86601225a 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -4,43 +4,68 @@ #define UT_RUN_TIME_ONLY #include "glaze/coroutine.hpp" + #include "ut/ut.hpp" using namespace ut; suite generator = [] { std::atomic result{}; - auto task = [&](uint64_t count_to) -> glz::task - { - // Create a generator function that will yield and incrementing - // number each time its called. - auto gen = []() -> glz::generator - { - uint64_t i = 0; - while (true) - { - co_yield i; - ++i; - } - }; - - // Generate the next number until its greater than count to. - for (auto val : gen()) - { - std::cout << val << ", "; - result += val; - - if (val >= count_to) - { - break; - } - } - co_return; - }; + auto task = [&](uint64_t count_to) -> glz::task { + // Create a generator function that will yield and incrementing + // number each time its called. + auto gen = []() -> glz::generator { + uint64_t i = 0; + while (true) { + co_yield i; + ++i; + } + }; + + // Generate the next number until its greater than count to. + for (auto val : gen()) { + //std::cout << val << ", "; + result += val; + + if (val >= count_to) { + break; + } + } + co_return; + }; glz::sync_wait(task(100)); - + expect(result == 5050) << result; }; +suite thread_pool = [] { + // This lambda will create a glz::task that returns a unit64_t. + // It can be invoked many times with different arguments. + auto make_task_inline = [](uint64_t x) -> glz::task { co_return x + x; }; + + // This will block the calling thread until the created task completes. + // Since this task isn't scheduled on any glz::thread_pool or glz::io_scheduler + // it will execute directly on the calling thread. + auto result = glz::sync_wait(make_task_inline(5)); + expect(result == 10); + //std::cout << "Inline Result = " << result << "\n"; + + // We'll make a 1 thread glz::thread_pool to demonstrate offloading the task's + // execution to another thread. We'll capture the thread pool in the lambda, + // note that you will need to guarantee the thread pool outlives the coroutine. + glz::thread_pool tp{glz::thread_pool::options{.thread_count = 1}}; + + auto make_task_offload = [&tp](uint64_t x) -> glz::task { + co_await tp.schedule(); // Schedules execution on the thread pool. + co_return x + x; // This will execute on the thread pool. + }; + + // This will still block the calling thread, but it will now offload to the + // glz::thread_pool since the coroutine task is immediately scheduled. + result = glz::sync_wait(make_task_offload(10)); + expect(result == 20); + //std::cout << "Offload Result = " << result << "\n"; +}; + int main() { return 0; } From e7d60a710a4674267398525c2390378124926481 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 06:59:05 -0500 Subject: [PATCH 137/309] thread_pool high cpu usage fix --- include/glaze/coroutine/concepts.hpp | 2 +- include/glaze/coroutine/thread_pool.hpp | 194 +++++++++++++----------- 2 files changed, 107 insertions(+), 89 deletions(-) diff --git a/include/glaze/coroutine/concepts.hpp b/include/glaze/coroutine/concepts.hpp index 7cacac5d9c..41afaa878e 100644 --- a/include/glaze/coroutine/concepts.hpp +++ b/include/glaze/coroutine/concepts.hpp @@ -34,7 +34,7 @@ namespace glz } -> awaiter; { t.resume(c) - } -> std::same_as; + } -> std::same_as; }; template diff --git a/include/glaze/coroutine/thread_pool.hpp b/include/glaze/coroutine/thread_pool.hpp index a7fd25e70a..656a9745dc 100644 --- a/include/glaze/coroutine/thread_pool.hpp +++ b/include/glaze/coroutine/thread_pool.hpp @@ -68,12 +68,12 @@ namespace glz */ void await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept { - m_awaiting_coroutine = awaiting_coroutine; - m_thread_pool.schedule_impl(m_awaiting_coroutine); + m_awaiting_coroutine = awaiting_coroutine; + m_thread_pool.schedule_impl(m_awaiting_coroutine); - // void return on await_suspend suspends the _this_ coroutine, which is now scheduled on the - // thread pool and returns control to the caller. They could be sync_wait'ing or go do - // something else while this coroutine gets picked up by the thread pool. + // void return on await_suspend suspends the _this_ coroutine, which is now scheduled on the + // thread pool and returns control to the caller. They could be sync_wait'ing or go do + // something else while this coroutine gets picked up by the thread pool. } /** @@ -106,13 +106,13 @@ namespace glz */ explicit thread_pool(options opts = options{.thread_count = std::thread::hardware_concurrency(), .on_thread_start_functor = nullptr, - .on_thread_stop_functor = nullptr}) : m_opts(std::move(opts)) + .on_thread_stop_functor = nullptr}) + : m_opts(std::move(opts)) { m_threads.reserve(m_opts.thread_count); - for (uint32_t i = 0; i < m_opts.thread_count; ++i) - { - m_threads.emplace_back([this, i]() { executor(i); }); + for (uint32_t i = 0; i < m_opts.thread_count; ++i) { + m_threads.emplace_back([this, i]() { executor(i); }); } } @@ -121,9 +121,7 @@ namespace glz auto operator=(const thread_pool&) -> thread_pool& = delete; auto operator=(thread_pool&&) -> thread_pool& = delete; - virtual ~thread_pool() { - shutdown(); - } + virtual ~thread_pool() { shutdown(); } /** * @return The number of executor threads for processing tasks. @@ -139,11 +137,10 @@ namespace glz */ [[nodiscard]] auto schedule() -> operation { - if (!m_shutdown_requested.load(std::memory_order::acquire)) - { - m_size.fetch_add(1, std::memory_order::release); - return operation{*this}; - } + if (!m_shutdown_requested.load(std::memory_order::acquire)) { + m_size.fetch_add(1, std::memory_order::release); + return operation{*this}; + } GLZ_THROW_OR_ABORT(std::runtime_error("glz::thread_pool is shutting down, unable to schedule new tasks.")); } @@ -171,24 +168,30 @@ namespace glz /** * Schedules any coroutine handle that is ready to be resumed. * @param handle The coroutine handle to schedule. + * @return True if the coroutine is resumed, false if its a nullptr. */ - void resume(std::coroutine_handle<> handle) noexcept + bool resume(std::coroutine_handle<> handle) noexcept { - if (handle == nullptr) - { - return; - } + if (handle == nullptr) { + return false; + } - m_size.fetch_add(1, std::memory_order::release); - schedule_impl(handle); + if (m_shutdown_requested.load(std::memory_order::acquire)) { + return false; + } + + m_size.fetch_add(1, std::memory_order::release); + schedule_impl(handle); + return true; } /** * Schedules the set of coroutine handles that are ready to be resumed. * @param handles The coroutine handles to schedule. + * @param uint64_t The number of tasks resumed, if any where null they are discarded. */ template > range_type> - auto resume(const range_type& handles) noexcept -> void + uint64_t resume(const range_type& handles) noexcept { m_size.fetch_add(std::size(handles), std::memory_order::release); @@ -210,7 +213,17 @@ namespace glz m_size.fetch_sub(null_handles, std::memory_order::release); } - m_wait_cv.notify_one(); + uint64_t total = std::size(handles) - null_handles; + if (total >= m_threads.size()) { + m_wait_cv.notify_all(); + } + else { + for (uint64_t i = 0; i < total; ++i) { + m_wait_cv.notify_one(); + } + } + + return total; } /** @@ -228,24 +241,21 @@ namespace glz */ void shutdown() noexcept { - // Only allow shutdown to occur once. - if (m_shutdown_requested.exchange(true, std::memory_order::acq_rel) == false) - { - { - // There is a race condition if we are not holding the lock with the executors - // to always receive this. std::jthread stop token works without this properly. - std::unique_lock lk{m_wait_mutex}; - m_wait_cv.notify_all(); - } - - for (auto& thread : m_threads) - { - if (thread.joinable()) - { - thread.join(); - } - } - } + // Only allow shutdown to occur once. + if (m_shutdown_requested.exchange(true, std::memory_order::acq_rel) == false) { + { + // There is a race condition if we are not holding the lock with the executors + // to always receive this. std::jthread stop token works without this properly. + std::unique_lock lk{m_wait_mutex}; + m_wait_cv.notify_all(); + } + + for (auto& thread : m_threads) { + if (thread.joinable()) { + thread.join(); + } + } + } } /** @@ -289,57 +299,65 @@ namespace glz */ void executor(std::size_t idx) { - if (m_opts.on_thread_start_functor != nullptr) - { - m_opts.on_thread_start_functor(idx); - } - - // Process until shutdown is requested and the total number of tasks reaches zero. - while (!m_shutdown_requested.load(std::memory_order::acquire) || m_size.load(std::memory_order::acquire) > 0) - { - std::unique_lock lk{m_wait_mutex}; - m_wait_cv.wait( - lk, - [&] { - return m_size.load(std::memory_order::acquire) > 0 || - m_shutdown_requested.load(std::memory_order::acquire); - }); - // Process this batch until the queue is empty. - while (!m_queue.empty()) - { - auto handle = m_queue.front(); - m_queue.pop_front(); - - // Release the lock while executing the coroutine. - lk.unlock(); - handle.resume(); - - m_size.fetch_sub(1, std::memory_order::release); - lk.lock(); - } - } - - if (m_opts.on_thread_stop_functor != nullptr) - { - m_opts.on_thread_stop_functor(idx); - } + if (m_opts.on_thread_start_functor != nullptr) { + m_opts.on_thread_start_functor(idx); + } + + // Process until shutdown is requested. + while (!m_shutdown_requested.load(std::memory_order::acquire)) { + std::unique_lock lk{m_wait_mutex}; + m_wait_cv.wait(lk, + [&]() { return !m_queue.empty() || m_shutdown_requested.load(std::memory_order::acquire); }); + + if (m_queue.empty()) { + continue; + } + + auto handle = m_queue.front(); + m_queue.pop_front(); + lk.unlock(); + + // Release the lock while executing the coroutine. + handle.resume(); + m_size.fetch_sub(1, std::memory_order::release); + } + + // Process until there are no ready tasks left. + while (m_size.load(std::memory_order::acquire) > 0) { + std::unique_lock lk{m_wait_mutex}; + // m_size will only drop to zero once all executing coroutines are finished + // but the queue could be empty for threads that finished early. + if (m_queue.empty()) { + break; + } + + auto handle = m_queue.front(); + m_queue.pop_front(); + lk.unlock(); + + // Release the lock while executing the coroutine. + handle.resume(); + m_size.fetch_sub(1, std::memory_order::release); + } + + if (m_opts.on_thread_stop_functor != nullptr) { + m_opts.on_thread_stop_functor(idx); + } } /** * @param handle Schedules the given coroutine to be executed upon the first available thread. */ void schedule_impl(std::coroutine_handle<> handle) noexcept { - if (handle == nullptr) - { - return; - } - - { - std::scoped_lock lk{m_wait_mutex}; - m_queue.emplace_back(handle); - } + if (handle == nullptr) { + return; + } - m_wait_cv.notify_one(); + { + std::scoped_lock lk{m_wait_mutex}; + m_queue.emplace_back(handle); + m_wait_cv.notify_one(); + } } /// The number of tasks in the queue + currently executing. From 44fdcb2f564357ef30267ff9aa856f975f4a6a70 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 07:15:34 -0500 Subject: [PATCH 138/309] more coroutines --- include/glaze/coroutine.hpp | 1 + include/glaze/coroutine/event.hpp | 2 +- include/glaze/coroutine/thread_pool.hpp | 2 +- include/glaze/coroutine/when_all.hpp | 476 ++++++++++++++++++++++++ tests/coroutine_test/coroutine_test.cpp | 46 ++- 5 files changed, 522 insertions(+), 5 deletions(-) create mode 100644 include/glaze/coroutine/when_all.hpp diff --git a/include/glaze/coroutine.hpp b/include/glaze/coroutine.hpp index 5d04ca5906..2e988e9422 100644 --- a/include/glaze/coroutine.hpp +++ b/include/glaze/coroutine.hpp @@ -8,3 +8,4 @@ #include "glaze/coroutine/sync_wait.hpp" #include "glaze/coroutine/task.hpp" #include "glaze/coroutine/thread_pool.hpp" +#include "glaze/coroutine/when_all.hpp" diff --git a/include/glaze/coroutine/event.hpp b/include/glaze/coroutine/event.hpp index 3e014dc47d..9ff27470bb 100644 --- a/include/glaze/coroutine/event.hpp +++ b/include/glaze/coroutine/event.hpp @@ -152,4 +152,4 @@ namespace glz auto reverse(awaiter* head) -> awaiter*; }; -} // namespace coro +} diff --git a/include/glaze/coroutine/thread_pool.hpp b/include/glaze/coroutine/thread_pool.hpp index 656a9745dc..789cc9c14b 100644 --- a/include/glaze/coroutine/thread_pool.hpp +++ b/include/glaze/coroutine/thread_pool.hpp @@ -366,4 +366,4 @@ namespace glz std::atomic m_shutdown_requested{false}; }; -} // namespace coro +} diff --git a/include/glaze/coroutine/when_all.hpp b/include/glaze/coroutine/when_all.hpp new file mode 100644 index 0000000000..b657a2cd6e --- /dev/null +++ b/include/glaze/coroutine/when_all.hpp @@ -0,0 +1,476 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "glaze/coroutine/awaitable.hpp" + +namespace glz +{ + namespace detail + { + class when_all_latch + { + public: + when_all_latch(std::size_t count) noexcept : m_count(count + 1) {} + + when_all_latch(const when_all_latch&) = delete; + when_all_latch(when_all_latch&& other) + : m_count(other.m_count.load(std::memory_order::acquire)), + m_awaiting_coroutine(std::exchange(other.m_awaiting_coroutine, nullptr)) + {} + + auto operator=(const when_all_latch&) -> when_all_latch& = delete; + auto operator=(when_all_latch&& other) -> when_all_latch& + { + if (std::addressof(other) != this) { + m_count.store(other.m_count.load(std::memory_order::acquire), std::memory_order::relaxed); + m_awaiting_coroutine = std::exchange(other.m_awaiting_coroutine, nullptr); + } + + return *this; + } + + auto is_ready() const noexcept -> bool + { + return m_awaiting_coroutine != nullptr && m_awaiting_coroutine.done(); + } + + auto try_await(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool + { + m_awaiting_coroutine = awaiting_coroutine; + return m_count.fetch_sub(1, std::memory_order::acq_rel) > 1; + } + + auto notify_awaitable_completed() noexcept -> void + { + if (m_count.fetch_sub(1, std::memory_order::acq_rel) == 1) { + m_awaiting_coroutine.resume(); + } + } + + private: + /// The number of tasks that are being waited on. + std::atomic m_count; + /// The when_all_task awaiting to be resumed upon all task completions. + std::coroutine_handle<> m_awaiting_coroutine{nullptr}; + }; + + template + class when_all_ready_awaitable; + + template + class when_all_task; + + /// Empty tuple<> implementation. + template <> + class when_all_ready_awaitable> + { + public: + constexpr when_all_ready_awaitable() noexcept {} + explicit constexpr when_all_ready_awaitable(std::tuple<>) noexcept {} + + constexpr auto await_ready() const noexcept -> bool { return true; } + auto await_suspend(std::coroutine_handle<>) noexcept -> void {} + auto await_resume() const noexcept -> std::tuple<> { return {}; } + }; + + template + class when_all_ready_awaitable> + { + public: + explicit when_all_ready_awaitable(task_types&&... tasks) noexcept( + std::conjunction...>::value) + : m_latch(sizeof...(task_types)), m_tasks(std::move(tasks)...) + {} + + explicit when_all_ready_awaitable(std::tuple&& tasks) noexcept( + std::is_nothrow_move_constructible_v>) + : m_latch(sizeof...(task_types)), m_tasks(std::move(tasks)) + {} + + when_all_ready_awaitable(const when_all_ready_awaitable&) = delete; + when_all_ready_awaitable(when_all_ready_awaitable&& other) + : m_latch(std::move(other.m_latch)), m_tasks(std::move(other.m_tasks)) + {} + + auto operator=(const when_all_ready_awaitable&) -> when_all_ready_awaitable& = delete; + auto operator=(when_all_ready_awaitable&&) -> when_all_ready_awaitable& = delete; + + auto operator co_await() & noexcept + { + struct awaiter + { + explicit awaiter(when_all_ready_awaitable& awaitable) noexcept : m_awaitable(awaitable) {} + + auto await_ready() const noexcept -> bool { return m_awaitable.is_ready(); } + + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool + { + return m_awaitable.try_await(awaiting_coroutine); + } + + auto await_resume() noexcept -> std::tuple& { return m_awaitable.m_tasks; } + + private: + when_all_ready_awaitable& m_awaitable; + }; + + return awaiter{*this}; + } + + auto operator co_await() && noexcept + { + struct awaiter + { + explicit awaiter(when_all_ready_awaitable& awaitable) noexcept : m_awaitable(awaitable) {} + + auto await_ready() const noexcept -> bool { return m_awaitable.is_ready(); } + + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool + { + return m_awaitable.try_await(awaiting_coroutine); + } + + auto await_resume() noexcept -> std::tuple&& { return std::move(m_awaitable.m_tasks); } + + private: + when_all_ready_awaitable& m_awaitable; + }; + + return awaiter{*this}; + } + + private: + auto is_ready() const noexcept -> bool { return m_latch.is_ready(); } + + auto try_await(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool + { + std::apply([this](auto&&... tasks) { ((tasks.start(m_latch)), ...); }, m_tasks); + return m_latch.try_await(awaiting_coroutine); + } + + when_all_latch m_latch; + std::tuple m_tasks; + }; + + template + class when_all_ready_awaitable + { + public: + explicit when_all_ready_awaitable(task_container_type&& tasks) noexcept + : m_latch(std::size(tasks)), m_tasks(std::forward(tasks)) + {} + + when_all_ready_awaitable(const when_all_ready_awaitable&) = delete; + when_all_ready_awaitable(when_all_ready_awaitable&& other) noexcept( + std::is_nothrow_move_constructible_v) + : m_latch(std::move(other.m_latch)), m_tasks(std::move(m_tasks)) + {} + + auto operator=(const when_all_ready_awaitable&) -> when_all_ready_awaitable& = delete; + auto operator=(when_all_ready_awaitable&) -> when_all_ready_awaitable& = delete; + + auto operator co_await() & noexcept + { + struct awaiter + { + awaiter(when_all_ready_awaitable& awaitable) : m_awaitable(awaitable) {} + + auto await_ready() const noexcept -> bool { return m_awaitable.is_ready(); } + + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool + { + return m_awaitable.try_await(awaiting_coroutine); + } + + auto await_resume() noexcept -> task_container_type& { return m_awaitable.m_tasks; } + + private: + when_all_ready_awaitable& m_awaitable; + }; + + return awaiter{*this}; + } + + auto operator co_await() && noexcept + { + struct awaiter + { + awaiter(when_all_ready_awaitable& awaitable) : m_awaitable(awaitable) {} + + auto await_ready() const noexcept -> bool { return m_awaitable.is_ready(); } + + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool + { + return m_awaitable.try_await(awaiting_coroutine); + } + + auto await_resume() noexcept -> task_container_type&& { return std::move(m_awaitable.m_tasks); } + + private: + when_all_ready_awaitable& m_awaitable; + }; + + return awaiter{*this}; + } + + private: + auto is_ready() const noexcept -> bool { return m_latch.is_ready(); } + + auto try_await(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool + { + for (auto& task : m_tasks) { + task.start(m_latch); + } + + return m_latch.try_await(awaiting_coroutine); + } + + when_all_latch m_latch; + task_container_type m_tasks; + }; + + template + class when_all_task_promise + { + public: + using coroutine_handle_type = std::coroutine_handle>; + + when_all_task_promise() noexcept {} + + auto get_return_object() noexcept { return coroutine_handle_type::from_promise(*this); } + + auto initial_suspend() noexcept -> std::suspend_always { return {}; } + + auto final_suspend() noexcept + { + struct completion_notifier + { + auto await_ready() const noexcept -> bool { return false; } + auto await_suspend(coroutine_handle_type coroutine) const noexcept -> void + { + coroutine.promise().m_latch->notify_awaitable_completed(); + } + auto await_resume() const noexcept {} + }; + + return completion_notifier{}; + } + + auto unhandled_exception() noexcept { m_exception_ptr = std::current_exception(); } + + auto yield_value(return_type&& value) noexcept + { + m_return_value = std::addressof(value); + return final_suspend(); + } + + auto start(when_all_latch& latch) noexcept -> void + { + m_latch = &latch; + coroutine_handle_type::from_promise(*this).resume(); + } + + auto result() & -> return_type& + { + if (m_exception_ptr) { + std::rethrow_exception(m_exception_ptr); + } + return *m_return_value; + } + + auto result() && -> return_type&& + { + if (m_exception_ptr) { + std::rethrow_exception(m_exception_ptr); + } + return std::forward(*m_return_value); + } + + auto return_void() noexcept -> void + { + // We should have either suspended at co_yield point or + // an exception was thrown before running off the end of + // the coroutine. + assert(false); + } + + private: + when_all_latch* m_latch{nullptr}; + std::exception_ptr m_exception_ptr; + std::add_pointer_t m_return_value; + }; + + template <> + class when_all_task_promise + { + public: + using coroutine_handle_type = std::coroutine_handle>; + + when_all_task_promise() noexcept {} + + auto get_return_object() noexcept { return coroutine_handle_type::from_promise(*this); } + + auto initial_suspend() noexcept -> std::suspend_always { return {}; } + + auto final_suspend() noexcept + { + struct completion_notifier + { + auto await_ready() const noexcept -> bool { return false; } + auto await_suspend(coroutine_handle_type coroutine) const noexcept -> void + { + coroutine.promise().m_latch->notify_awaitable_completed(); + } + auto await_resume() const noexcept -> void {} + }; + + return completion_notifier{}; + } + + auto unhandled_exception() noexcept -> void { m_exception_ptr = std::current_exception(); } + + auto return_void() noexcept -> void {} + + auto result() -> void + { + if (m_exception_ptr) { + std::rethrow_exception(m_exception_ptr); + } + } + + auto start(when_all_latch& latch) -> void + { + m_latch = &latch; + coroutine_handle_type::from_promise(*this).resume(); + } + + private: + when_all_latch* m_latch{nullptr}; + std::exception_ptr m_exception_ptr; + }; + + template + class when_all_task + { + public: + // To be able to call start(). + template + friend class when_all_ready_awaitable; + + using promise_type = when_all_task_promise; + using coroutine_handle_type = typename promise_type::coroutine_handle_type; + + when_all_task(coroutine_handle_type coroutine) noexcept : m_coroutine(coroutine) {} + + when_all_task(const when_all_task&) = delete; + when_all_task(when_all_task&& other) noexcept + : m_coroutine(std::exchange(other.m_coroutine, coroutine_handle_type{})) + {} + + auto operator=(const when_all_task&) -> when_all_task& = delete; + auto operator=(when_all_task&&) -> when_all_task& = delete; + + ~when_all_task() + { + if (m_coroutine != nullptr) { + m_coroutine.destroy(); + } + } + + auto return_value() & -> decltype(auto) + { + if constexpr (std::is_void_v) { + m_coroutine.promise().result(); + return std::nullptr_t{}; + } + else { + return m_coroutine.promise().result(); + } + } + + auto return_value() const& -> decltype(auto) + { + if constexpr (std::is_void_v) { + m_coroutine.promise().result(); + return std::nullptr_t{}; + } + else { + return m_coroutine.promise().result(); + } + } + + auto return_value() && -> decltype(auto) + { + if constexpr (std::is_void_v) { + m_coroutine.promise().result(); + return std::nullptr_t{}; + } + else { + return m_coroutine.promise().result(); + } + } + + private: + auto start(when_all_latch& latch) noexcept -> void { m_coroutine.promise().start(latch); } + + coroutine_handle_type m_coroutine; + }; + + template ::awaiter_return_type> + static auto make_when_all_task(Awaitable a) -> when_all_task; + + template + static auto make_when_all_task(awaitable a) -> when_all_task + { + if constexpr (std::is_void_v) { + co_await static_cast(a); + co_return; + } + else { + co_yield co_await static_cast(a); + } + } + + } // namespace detail + + template + [[nodiscard]] auto when_all(awaitables_type... awaitables) + { + return detail::when_all_ready_awaitable::awaiter_return_type>...>>( + std::make_tuple(detail::make_when_all_task(std::move(awaitables))...)); + } + + template , + class return_type = typename awaitable_traits::awaiter_return_type> + [[nodiscard]] auto when_all(range_type awaitables) + -> detail::when_all_ready_awaitable>> + { + std::vector> output_tasks; + + // If the size is known in constant time reserve the output tasks size. + if constexpr (std::ranges::sized_range) { + output_tasks.reserve(std::size(awaitables)); + } + + // Wrap each task into a when_all_task. + for (auto&& a : awaitables) { + output_tasks.emplace_back(detail::make_when_all_task(std::move(a))); + } + + // Return the single awaitable that drives all the user's tasks. + return detail::when_all_ready_awaitable(std::move(output_tasks)); + } +} diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index d86601225a..06594f38b4 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -24,7 +24,7 @@ suite generator = [] { // Generate the next number until its greater than count to. for (auto val : gen()) { - //std::cout << val << ", "; + // std::cout << val << ", "; result += val; if (val >= count_to) { @@ -49,7 +49,7 @@ suite thread_pool = [] { // it will execute directly on the calling thread. auto result = glz::sync_wait(make_task_inline(5)); expect(result == 10); - //std::cout << "Inline Result = " << result << "\n"; + // std::cout << "Inline Result = " << result << "\n"; // We'll make a 1 thread glz::thread_pool to demonstrate offloading the task's // execution to another thread. We'll capture the thread pool in the lambda, @@ -65,7 +65,47 @@ suite thread_pool = [] { // glz::thread_pool since the coroutine task is immediately scheduled. result = glz::sync_wait(make_task_offload(10)); expect(result == 20); - //std::cout << "Offload Result = " << result << "\n"; + // std::cout << "Offload Result = " << result << "\n"; +}; + +suite when_all = [] { + // Create a thread pool to execute all the tasks in parallel. + glz::thread_pool tp{glz::thread_pool::options{.thread_count = 4}}; + // Create the task we want to invoke multiple times and execute in parallel on the thread pool. + auto twice = [&](uint64_t x) -> glz::task { + co_await tp.schedule(); // Schedule onto the thread pool. + co_return x + x; // Executed on the thread pool. + }; + + // Make our tasks to execute, tasks can be passed in via a std::ranges::range type or var args. + std::vector> tasks{}; + for (std::size_t i = 0; i < 5; ++i) { + tasks.emplace_back(twice(i + 1)); + } + + // Synchronously wait on this thread for the thread pool to finish executing all the tasks in parallel. + auto results = glz::sync_wait(glz::when_all(std::move(tasks))); + expect(results[0].return_value() == 2); + expect(results[1].return_value() == 4); + expect(results[2].return_value() == 6); + expect(results[3].return_value() == 8); + expect(results[4].return_value() == 10); + + // Use var args instead of a container as input to glz::when_all. + auto square = [&](uint8_t x) -> glz::task { + co_await tp.schedule(); + co_return x * x; + }; + + // Var args allows you to pass in tasks with different return types and returns + // the result as a std::tuple. + auto tuple_results = glz::sync_wait(glz::when_all(square(2), twice(10))); + + auto first = std::get<0>(tuple_results).return_value(); + auto second = std::get<1>(tuple_results).return_value(); + + expect(first == 4); + expect(second == 20); }; int main() { return 0; } From 74c160bd30a286098a112e0545eb1490ec37ff12 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 07:21:58 -0500 Subject: [PATCH 139/309] event --- include/glaze/coroutine/event.hpp | 82 ++++++++++++++++++++++--- tests/coroutine_test/coroutine_test.cpp | 25 +++++++- 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/include/glaze/coroutine/event.hpp b/include/glaze/coroutine/event.hpp index 9ff27470bb..85a3048651 100644 --- a/include/glaze/coroutine/event.hpp +++ b/include/glaze/coroutine/event.hpp @@ -38,9 +38,8 @@ namespace glz t2: resume() * \endcode */ - class event + struct event { - public: struct awaiter { /** @@ -59,7 +58,28 @@ namespace glz * to resume execution immediately. * @return False if the event is already set, otherwise true to suspend this coroutine. */ - auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool; + bool await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept + { + const void* const set_state = &m_event; + + m_awaiting_coroutine = awaiting_coroutine; + + // This value will update if other threads write to it via acquire. + void* old_value = m_event.m_state.load(std::memory_order::acquire); + do + { + // Resume immediately if already in the set state. + if (old_value == set_state) + { + return false; + } + + m_next = static_cast(old_value); + } while (!m_event.m_state.compare_exchange_weak( + old_value, this, std::memory_order::release, std::memory_order::acquire)); + + return true; + } /** * Nothing to do on resume. @@ -79,7 +99,10 @@ namespace glz * @param initially_set By default all events start as not set, but if needed this parameter can * set the event to already be triggered. */ - explicit event(bool initially_set = false) noexcept; + explicit event(bool initially_set = false) noexcept + : m_state((initially_set) ? static_cast(this) : nullptr) + {} + ~event() = default; event(const event&) = delete; @@ -98,7 +121,29 @@ namespace glz * @param policy The order in which the waiters should be resumed, defaults to LIFO since it * is more efficient, FIFO requires reversing the order of the waiters first. */ - auto set(resume_order_policy policy = resume_order_policy::lifo) noexcept -> void; + void set(resume_order_policy policy = resume_order_policy::lifo) noexcept + { + // Exchange the state to this, if the state was previously not this, then traverse the list + // of awaiters and resume their coroutines. + void* old_value = m_state.exchange(this, std::memory_order::acq_rel); + if (old_value != this) + { + // If FIFO has been requsted then reverse the order upon resuming. + if (policy == resume_order_policy::fifo) + { + old_value = reverse(static_cast(old_value)); + } + // else lifo nothing to do + + auto* waiters = static_cast(old_value); + while (waiters != nullptr) + { + auto* next = waiters->m_next; + waiters->m_awaiting_coroutine.resume(); + waiters = next; + } + } + } /** * Sets this event and resumes all awaiters onto the given executor. This will distribute @@ -133,7 +178,11 @@ namespace glz * Resets the event from set to not set so it can be re-used. If the event is not currently * set then this function has no effect. */ - auto reset() noexcept -> void; + void reset() noexcept + { + void* old_value = this; + m_state.compare_exchange_strong(old_value, nullptr, std::memory_order::acquire); + } protected: /// For access to m_state. @@ -149,7 +198,24 @@ namespace glz /** * Reverses the set of waiters from LIFO->FIFO and returns the new head. */ - auto reverse(awaiter* head) -> awaiter*; + auto reverse(awaiter* curr) -> awaiter* + { + if (curr == nullptr || curr->m_next == nullptr) + { + return curr; + } + + awaiter* prev = nullptr; + awaiter* next = nullptr; + while (curr != nullptr) + { + next = curr->m_next; + curr->m_next = prev; + prev = curr; + curr = next; + } + + return prev; + } }; - } diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 06594f38b4..fc52ea81e1 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -94,7 +94,7 @@ suite when_all = [] { // Use var args instead of a container as input to glz::when_all. auto square = [&](uint8_t x) -> glz::task { co_await tp.schedule(); - co_return x * x; + co_return x* x; }; // Var args allows you to pass in tasks with different return types and returns @@ -108,4 +108,27 @@ suite when_all = [] { expect(second == 20); }; +suite event = [] { + glz::event e; + + // These tasks will wait until the given event has been set before advancing. + auto make_wait_task = [](const glz::event& e, uint64_t i) -> glz::task { + std::cout << "task " << i << " is waiting on the event...\n"; + co_await e; + std::cout << "task " << i << " event triggered, now resuming.\n"; + co_return; + }; + + // This task will trigger the event allowing all waiting tasks to proceed. + auto make_set_task = [](glz::event& e) -> glz::task { + std::cout << "set task is triggering the event\n"; + e.set(); + co_return; + }; + + // Given more than a single task to synchronously wait on, use when_all() to execute all the + // tasks concurrently on this thread and then sync_wait() for them all to complete. + glz::sync_wait(glz::when_all(make_wait_task(e, 1), make_wait_task(e, 2), make_wait_task(e, 3), make_set_task(e))); +}; + int main() { return 0; } From cc7e2ce4e779bbcfec85289026ee9216d8bc4d79 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 07:24:18 -0500 Subject: [PATCH 140/309] structs --- include/glaze/coroutine/thread_pool.hpp | 5 ++--- include/glaze/coroutine/when_all.hpp | 27 +++++++++---------------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/include/glaze/coroutine/thread_pool.hpp b/include/glaze/coroutine/thread_pool.hpp index 789cc9c14b..c980b2691d 100644 --- a/include/glaze/coroutine/thread_pool.hpp +++ b/include/glaze/coroutine/thread_pool.hpp @@ -40,16 +40,15 @@ namespace glz * the thread pool will stop accepting new tasks but will complete all tasks that were scheduled * prior to the shutdown request. */ - class thread_pool + struct thread_pool { - public: /** * An operation is an awaitable type with a coroutine to resume the task scheduled on one of * the executor threads. */ class operation { - friend class thread_pool; + friend struct thread_pool; /** * Only thread_pools can create operations when a task is being scheduled. * @param tp The thread pool that created this operation. diff --git a/include/glaze/coroutine/when_all.hpp b/include/glaze/coroutine/when_all.hpp index b657a2cd6e..d91e89eadc 100644 --- a/include/glaze/coroutine/when_all.hpp +++ b/include/glaze/coroutine/when_all.hpp @@ -18,9 +18,8 @@ namespace glz { namespace detail { - class when_all_latch + struct when_all_latch { - public: when_all_latch(std::size_t count) noexcept : m_count(count + 1) {} when_all_latch(const when_all_latch&) = delete; @@ -66,16 +65,15 @@ namespace glz }; template - class when_all_ready_awaitable; + struct when_all_ready_awaitable; template - class when_all_task; + struct when_all_task; /// Empty tuple<> implementation. template <> - class when_all_ready_awaitable> + struct when_all_ready_awaitable> { - public: constexpr when_all_ready_awaitable() noexcept {} explicit constexpr when_all_ready_awaitable(std::tuple<>) noexcept {} @@ -85,9 +83,8 @@ namespace glz }; template - class when_all_ready_awaitable> + struct when_all_ready_awaitable> { - public: explicit when_all_ready_awaitable(task_types&&... tasks) noexcept( std::conjunction...>::value) : m_latch(sizeof...(task_types)), m_tasks(std::move(tasks)...) @@ -164,9 +161,8 @@ namespace glz }; template - class when_all_ready_awaitable + struct when_all_ready_awaitable { - public: explicit when_all_ready_awaitable(task_container_type&& tasks) noexcept : m_latch(std::size(tasks)), m_tasks(std::forward(tasks)) {} @@ -241,9 +237,8 @@ namespace glz }; template - class when_all_task_promise + struct when_all_task_promise { - public: using coroutine_handle_type = std::coroutine_handle>; when_all_task_promise() noexcept {} @@ -312,9 +307,8 @@ namespace glz }; template <> - class when_all_task_promise + struct when_all_task_promise { - public: using coroutine_handle_type = std::coroutine_handle>; when_all_task_promise() noexcept {} @@ -361,12 +355,11 @@ namespace glz }; template - class when_all_task + struct when_all_task { - public: // To be able to call start(). template - friend class when_all_ready_awaitable; + friend struct when_all_ready_awaitable; using promise_type = when_all_task_promise; using coroutine_handle_type = typename promise_type::coroutine_handle_type; From 0d22302d91980d999fa1846fa6b09c90f65d2b3e Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 08:06:13 -0500 Subject: [PATCH 141/309] Adding latch.hpp --- include/glaze/coroutine.hpp | 2 + include/glaze/coroutine/file_descriptor.hpp | 9 + include/glaze/coroutine/io_scheduler.hpp | 363 ++++++++++++++++++++ include/glaze/coroutine/latch.hpp | 81 +++++ include/glaze/coroutine/poll.hpp | 62 ++++ include/glaze/coroutine/poll_info.hpp | 78 +++++ include/glaze/coroutine/task_container.hpp | 270 +++++++++++++++ include/glaze/coroutine/time.hpp | 10 + tests/coroutine_test/coroutine_test.cpp | 50 +++ 9 files changed, 925 insertions(+) create mode 100644 include/glaze/coroutine/file_descriptor.hpp create mode 100644 include/glaze/coroutine/io_scheduler.hpp create mode 100644 include/glaze/coroutine/latch.hpp create mode 100644 include/glaze/coroutine/poll.hpp create mode 100644 include/glaze/coroutine/poll_info.hpp create mode 100644 include/glaze/coroutine/task_container.hpp create mode 100644 include/glaze/coroutine/time.hpp diff --git a/include/glaze/coroutine.hpp b/include/glaze/coroutine.hpp index 2e988e9422..5188a3e03e 100644 --- a/include/glaze/coroutine.hpp +++ b/include/glaze/coroutine.hpp @@ -5,6 +5,8 @@ #include "glaze/coroutine/awaitable.hpp" #include "glaze/coroutine/generator.hpp" +#include "glaze/coroutine/io_scheduler.hpp" +#include "glaze/coroutine/latch.hpp" #include "glaze/coroutine/sync_wait.hpp" #include "glaze/coroutine/task.hpp" #include "glaze/coroutine/thread_pool.hpp" diff --git a/include/glaze/coroutine/file_descriptor.hpp b/include/glaze/coroutine/file_descriptor.hpp new file mode 100644 index 0000000000..ac18cbea4e --- /dev/null +++ b/include/glaze/coroutine/file_descriptor.hpp @@ -0,0 +1,9 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +namespace glz +{ + using fd_t = int; +} diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp new file mode 100644 index 0000000000..ff482d821f --- /dev/null +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -0,0 +1,363 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include "glaze/coroutine/poll_info.hpp" +#include "glaze/coroutine/file_descriptor.hpp" +#include "glaze/coroutine/poll.hpp" +#include "glaze/coroutine/task_container.hpp" +#include "glaze/coroutine/thread_pool.hpp" + +#ifdef GLZ_FEATURE_NETWORKING +#include "coro/net/socket.hpp" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace glz +{ + class io_scheduler + { + using timed_events = detail::poll_info::timed_events; + + public: + class schedule_operation; + friend schedule_operation; + + enum class thread_strategy_t { + /// Spawns a dedicated background thread for the scheduler to run on. + spawn, + /// Requires the user to call process_events() to drive the scheduler. + manual + }; + + enum class execution_strategy_t { + /// Tasks will be FIFO queued to be executed on a thread pool. This is better for tasks that + /// are long lived and will use lots of CPU because long lived tasks will block other i/o + /// operations while they complete. This strategy is generally better for lower latency + /// requirements at the cost of throughput. + process_tasks_on_thread_pool, + /// Tasks will be executed inline on the io scheduler thread. This is better for short tasks + /// that can be quickly processed and not block other i/o operations for very long. This + /// strategy is generally better for higher throughput at the cost of latency. + process_tasks_inline + }; + + struct options + { + /// Should the io scheduler spawn a dedicated event processor? + thread_strategy_t thread_strategy{thread_strategy_t::spawn}; + /// If spawning a dedicated event processor a functor to call upon that thread starting. + std::function on_io_thread_start_functor{nullptr}; + /// If spawning a dedicated event processor a functor to call upon that thread stopping. + std::function on_io_thread_stop_functor{nullptr}; + /// Thread pool options for the task processor threads. See thread pool for more details. + thread_pool::options pool{ + .thread_count = ((std::thread::hardware_concurrency() > 1) ? (std::thread::hardware_concurrency() - 1) : 1), + .on_thread_start_functor = nullptr, + .on_thread_stop_functor = nullptr}; + + /// If inline task processing is enabled then the io worker will resume tasks on its thread + /// rather than scheduling them to be picked up by the thread pool. + const execution_strategy_t execution_strategy{execution_strategy_t::process_tasks_on_thread_pool}; + }; + + explicit io_scheduler(options opts = options{ + .thread_strategy = thread_strategy_t::spawn, + .on_io_thread_start_functor = nullptr, + .on_io_thread_stop_functor = nullptr, + .pool = {.thread_count = ((std::thread::hardware_concurrency() > 1) + ? (std::thread::hardware_concurrency() - 1) + : 1), + .on_thread_start_functor = nullptr, + .on_thread_stop_functor = nullptr}, + .execution_strategy = execution_strategy_t::process_tasks_on_thread_pool}); + + io_scheduler(const io_scheduler&) = delete; + io_scheduler(io_scheduler&&) = delete; + auto operator=(const io_scheduler&) -> io_scheduler& = delete; + auto operator=(io_scheduler&&) -> io_scheduler& = delete; + + ~io_scheduler(); + + /** + * Given a thread_strategy_t::manual this function should be called at regular intervals to + * process events that are ready. If a using thread_strategy_t::spawn this is run continously + * on a dedicated background thread and does not need to be manually invoked. + * @param timeout If no events are ready how long should the function wait for events to be ready? + * Passing zero (default) for the timeout will check for any events that are + * ready now, and then return. This could be zero events. Passing -1 means block + * indefinitely until an event happens. + * @param return The number of tasks currently executing or waiting to execute. + */ + auto process_events(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) -> std::size_t; + + class schedule_operation + { + friend class io_scheduler; + explicit schedule_operation(io_scheduler& scheduler) noexcept : m_scheduler(scheduler) {} + + public: + /** + * Operations always pause so the executing thread can be switched. + */ + auto await_ready() noexcept -> bool { return false; } + + /** + * Suspending always returns to the caller (using void return of await_suspend()) and + * stores the coroutine internally for the executing thread to resume from. + */ + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> void + { + if (m_scheduler.m_opts.execution_strategy == execution_strategy_t::process_tasks_inline) { + m_scheduler.m_size.fetch_add(1, std::memory_order::release); + { + std::scoped_lock lk{m_scheduler.m_scheduled_tasks_mutex}; + m_scheduler.m_scheduled_tasks.emplace_back(awaiting_coroutine); + } + + // Trigger the event to wake-up the scheduler if this event isn't currently triggered. + bool expected{false}; + if (m_scheduler.m_schedule_fd_triggered.compare_exchange_strong( + expected, true, std::memory_order::release, std::memory_order::relaxed)) { + eventfd_t value{1}; + eventfd_write(m_scheduler.m_schedule_fd, value); + } + } + else { + m_scheduler.m_thread_pool->resume(awaiting_coroutine); + } + } + + /** + * no-op as this is the function called first by the thread pool's executing thread. + */ + auto await_resume() noexcept -> void {} + + private: + /// The thread pool that this operation will execute on. + io_scheduler& m_scheduler; + }; + + /** + * Schedules the current task onto this io_scheduler for execution. + */ + auto schedule() -> schedule_operation { return schedule_operation{*this}; } + + /** + * Schedules a task onto the io_scheduler and moves ownership of the task to the io_scheduler. + * Only void return type tasks can be scheduled in this manner since the task submitter will no + * longer have control over the scheduled task. + * @param task The task to execute on this io_scheduler. It's lifetime ownership will be transferred + * to this io_scheduler. + */ + auto schedule(coro::task&& task) -> void; + + /** + * Schedules the current task to run after the given amount of time has elapsed. + * @param amount The amount of time to wait before resuming execution of this task. + * Given zero or negative amount of time this behaves identical to schedule(). + */ + [[nodiscard]] auto schedule_after(std::chrono::milliseconds amount) -> coro::task; + + /** + * Schedules the current task to run at a given time point in the future. + * @param time The time point to resume execution of this task. Given 'now' or a time point + * in the past this behaves identical to schedule(). + */ + [[nodiscard]] auto schedule_at(time_point time) -> coro::task; + + /** + * Yields the current task to the end of the queue of waiting tasks. + */ + [[nodiscard]] auto yield() -> schedule_operation { return schedule_operation{*this}; }; + + /** + * Yields the current task for the given amount of time. + * @param amount The amount of time to yield for before resuming executino of this task. + * Given zero or negative amount of time this behaves identical to yield(). + */ + [[nodiscard]] auto yield_for(std::chrono::milliseconds amount) -> coro::task; + + /** + * Yields the current task until the given time point in the future. + * @param time The time point to resume execution of this task. Given 'now' or a time point in the + * in the past this behaves identical to yield(). + */ + [[nodiscard]] auto yield_until(time_point time) -> coro::task; + + /** + * Polls the given file descriptor for the given operations. + * @param fd The file descriptor to poll for events. + * @param op The operations to poll for. + * @param timeout The amount of time to wait for the events to trigger. A timeout of zero will + * block indefinitely until the event triggers. + * @return The result of the poll operation. + */ + [[nodiscard]] auto poll(fd_t fd, coro::poll_op op, + std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) + -> coro::task; + +#ifdef GLZ_FEATURE_NETWORKING + /** + * Polls the given coro::net::socket for the given operations. + * @param sock The socket to poll for events on. + * @param op The operations to poll for. + * @param timeout The amount of time to wait for the events to trigger. A timeout of zero will + * block indefinitely until the event triggers. + * @return THe result of the poll operation. + */ + [[nodiscard]] auto poll(const net::socket& sock, coro::poll_op op, + std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) + -> coro::task + { + return poll(sock.native_handle(), op, timeout); + } +#endif + + /** + * Resumes execution of a direct coroutine handle on this io scheduler. + * @param handle The coroutine handle to resume execution. + */ + auto resume(std::coroutine_handle<> handle) -> bool + { + if (handle == nullptr) { + return false; + } + + if (m_shutdown_requested.load(std::memory_order::acquire)) { + return false; + } + + if (m_opts.execution_strategy == execution_strategy_t::process_tasks_inline) { + { + std::scoped_lock lk{m_scheduled_tasks_mutex}; + m_scheduled_tasks.emplace_back(handle); + } + + bool expected{false}; + if (m_schedule_fd_triggered.compare_exchange_strong(expected, true, std::memory_order::release, + std::memory_order::relaxed)) { + eventfd_t value{1}; + eventfd_write(m_schedule_fd, value); + } + + return true; + } + else { + return m_thread_pool->resume(handle); + } + } + + /** + * @return The number of tasks waiting in the task queue + the executing tasks. + */ + auto size() const noexcept -> std::size_t + { + if (m_opts.execution_strategy == execution_strategy_t::process_tasks_inline) { + return m_size.load(std::memory_order::acquire); + } + else { + return m_size.load(std::memory_order::acquire) + m_thread_pool->size(); + } + } + + /** + * @return True if the task queue is empty and zero tasks are currently executing. + */ + auto empty() const noexcept -> bool { return size() == 0; } + + /** + * Starts the shutdown of the io scheduler. All currently executing and pending tasks will complete + * prior to shutting down. This call is blocking and will not return until all tasks complete. + */ + auto shutdown() noexcept -> void; + + /** + * Scans for completed coroutines and destroys them freeing up resources. This is also done on starting + * new tasks but this allows the user to cleanup resources manually. One usage might be making sure fds + * are cleaned up as soon as possible. + */ + auto garbage_collect() noexcept -> void; + + private: + /// The configuration options. + options m_opts; + + /// The event loop epoll file descriptor. + fd_t m_epoll_fd{-1}; + /// The event loop fd to trigger a shutdown. + fd_t m_shutdown_fd{-1}; + /// The event loop timer fd for timed events, e.g. yield_for() or scheduler_after(). + fd_t m_timer_fd{-1}; + /// The schedule file descriptor if the scheduler is in inline processing mode. + fd_t m_schedule_fd{-1}; + std::atomic m_schedule_fd_triggered{false}; + + /// The number of tasks executing or awaiting events in this io scheduler. + std::atomic m_size{0}; + + /// The background io worker threads. + std::thread m_io_thread; + /// Thread pool for executing tasks when not in inline mode. + std::unique_ptr m_thread_pool{nullptr}; + + std::mutex m_timed_events_mutex{}; + /// The map of time point's to poll infos for tasks that are yielding for a period of time + /// or for tasks that are polling with timeouts. + timed_events m_timed_events{}; + + /// Has the io_scheduler been requested to shut down? + std::atomic m_shutdown_requested{false}; + + std::atomic m_io_processing{false}; + auto process_events_manual(std::chrono::milliseconds timeout) -> void; + auto process_events_dedicated_thread() -> void; + auto process_events_execute(std::chrono::milliseconds timeout) -> void; + static auto event_to_poll_status(uint32_t events) -> poll_status; + + auto process_scheduled_execute_inline() -> void; + std::mutex m_scheduled_tasks_mutex{}; + std::vector> m_scheduled_tasks{}; + + /// Tasks that have their ownership passed into the scheduler. This is a bit strange for now + /// but the concept doesn't pass since io_scheduler isn't fully defined yet. + /// The type is coro::task_container* + /// Do not inline any functions that use this in the io_scheduler header, it can cause the linker + /// to complain about "defined in discarded section" because it gets defined multiple times + void* m_owned_tasks{nullptr}; + + static constexpr const int m_shutdown_object{0}; + static constexpr const void* m_shutdown_ptr = &m_shutdown_object; + + static constexpr const int m_timer_object{0}; + static constexpr const void* m_timer_ptr = &m_timer_object; + + static constexpr const int m_schedule_object{0}; + static constexpr const void* m_schedule_ptr = &m_schedule_object; + + static const constexpr std::chrono::milliseconds m_default_timeout{1000}; + static const constexpr std::chrono::milliseconds m_no_timeout{0}; + static const constexpr std::size_t m_max_events = 16; + std::array m_events{}; + std::vector> m_handles_to_resume{}; + + auto process_event_execute(detail::poll_info* pi, poll_status status) -> void; + auto process_timeout_execute() -> void; + + auto add_timer_token(time_point tp, detail::poll_info& pi) -> timed_events::iterator; + auto remove_timer_token(timed_events::iterator pos) -> void; + auto update_timeout(time_point now) -> void; + }; +} diff --git a/include/glaze/coroutine/latch.hpp b/include/glaze/coroutine/latch.hpp new file mode 100644 index 0000000000..1b3ac58ac2 --- /dev/null +++ b/include/glaze/coroutine/latch.hpp @@ -0,0 +1,81 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include + +#include "glaze/coroutine/event.hpp" +#include "glaze/coroutine/thread_pool.hpp" + +namespace glz +{ + /** + * The latch is thread safe counter to wait for 1 or more other tasks to complete, they signal their + * completion by calling `count_down()` on the latch and upon the latch counter reaching zero the + * coroutine `co_await`ing the latch then resumes execution. + * + * This is useful for spawning many worker tasks to complete either a computationally complex task + * across a thread pool of workers, or waiting for many asynchronous results like http requests + * to complete. + */ + struct latch + { + /** + * Creates a latch with the given count of tasks to wait to complete. + * @param count The number of tasks to wait to complete, if this is zero or negative then the + * latch starts 'completed' immediately and execution is resumed with no suspension. + */ + latch(std::int64_t count) noexcept : m_count(count), m_event(count <= 0) {} + + latch(const latch&) = delete; + latch(latch&&) = delete; + auto operator=(const latch&) -> latch& = delete; + auto operator=(latch&&) -> latch& = delete; + + /** + * @return True if the latch has been counted down to zero. + */ + auto is_ready() const noexcept -> bool { return m_event.is_set(); } + + /** + * @return The number of tasks this latch is still waiting to complete. + */ + auto remaining() const noexcept -> std::size_t { return m_count.load(std::memory_order::acquire); } + + /** + * If the latch counter goes to zero then the task awaiting the latch is resumed. + * @param n The number of tasks to complete towards the latch, defaults to 1. + */ + auto count_down(std::int64_t n = 1) noexcept -> void + { + if (m_count.fetch_sub(n, std::memory_order::acq_rel) <= n) { + m_event.set(); + } + } + + /** + * If the latch counter goes to zero then the task awaiting the latch is resumed on the given + * thread pool. + * @param tp The thread pool to schedule the task that is waiting on the latch on. + * @param n The number of tasks to complete towards the latch, defaults to 1. + */ + auto count_down(coro::thread_pool& tp, std::int64_t n = 1) noexcept -> void + { + if (m_count.fetch_sub(n, std::memory_order::acq_rel) <= n) { + m_event.set(tp); + } + } + + auto operator co_await() const noexcept -> event::awaiter { return m_event.operator co_await(); } + + private: + /// The number of tasks to wait for completion before triggering the event to resume. + std::atomic m_count; + /// The event to trigger when the latch counter reaches zero, this resumes the coroutine that + /// is co_await'ing on the latch. + event m_event; + }; +} diff --git a/include/glaze/coroutine/poll.hpp b/include/glaze/coroutine/poll.hpp new file mode 100644 index 0000000000..af84560f1e --- /dev/null +++ b/include/glaze/coroutine/poll.hpp @@ -0,0 +1,62 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include + +namespace glz +{ + /*enum struct poll_op : uint64_t { + read = EPOLLIN, + write = EPOLLOUT, + read_write = EPOLLIN | EPOLLOUT + }; + + inline bool poll_op_readable(poll_op op) { return (uint64_t(op) & EPOLLIN); } + + inline bool poll_op_writeable(poll_op op) { return (uint64_t(op) & EPOLLOUT); } + + std::string to_string(poll_op op) + { + switch (op) { + case poll_op::read: + return "read"; + case poll_op::write: + return "write"; + case poll_op::read_write: + return "read_write"; + default: + return "unknown"; + } + } + + enum class poll_status { + /// The poll operation was was successful. + event, + /// The poll operation timed out. + timeout, + /// The file descriptor had an error while polling. + error, + /// The file descriptor has been closed by the remote or an internal error/close. + closed + }; + + std::string to_string(poll_status status) + { + switch (status) { + case poll_status::event: + return "event"; + case poll_status::timeout: + return "timeout"; + case poll_status::error: + return "error"; + case poll_status::closed: + return "closed"; + default: + return "unknown"; + } + }*/ +} diff --git a/include/glaze/coroutine/poll_info.hpp b/include/glaze/coroutine/poll_info.hpp new file mode 100644 index 0000000000..90a6584deb --- /dev/null +++ b/include/glaze/coroutine/poll_info.hpp @@ -0,0 +1,78 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#pragma once + +#include +#include +#include +#include + +#include "glaze/coroutine/file_descriptor.hpp" +#include "glaze/coroutine/poll.hpp" +#include "glaze/coroutine/time.hpp" + +namespace glz +{ + /** + * Poll Info encapsulates everything about a poll operation for the event as well as its paired + * timeout. This is important since coroutines that are waiting on an event or timeout do not + * immediately execute, they are re-scheduled onto the thread pool, so its possible its pair + * event or timeout also triggers while the coroutine is still waiting to resume. This means that + * the first one to happen, the event itself or its timeout, needs to disable the other pair item + * prior to resuming the coroutine. + * + * Finally, its also important to note that the event and its paired timeout could happen during + * the same epoll_wait and possibly trigger the coroutine to start twice. Only one can win, so the + * first one processed sets m_processed to true and any subsequent events in the same epoll batch + * are effectively discarded. + */ + struct poll_info + { + using timed_events = std::multimap; + + poll_info() = default; + ~poll_info() = default; + + poll_info(const poll_info&) = delete; + poll_info(poll_info&&) = delete; + auto operator=(const poll_info&) -> poll_info& = delete; + auto operator=(poll_info&&) -> poll_info& = delete; + + struct poll_awaiter + { + explicit poll_awaiter(poll_info& pi) noexcept : m_pi(pi) {} + + auto await_ready() const noexcept -> bool { return false; } + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> void + { + m_pi.m_awaiting_coroutine = awaiting_coroutine; + std::atomic_thread_fence(std::memory_order::release); + } + auto await_resume() noexcept -> poll_status { return m_pi.m_poll_status; } + + poll_info& m_pi; + }; + + auto operator co_await() noexcept -> poll_awaiter { return poll_awaiter{*this}; } + + /// The file descriptor being polled on. This is needed so that if the timeout occurs first then + /// the event loop can immediately disable the event within epoll. + fd_t m_fd{-1}; + /// The timeout's position in the timeout map. A poll() with no timeout or yield() this is empty. + /// This is needed so that if the event occurs first then the event loop can immediately disable + /// the timeout within epoll. + std::optional m_timer_pos{std::nullopt}; + /// The awaiting coroutine for this poll info to resume upon event or timeout. + std::coroutine_handle<> m_awaiting_coroutine; + /// The status of the poll operation. + poll_status m_poll_status{coro::poll_status::error}; + /// Did the timeout and event trigger at the same time on the same epoll_wait call? + /// Once this is set to true all future events on this poll info are null and void. + bool m_processed{false}; + }; +} diff --git a/include/glaze/coroutine/task_container.hpp b/include/glaze/coroutine/task_container.hpp new file mode 100644 index 0000000000..53eac64597 --- /dev/null +++ b/include/glaze/coroutine/task_container.hpp @@ -0,0 +1,270 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "glaze/coroutine/concepts.hpp" +#include "glaze/coroutine/task.hpp" + +namespace glz +{ + struct io_scheduler; + + template + struct task_container + { + struct options + { + /// The number of task spots to reserve space for upon creating the container. + std::size_t reserve_size{8}; + /// The growth factor for task space in the container when capacity is full. + double growth_factor{2}; + }; + + /** + * @param e Tasks started in the container are scheduled onto this executor. For tasks created + * from a coro::io_scheduler, this would usually be that coro::io_scheduler instance. + * @param opts Task container options. + */ + task_container(std::shared_ptr e, + const options opts = options{.reserve_size = 8, .growth_factor = 2}) + : m_growth_factor(opts.growth_factor), m_executor(std::move(e)), m_executor_ptr(m_executor.get()) + { + if (m_executor == nullptr) { + throw std::runtime_error{"task_container cannot have a nullptr executor"}; + } + + init(opts.reserve_size); + } + task_container(const task_container&) = delete; + task_container(task_container&&) = delete; + auto operator=(const task_container&) -> task_container& = delete; + auto operator=(task_container&&) -> task_container& = delete; + ~task_container() + { + // This will hang the current thread.. but if tasks are not complete thats also pretty bad. + while (!empty()) { + garbage_collect(); + } + } + + enum class garbage_collect_t { + /// Execute garbage collection. + yes, + /// Do not execute garbage collection. + no + }; + + /** + * Stores a user task and starts its execution on the container's thread pool. + * @param user_task The scheduled user's task to store in this task container and start its execution. + * @param cleanup Should the task container run garbage collect at the beginning of this store + * call? Calling at regular intervals will reduce memory usage of completed + * tasks and allow for the task container to re-use allocated space. + */ + auto start(coro::task&& user_task, garbage_collect_t cleanup = garbage_collect_t::yes) -> void + { + m_size.fetch_add(1, std::memory_order::relaxed); + + std::unique_lock lk{m_mutex}; + + if (cleanup == garbage_collect_t::yes) { + gc_internal(); + } + + // Only grow if completely full and attempting to add more. + if (m_free_task_indices.empty()) { + grow(); + } + + // Reserve a free task index + std::size_t index = m_free_task_indices.front(); + m_free_task_indices.pop(); + + // We've reserved the slot, we can release the lock. + lk.unlock(); + + // Store the task inside a cleanup task for self deletion. + m_tasks[index] = make_cleanup_task(std::move(user_task), index); + + // Start executing from the cleanup task to schedule the user's task onto the thread pool. + m_tasks[index].resume(); + } + + /** + * Garbage collects any tasks that are marked as deleted. This frees up space to be re-used by + * the task container for newly stored tasks. + * @return The number of tasks that were deleted. + */ + auto garbage_collect() -> std::size_t __ATTRIBUTE__(used) + { + std::scoped_lock lk{m_mutex}; + return gc_internal(); + } + + /** + * @return The number of active tasks in the container. + */ + auto size() const -> std::size_t { return m_size.load(std::memory_order::relaxed); } + + /** + * @return True if there are no active tasks in the container. + */ + auto empty() const -> bool { return size() == 0; } + + /** + * @return The capacity of this task manager before it will need to grow in size. + */ + auto capacity() const -> std::size_t + { + std::atomic_thread_fence(std::memory_order::acquire); + return m_tasks.size(); + } + + /** + * Will continue to garbage collect and yield until all tasks are complete. This method can be + * co_await'ed to make it easier to wait for the task container to have all its tasks complete. + * + * This does not shut down the task container, but can be used when shutting down, or if your + * logic requires all the tasks contained within to complete, it is similar to coro::latch. + */ + auto garbage_collect_and_yield_until_empty() -> coro::task + { + while (!empty()) { + garbage_collect(); + co_await m_executor_ptr->yield(); + } + } + + private: + /** + * Grows each task container by the growth factor. + * @return The position of the free index after growing. + */ + auto grow() -> void + { + // Save an index at the current last item. + std::size_t new_size = m_tasks.size() * m_growth_factor; + for (std::size_t i = m_tasks.size(); i < new_size; ++i) { + m_free_task_indices.emplace(i); + } + m_tasks.resize(new_size); + } + + /** + * Internal GC call, expects the public function to lock. + */ + auto gc_internal() -> std::size_t + { + std::size_t deleted{0}; + auto pos = std::begin(m_tasks_to_delete); + while (pos != std::end(m_tasks_to_delete)) { + // Skip tasks that are still running or have yet to start. + if (!m_tasks[*pos].is_ready()) { + pos++; + continue; + } + // Destroy the cleanup task and the user task. + m_tasks[*pos].destroy(); + // Put the deleted position at the end of the free indexes list. + m_free_task_indices.emplace(*pos); + // Remove index from tasks to delete + m_tasks_to_delete.erase(pos++); + // Indicate a task was deleted. + ++deleted; + } + m_size.fetch_sub(deleted, std::memory_order::relaxed); + return deleted; + } + + /** + * Encapsulate the users tasks in a cleanup task which marks itself for deletion upon + * completion. Simply co_await the users task until its completed and then mark the given + * position within the task manager as being deletable. The scheduler's next iteration + * in its event loop will then free that position up to be re-used. + * + * This function will also unconditionally catch all unhandled exceptions by the user's + * task to prevent the scheduler from throwing exceptions. + * @param user_task The user's task. + * @param index The index where the task data will be stored in the task manager. + * @return The user's task wrapped in a self cleanup task. + */ + auto make_cleanup_task(task user_task, std::size_t index) -> coro::task + { + // Immediately move the task onto the executor. + co_await m_executor_ptr->schedule(); + + try { + // Await the users task to complete. + co_await user_task; + } + catch (const std::exception& e) { + // TODO: what would be a good way to report this to the user...? Catching here is required + // since the co_await will unwrap the unhandled exception on the task. + // The user's task should ideally be wrapped in a catch all and handle it themselves, but + // that cannot be guaranteed. + std::cerr << "coro::task_container user_task had an unhandled exception e.what()= " << e.what() << "\n"; + } + catch (...) { + // don't crash if they throw something that isn't derived from std::exception + std::cerr << "coro::task_container user_task had unhandle exception, not derived from std::exception.\n"; + } + + { + // This scope is required around this lock otherwise if this task on destruction schedules a new task it + // can cause a deadlock, notably tls::client schedules a task to cleanup tls resources. + std::scoped_lock lk{m_mutex}; + m_tasks_to_delete.emplace_back(index); + } + + co_return; + } + + /// Mutex for safely mutating the task containers across threads, expected usage is within + /// thread pools for indeterminate lifetime requests. + std::mutex m_mutex{}; + /// The number of alive tasks. + std::atomic m_size{}; + /// Maintains the lifetime of the tasks until they are completed. + std::vector> m_tasks{}; + /// The full set of free indicies into `m_tasks`. + std::queue m_free_task_indices{}; + /// The set of tasks that have completed and need to be deleted. + std::list m_tasks_to_delete{}; + /// The amount to grow the containers by when all spaces are taken. + double m_growth_factor{}; + /// The executor to schedule tasks that have just started. + std::shared_ptr m_executor{nullptr}; + /// This is used internally since io_scheduler cannot pass itself in as a shared_ptr. + executor_type* m_executor_ptr{nullptr}; + + /** + * Special constructor for internal types to create their embeded task containers. + */ + + friend io_scheduler; + task_container(executor_type& e, const options opts = options{.reserve_size = 8, .growth_factor = 2}) + : m_growth_factor(opts.growth_factor), m_executor_ptr(&e) + { + init(opts.reserve_size); + } + + auto init(std::size_t reserve_size) -> void + { + m_tasks.resize(reserve_size); + for (std::size_t i = 0; i < reserve_size; ++i) { + m_free_task_indices.emplace(i); + } + } + }; +} diff --git a/include/glaze/coroutine/time.hpp b/include/glaze/coroutine/time.hpp new file mode 100644 index 0000000000..3d133b2e6d --- /dev/null +++ b/include/glaze/coroutine/time.hpp @@ -0,0 +1,10 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +namespace glz +{ + using clock = std::chrono::steady_clock; + using time_point = clock::time_point; +} diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index fc52ea81e1..068fe1492a 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -131,4 +131,54 @@ suite event = [] { glz::sync_wait(glz::when_all(make_wait_task(e, 1), make_wait_task(e, 2), make_wait_task(e, 3), make_set_task(e))); }; +suite latch = [] { + // Complete worker tasks faster on a thread pool, using the io_scheduler version so the worker + // tasks can yield for a specific amount of time to mimic difficult work. The pool is only + // setup with a single thread to showcase yield_for(). + glz::io_scheduler tp{glz::io_scheduler::options{.pool = glz::thread_pool::options{.thread_count = 1}}}; + + // This task will wait until the given latch setters have completed. + auto make_latch_task = [](glz::latch& l) -> glz::task { + // It seems like the dependent worker tasks could be created here, but in that case it would + // be superior to simply do: `co_await coro::when_all(tasks);` + // It is also important to note that the last dependent task will resume the waiting latch + // task prior to actually completing -- thus the dependent task's frame could be destroyed + // by the latch task completing before it gets a chance to finish after calling resume() on + // the latch task! + + std::cout << "latch task is now waiting on all children tasks...\n"; + co_await l; + std::cout << "latch task dependency tasks completed, resuming.\n"; + co_return; + }; + + // This task does 'work' and counts down on the latch when completed. The final child task to + // complete will end up resuming the latch task when the latch's count reaches zero. + auto make_worker_task = [](glz::io_scheduler& tp, glz::latch& l, int64_t i) -> glz::task { + // Schedule the worker task onto the thread pool. + co_await tp.schedule(); + std::cout << "worker task " << i << " is working...\n"; + // Do some expensive calculations, yield to mimic work...! Its also important to never use + // std::this_thread::sleep_for() within the context of coroutines, it will block the thread + // and other tasks that are ready to execute will be blocked. + co_await tp.yield_for(std::chrono::milliseconds{i * 20}); + std::cout << "worker task " << i << " is done, counting down on the latch\n"; + l.count_down(); + co_return; + }; + + const int64_t num_tasks{5}; + glz::latch l{num_tasks}; + std::vector> tasks{}; + + // Make the latch task first so it correctly waits for all worker tasks to count down. + tasks.emplace_back(make_latch_task(l)); + for (int64_t i = 1; i <= num_tasks; ++i) { + tasks.emplace_back(make_worker_task(tp, l, i)); + } + + // Wait for all tasks to complete. + glz::sync_wait(glz::when_all(std::move(tasks))); +}; + int main() { return 0; } From 8e3e3fa5edd0efea87c0a83e8997229bdcfeb83f Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 08:19:48 -0500 Subject: [PATCH 142/309] fixing --- include/glaze/coroutine/io_scheduler.hpp | 56 +++++++++++++--------- include/glaze/coroutine/latch.hpp | 2 +- include/glaze/coroutine/poll.hpp | 18 ++++--- include/glaze/coroutine/poll_info.hpp | 4 +- include/glaze/coroutine/task_container.hpp | 23 +++++++-- tests/coroutine_test/coroutine_test.cpp | 2 +- 6 files changed, 66 insertions(+), 39 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index ff482d821f..93d8c6e650 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -15,8 +15,6 @@ #include "coro/net/socket.hpp" #endif -#include - #include #include #include @@ -27,12 +25,25 @@ namespace glz { - class io_scheduler + // TODO: implement + using event_handle_t = int; + using poll_event_t = int; + + // Linux + //using poll_event_t = struct epoll_event; + //using event_handle_t = eventfd_t; + + // TODO: implement + int event_write(auto, auto) + { + return 0; + } + + struct io_scheduler { - using timed_events = detail::poll_info::timed_events; + using timed_events = poll_info::timed_events; - public: - class schedule_operation; + struct schedule_operation; friend schedule_operation; enum class thread_strategy_t { @@ -103,12 +114,11 @@ namespace glz */ auto process_events(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) -> std::size_t; - class schedule_operation + struct schedule_operation { - friend class io_scheduler; + friend struct io_scheduler; explicit schedule_operation(io_scheduler& scheduler) noexcept : m_scheduler(scheduler) {} - public: /** * Operations always pause so the executing thread can be switched. */ @@ -131,8 +141,8 @@ namespace glz bool expected{false}; if (m_scheduler.m_schedule_fd_triggered.compare_exchange_strong( expected, true, std::memory_order::release, std::memory_order::relaxed)) { - eventfd_t value{1}; - eventfd_write(m_scheduler.m_schedule_fd, value); + event_handle_t value{1}; + event_write(m_scheduler.m_schedule_fd, value); } } else { @@ -162,21 +172,21 @@ namespace glz * @param task The task to execute on this io_scheduler. It's lifetime ownership will be transferred * to this io_scheduler. */ - auto schedule(coro::task&& task) -> void; + auto schedule(glz::task&& task) -> void; /** * Schedules the current task to run after the given amount of time has elapsed. * @param amount The amount of time to wait before resuming execution of this task. * Given zero or negative amount of time this behaves identical to schedule(). */ - [[nodiscard]] auto schedule_after(std::chrono::milliseconds amount) -> coro::task; + [[nodiscard]] auto schedule_after(std::chrono::milliseconds amount) -> glz::task; /** * Schedules the current task to run at a given time point in the future. * @param time The time point to resume execution of this task. Given 'now' or a time point * in the past this behaves identical to schedule(). */ - [[nodiscard]] auto schedule_at(time_point time) -> coro::task; + [[nodiscard]] auto schedule_at(time_point time) -> glz::task; /** * Yields the current task to the end of the queue of waiting tasks. @@ -188,14 +198,14 @@ namespace glz * @param amount The amount of time to yield for before resuming executino of this task. * Given zero or negative amount of time this behaves identical to yield(). */ - [[nodiscard]] auto yield_for(std::chrono::milliseconds amount) -> coro::task; + [[nodiscard]] auto yield_for(std::chrono::milliseconds amount) -> glz::task; /** * Yields the current task until the given time point in the future. * @param time The time point to resume execution of this task. Given 'now' or a time point in the * in the past this behaves identical to yield(). */ - [[nodiscard]] auto yield_until(time_point time) -> coro::task; + [[nodiscard]] auto yield_until(time_point time) -> glz::task; /** * Polls the given file descriptor for the given operations. @@ -205,9 +215,9 @@ namespace glz * block indefinitely until the event triggers. * @return The result of the poll operation. */ - [[nodiscard]] auto poll(fd_t fd, coro::poll_op op, + [[nodiscard]] auto poll(fd_t fd, glz::poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) - -> coro::task; + -> glz::task; #ifdef GLZ_FEATURE_NETWORKING /** @@ -249,8 +259,8 @@ namespace glz bool expected{false}; if (m_schedule_fd_triggered.compare_exchange_strong(expected, true, std::memory_order::release, std::memory_order::relaxed)) { - eventfd_t value{1}; - eventfd_write(m_schedule_fd, value); + event_handle_t value{1}; + event_write(m_schedule_fd, value); } return true; @@ -350,13 +360,13 @@ namespace glz static const constexpr std::chrono::milliseconds m_default_timeout{1000}; static const constexpr std::chrono::milliseconds m_no_timeout{0}; static const constexpr std::size_t m_max_events = 16; - std::array m_events{}; + std::array m_events{}; std::vector> m_handles_to_resume{}; - auto process_event_execute(detail::poll_info* pi, poll_status status) -> void; + auto process_event_execute(poll_info* pi, poll_status status) -> void; auto process_timeout_execute() -> void; - auto add_timer_token(time_point tp, detail::poll_info& pi) -> timed_events::iterator; + auto add_timer_token(time_point tp, poll_info& pi) -> timed_events::iterator; auto remove_timer_token(timed_events::iterator pos) -> void; auto update_timeout(time_point now) -> void; }; diff --git a/include/glaze/coroutine/latch.hpp b/include/glaze/coroutine/latch.hpp index 1b3ac58ac2..8653692188 100644 --- a/include/glaze/coroutine/latch.hpp +++ b/include/glaze/coroutine/latch.hpp @@ -62,7 +62,7 @@ namespace glz * @param tp The thread pool to schedule the task that is waiting on the latch on. * @param n The number of tasks to complete towards the latch, defaults to 1. */ - auto count_down(coro::thread_pool& tp, std::int64_t n = 1) noexcept -> void + auto count_down(glz::thread_pool& tp, std::int64_t n = 1) noexcept -> void { if (m_count.fetch_sub(n, std::memory_order::acq_rel) <= n) { m_event.set(tp); diff --git a/include/glaze/coroutine/poll.hpp b/include/glaze/coroutine/poll.hpp index af84560f1e..9df7c4e3de 100644 --- a/include/glaze/coroutine/poll.hpp +++ b/include/glaze/coroutine/poll.hpp @@ -9,15 +9,19 @@ namespace glz { - /*enum struct poll_op : uint64_t { - read = EPOLLIN, - write = EPOLLOUT, - read_write = EPOLLIN | EPOLLOUT + // TODO: handle poll flags + constexpr auto poll_in = 0b00000001; + constexpr auto poll_out = 0b00000010; + + enum struct poll_op : uint64_t { + read = poll_in, + write = poll_out, + read_write = poll_in | poll_out }; - inline bool poll_op_readable(poll_op op) { return (uint64_t(op) & EPOLLIN); } + inline bool poll_op_readable(poll_op op) { return (uint64_t(op) & poll_in); } - inline bool poll_op_writeable(poll_op op) { return (uint64_t(op) & EPOLLOUT); } + inline bool poll_op_writeable(poll_op op) { return (uint64_t(op) & poll_out); } std::string to_string(poll_op op) { @@ -58,5 +62,5 @@ namespace glz default: return "unknown"; } - }*/ + } } diff --git a/include/glaze/coroutine/poll_info.hpp b/include/glaze/coroutine/poll_info.hpp index 90a6584deb..68da5a8162 100644 --- a/include/glaze/coroutine/poll_info.hpp +++ b/include/glaze/coroutine/poll_info.hpp @@ -33,7 +33,7 @@ namespace glz */ struct poll_info { - using timed_events = std::multimap; + using timed_events = std::multimap; poll_info() = default; ~poll_info() = default; @@ -70,7 +70,7 @@ namespace glz /// The awaiting coroutine for this poll info to resume upon event or timeout. std::coroutine_handle<> m_awaiting_coroutine; /// The status of the poll operation. - poll_status m_poll_status{coro::poll_status::error}; + poll_status m_poll_status{glz::poll_status::error}; /// Did the timeout and event trigger at the same time on the same epoll_wait call? /// Once this is set to true all future events on this poll info are null and void. bool m_processed{false}; diff --git a/include/glaze/coroutine/task_container.hpp b/include/glaze/coroutine/task_container.hpp index 53eac64597..854cc204b4 100644 --- a/include/glaze/coroutine/task_container.hpp +++ b/include/glaze/coroutine/task_container.hpp @@ -5,6 +5,15 @@ #pragma once +#ifndef GLZ_THROW_OR_ABORT +#if __cpp_exceptions +#define GLZ_THROW_OR_ABORT(EXC) (throw(EXC)) +#include +#else +#define GLZ_THROW_OR_ABORT(EXC) (std::abort()) +#endif +#endif + #include #include #include @@ -41,7 +50,7 @@ namespace glz : m_growth_factor(opts.growth_factor), m_executor(std::move(e)), m_executor_ptr(m_executor.get()) { if (m_executor == nullptr) { - throw std::runtime_error{"task_container cannot have a nullptr executor"}; + GLZ_THROW_OR_ABORT(std::runtime_error{"task_container cannot have a nullptr executor"}); } init(opts.reserve_size); @@ -72,7 +81,7 @@ namespace glz * call? Calling at regular intervals will reduce memory usage of completed * tasks and allow for the task container to re-use allocated space. */ - auto start(coro::task&& user_task, garbage_collect_t cleanup = garbage_collect_t::yes) -> void + auto start(glz::task&& user_task, garbage_collect_t cleanup = garbage_collect_t::yes) -> void { m_size.fetch_add(1, std::memory_order::relaxed); @@ -106,7 +115,7 @@ namespace glz * the task container for newly stored tasks. * @return The number of tasks that were deleted. */ - auto garbage_collect() -> std::size_t __ATTRIBUTE__(used) + auto garbage_collect() -> std::size_t { std::scoped_lock lk{m_mutex}; return gc_internal(); @@ -138,7 +147,7 @@ namespace glz * This does not shut down the task container, but can be used when shutting down, or if your * logic requires all the tasks contained within to complete, it is similar to coro::latch. */ - auto garbage_collect_and_yield_until_empty() -> coro::task + auto garbage_collect_and_yield_until_empty() -> glz::task { while (!empty()) { garbage_collect(); @@ -199,11 +208,12 @@ namespace glz * @param index The index where the task data will be stored in the task manager. * @return The user's task wrapped in a self cleanup task. */ - auto make_cleanup_task(task user_task, std::size_t index) -> coro::task + auto make_cleanup_task(task user_task, std::size_t index) -> glz::task { // Immediately move the task onto the executor. co_await m_executor_ptr->schedule(); +#if __cpp_exceptions try { // Await the users task to complete. co_await user_task; @@ -219,6 +229,9 @@ namespace glz // don't crash if they throw something that isn't derived from std::exception std::cerr << "coro::task_container user_task had unhandle exception, not derived from std::exception.\n"; } +#else + co_await user_task; +#endif { // This scope is required around this lock otherwise if this task on destruction schedules a new task it diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 068fe1492a..65b5f803e4 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -169,7 +169,7 @@ suite latch = [] { const int64_t num_tasks{5}; glz::latch l{num_tasks}; - std::vector> tasks{}; + std::vector> tasks{}; // Make the latch task first so it correctly waits for all worker tasks to count down. tasks.emplace_back(make_latch_task(l)); From b0708c839b44dfa3056a9cb5d66721fe1809055b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 08:55:33 -0500 Subject: [PATCH 143/309] adding functions to io_scheduler --- include/glaze/coroutine/io_scheduler.hpp | 484 +++++++++++++++++++++-- 1 file changed, 443 insertions(+), 41 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 93d8c6e650..16d75e8495 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -5,9 +5,9 @@ #pragma once -#include "glaze/coroutine/poll_info.hpp" #include "glaze/coroutine/file_descriptor.hpp" #include "glaze/coroutine/poll.hpp" +#include "glaze/coroutine/poll_info.hpp" #include "glaze/coroutine/task_container.hpp" #include "glaze/coroutine/thread_pool.hpp" @@ -28,18 +28,15 @@ namespace glz // TODO: implement using event_handle_t = int; using poll_event_t = int; - + // Linux - //using poll_event_t = struct epoll_event; - //using event_handle_t = eventfd_t; - + // using poll_event_t = struct epoll_event; + // using event_handle_t = eventfd_t; + // TODO: implement - int event_write(auto, auto) - { - return 0; - } - - struct io_scheduler + int event_write(auto, auto) { return 0; } + + struct io_scheduler final { using timed_events = poll_info::timed_events; @@ -84,23 +81,76 @@ namespace glz const execution_strategy_t execution_strategy{execution_strategy_t::process_tasks_on_thread_pool}; }; - explicit io_scheduler(options opts = options{ - .thread_strategy = thread_strategy_t::spawn, - .on_io_thread_start_functor = nullptr, - .on_io_thread_stop_functor = nullptr, - .pool = {.thread_count = ((std::thread::hardware_concurrency() > 1) - ? (std::thread::hardware_concurrency() - 1) - : 1), - .on_thread_start_functor = nullptr, - .on_thread_stop_functor = nullptr}, - .execution_strategy = execution_strategy_t::process_tasks_on_thread_pool}); + explicit io_scheduler( + options opts = options{.thread_strategy = thread_strategy_t::spawn, + .on_io_thread_start_functor = nullptr, + .on_io_thread_stop_functor = nullptr, + .pool = {.thread_count = ((std::thread::hardware_concurrency() > 1) + ? (std::thread::hardware_concurrency() - 1) + : 1), + .on_thread_start_functor = nullptr, + .on_thread_stop_functor = nullptr}, + .execution_strategy = execution_strategy_t::process_tasks_on_thread_pool}) + : m_opts(std::move(opts)), + m_epoll_fd(epoll_create1(EPOLL_CLOEXEC)), + m_shutdown_fd(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)), + m_timer_fd(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC)), + m_schedule_fd(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)), + m_owned_tasks(new coro::task_container(*this)) + { + if (opts.execution_strategy == execution_strategy_t::process_tasks_on_thread_pool) { + m_thread_pool = std::make_unique(std::move(m_opts.pool)); + } + + epoll_event e{}; + e.events = EPOLLIN; + + e.data.ptr = const_cast(m_shutdown_ptr); + epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_shutdown_fd, &e); + + e.data.ptr = const_cast(m_timer_ptr); + epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_timer_fd, &e); + + e.data.ptr = const_cast(m_schedule_ptr); + epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_schedule_fd, &e); + + if (m_opts.thread_strategy == thread_strategy_t::spawn) { + m_io_thread = std::thread([this]() { process_events_dedicated_thread(); }); + } + // else manual mode, the user must call process_events. + } io_scheduler(const io_scheduler&) = delete; io_scheduler(io_scheduler&&) = delete; auto operator=(const io_scheduler&) -> io_scheduler& = delete; auto operator=(io_scheduler&&) -> io_scheduler& = delete; - ~io_scheduler(); + ~io_scheduler() + { + shutdown(); + + if (m_io_thread.joinable()) { + m_io_thread.join(); + } + + if (m_epoll_fd != -1) { + close(m_epoll_fd); + m_epoll_fd = -1; + } + if (m_timer_fd != -1) { + close(m_timer_fd); + m_timer_fd = -1; + } + if (m_schedule_fd != -1) { + close(m_schedule_fd); + m_schedule_fd = -1; + } + + if (m_owned_tasks != nullptr) { + delete static_cast*>(m_owned_tasks); + m_owned_tasks = nullptr; + } + } /** * Given a thread_strategy_t::manual this function should be called at regular intervals to @@ -112,7 +162,11 @@ namespace glz * indefinitely until an event happens. * @param return The number of tasks currently executing or waiting to execute. */ - auto process_events(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) -> std::size_t; + std::size_t process_events(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) + { + process_events_manual(timeout); + return size(); + } struct schedule_operation { @@ -172,21 +226,28 @@ namespace glz * @param task The task to execute on this io_scheduler. It's lifetime ownership will be transferred * to this io_scheduler. */ - auto schedule(glz::task&& task) -> void; + void schedule(glz::task&& task) + { + auto* ptr = static_cast*>(m_owned_tasks); + ptr->start(std::move(task)); + } /** * Schedules the current task to run after the given amount of time has elapsed. * @param amount The amount of time to wait before resuming execution of this task. * Given zero or negative amount of time this behaves identical to schedule(). */ - [[nodiscard]] auto schedule_after(std::chrono::milliseconds amount) -> glz::task; + [[nodiscard]] auto schedule_after(std::chrono::milliseconds amount) -> glz::task + { + return yield_for(amount); + } /** * Schedules the current task to run at a given time point in the future. * @param time The time point to resume execution of this task. Given 'now' or a time point * in the past this behaves identical to schedule(). */ - [[nodiscard]] auto schedule_at(time_point time) -> glz::task; + [[nodiscard]] auto schedule_at(time_point time) -> glz::task { return yield_until(time); } /** * Yields the current task to the end of the queue of waiting tasks. @@ -198,14 +259,56 @@ namespace glz * @param amount The amount of time to yield for before resuming executino of this task. * Given zero or negative amount of time this behaves identical to yield(). */ - [[nodiscard]] auto yield_for(std::chrono::milliseconds amount) -> glz::task; + [[nodiscard]] auto yield_for(std::chrono::milliseconds amount) -> glz::task + { + if (amount <= 0ms) { + co_await schedule(); + } + else { + // Yield/timeout tasks are considered live in the scheduler and must be accounted for. Note + // that if the user gives an invalid amount and schedule() is directly called it will account + // for the scheduled task there. + m_size.fetch_add(1, std::memory_order::release); + + // Yielding does not requiring setting the timer position on the poll info since + // it doesn't have a corresponding 'event' that can trigger, it always waits for + // the timeout to occur before resuming. + + detail::poll_info pi{}; + add_timer_token(clock::now() + amount, pi); + co_await pi; + + m_size.fetch_sub(1, std::memory_order::release); + } + co_return; + } /** * Yields the current task until the given time point in the future. * @param time The time point to resume execution of this task. Given 'now' or a time point in the * in the past this behaves identical to yield(). */ - [[nodiscard]] auto yield_until(time_point time) -> glz::task; + [[nodiscard]] auto yield_until(time_point time) -> glz::task + { + auto now = clock::now(); + + // If the requested time is in the past (or now!) bail out! + if (time <= now) { + co_await schedule(); + } + else { + m_size.fetch_add(1, std::memory_order::release); + + auto amount = std::chrono::duration_cast(time - now); + + detail::poll_info pi{}; + add_timer_token(now + amount, pi); + co_await pi; + + m_size.fetch_sub(1, std::memory_order::release); + } + co_return; + } /** * Polls the given file descriptor for the given operations. @@ -217,7 +320,39 @@ namespace glz */ [[nodiscard]] auto poll(fd_t fd, glz::poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) - -> glz::task; + -> glz::task + { + // Because the size will drop when this coroutine suspends every poll needs to undo the subtraction + // on the number of active tasks in the scheduler. When this task is resumed by the event loop. + m_size.fetch_add(1, std::memory_order::release); + + // Setup two events, a timeout event and the actual poll for op event. + // Whichever triggers first will delete the other to guarantee only one wins. + // The resume token will be set by the scheduler to what the event turned out to be. + + bool timeout_requested = (timeout > 0ms); + + detail::poll_info pi{}; + pi.m_fd = fd; + + if (timeout_requested) { + pi.m_timer_pos = add_timer_token(clock::now() + timeout, pi); + } + + epoll_event e{}; + e.events = static_cast(op) | EPOLLONESHOT | EPOLLRDHUP; + e.data.ptr = π + if (epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, fd, &e) == -1) { + std::cerr << "epoll ctl error on fd " << fd << "\n"; + } + + // The event loop will 'clean-up' whichever event didn't win since the coroutine is scheduled + // onto the thread poll its possible the other type of event could trigger while its waiting + // to execute again, thus restarting the coroutine twice, that would be quite bad. + auto result = co_await pi; + m_size.fetch_sub(1, std::memory_order::release); + co_return result; + } #ifdef GLZ_FEATURE_NETWORKING /** @@ -292,14 +427,35 @@ namespace glz * Starts the shutdown of the io scheduler. All currently executing and pending tasks will complete * prior to shutting down. This call is blocking and will not return until all tasks complete. */ - auto shutdown() noexcept -> void; + void shutdown() noexcept + { + // Only allow shutdown to occur once. + if (m_shutdown_requested.exchange(true, std::memory_order::acq_rel) == false) { + if (m_thread_pool != nullptr) { + m_thread_pool->shutdown(); + } + + // Signal the event loop to stop asap, triggering the event fd is safe. + uint64_t value{1}; + auto written = ::write(m_shutdown_fd, &value, sizeof(value)); + (void)written; + + if (m_io_thread.joinable()) { + m_io_thread.join(); + } + } + } /** * Scans for completed coroutines and destroys them freeing up resources. This is also done on starting * new tasks but this allows the user to cleanup resources manually. One usage might be making sure fds * are cleaned up as soon as possible. */ - auto garbage_collect() noexcept -> void; + void garbage_collect() noexcept + { + auto* ptr = static_cast*>(m_owned_tasks); + ptr->garbage_collect(); + } private: /// The configuration options. @@ -332,12 +488,119 @@ namespace glz std::atomic m_shutdown_requested{false}; std::atomic m_io_processing{false}; - auto process_events_manual(std::chrono::milliseconds timeout) -> void; - auto process_events_dedicated_thread() -> void; - auto process_events_execute(std::chrono::milliseconds timeout) -> void; - static auto event_to_poll_status(uint32_t events) -> poll_status; + void process_events_manual(std::chrono::milliseconds timeout) + { + bool expected{false}; + if (m_io_processing.compare_exchange_strong(expected, true, std::memory_order::release, + std::memory_order::relaxed)) { + process_events_execute(timeout); + m_io_processing.exchange(false, std::memory_order::release); + } + } + + void process_events_dedicated_thread() + { + if (m_opts.on_io_thread_start_functor != nullptr) { + m_opts.on_io_thread_start_functor(); + } + + m_io_processing.exchange(true, std::memory_order::release); + // Execute tasks until stopped or there are no more tasks to complete. + while (!m_shutdown_requested.load(std::memory_order::acquire) || size() > 0) { + process_events_execute(m_default_timeout); + } + m_io_processing.exchange(false, std::memory_order::release); + + if (m_opts.on_io_thread_stop_functor != nullptr) { + m_opts.on_io_thread_stop_functor(); + } + } + + void process_events_execute(std::chrono::milliseconds timeout) + { + auto event_count = epoll_wait(m_epoll_fd, m_events.data(), m_max_events, timeout.count()); + if (event_count > 0) { + for (std::size_t i = 0; i < static_cast(event_count); ++i) { + epoll_event& event = m_events[i]; + void* handle_ptr = event.data.ptr; + + if (handle_ptr == m_timer_ptr) { + // Process all events that have timed out. + process_timeout_execute(); + } + else if (handle_ptr == m_schedule_ptr) { + // Process scheduled coroutines. + process_scheduled_execute_inline(); + } + else if (handle_ptr == m_shutdown_ptr) [[unlikely]] { + // Nothing to do , just needed to wake-up and smell the flowers + } + else { + // Individual poll task wake-up. + process_event_execute(static_cast(handle_ptr), + event_to_poll_status(event.events)); + } + } + } + + // Its important to not resume any handles until the full set is accounted for. If a timeout + // and an event for the same handle happen in the same epoll_wait() call then inline processing + // will destruct the poll_info object before the second event is handled. This is also possible + // with thread pool processing, but probably has an extremely low chance of occuring due to + // the thread switch required. If m_max_events == 1 this would be unnecessary. + + if (!m_handles_to_resume.empty()) { + if (m_opts.execution_strategy == execution_strategy_t::process_tasks_inline) { + for (auto& handle : m_handles_to_resume) { + handle.resume(); + } + } + else { + m_thread_pool->resume(m_handles_to_resume); + } + + m_handles_to_resume.clear(); + } + } + + static poll_status event_to_poll_status(uint32_t events) + { + if (events & EPOLLIN || events & EPOLLOUT) { + return poll_status::event; + } + else if (events & EPOLLERR) { + return poll_status::error; + } + else if (events & EPOLLRDHUP || events & EPOLLHUP) { + return poll_status::closed; + } + + throw std::runtime_error{"invalid epoll state"}; + } + + void process_scheduled_execute_inline() + { + std::vector> tasks{}; + { + // Acquire the entire list, and then reset it. + std::scoped_lock lk{m_scheduled_tasks_mutex}; + tasks.swap(m_scheduled_tasks); + + // Clear the schedule eventfd if this is a scheduled task. + eventfd_t value{0}; + eventfd_read(m_schedule_fd, &value); + + // Clear the in memory flag to reduce eventfd_* calls on scheduling. + m_schedule_fd_triggered.exchange(false, std::memory_order::release); + } + + // This set of handles can be safely resumed now since they do not have a corresponding timeout event. + for (auto& task : tasks) { + task.resume(); + } + m_size.fetch_sub(tasks.size(), std::memory_order::release); + } - auto process_scheduled_execute_inline() -> void; std::mutex m_scheduled_tasks_mutex{}; std::vector> m_scheduled_tasks{}; @@ -363,11 +626,150 @@ namespace glz std::array m_events{}; std::vector> m_handles_to_resume{}; - auto process_event_execute(poll_info* pi, poll_status status) -> void; - auto process_timeout_execute() -> void; + void process_event_execute(poll_info* pi, poll_status status) + { + if (!pi->m_processed) { + std::atomic_thread_fence(std::memory_order::acquire); + // Its possible the event and the timeout occurred in the same epoll, make sure only one + // is ever processed, the other is discarded. + pi->m_processed = true; + + // Given a valid fd always remove it from epoll so the next poll can blindly EPOLL_CTL_ADD. + if (pi->m_fd != -1) { + epoll_ctl(m_epoll_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); + } + + // Since this event triggered, remove its corresponding timeout if it has one. + if (pi->m_timer_pos.has_value()) { + remove_timer_token(pi->m_timer_pos.value()); + } + + pi->m_poll_status = status; + + while (pi->m_awaiting_coroutine == nullptr) { + std::atomic_thread_fence(std::memory_order::acquire); + } + + m_handles_to_resume.emplace_back(pi->m_awaiting_coroutine); + } + } + + void process_timeout_execute() + { + std::vector poll_infos{}; + auto now = clock::now(); + + { + std::scoped_lock lk{m_timed_events_mutex}; + while (!m_timed_events.empty()) { + auto first = m_timed_events.begin(); + auto [tp, pi] = *first; + + if (tp <= now) { + m_timed_events.erase(first); + poll_infos.emplace_back(pi); + } + else { + break; + } + } + } + + for (auto pi : poll_infos) { + if (!pi->m_processed) { + // Its possible the event and the timeout occurred in the same epoll, make sure only one + // is ever processed, the other is discarded. + pi->m_processed = true; + + // Since this timed out, remove its corresponding event if it has one. + if (pi->m_fd != -1) { + epoll_ctl(m_epoll_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); + } + + while (pi->m_awaiting_coroutine == nullptr) { + std::atomic_thread_fence(std::memory_order::acquire); + // std::cerr << "process_event_execute() has a nullptr event\n"; + } - auto add_timer_token(time_point tp, poll_info& pi) -> timed_events::iterator; - auto remove_timer_token(timed_events::iterator pos) -> void; - auto update_timeout(time_point now) -> void; + m_handles_to_resume.emplace_back(pi->m_awaiting_coroutine); + pi->m_poll_status = coro::poll_status::timeout; + } + } + + // Update the time to the next smallest time point, re-take the current now time + // since updating and resuming tasks could shift the time. + update_timeout(clock::now()); + } + + auto add_timer_token(time_point tp, poll_info& pi) -> timed_events::iterator + { + std::scoped_lock lk{m_timed_events_mutex}; + auto pos = m_timed_events.emplace(tp, &pi); + + // If this item was inserted as the smallest time point, update the timeout. + if (pos == m_timed_events.begin()) { + update_timeout(clock::now()); + } + + return pos; + } + + void remove_timer_token(timed_events::iterator pos) + { + { + std::scoped_lock lk{m_timed_events_mutex}; + auto is_first = (m_timed_events.begin() == pos); + + m_timed_events.erase(pos); + + // If this was the first item, update the timeout. It would be acceptable to just let it + // also fire the timeout as the event loop will ignore it since nothing will have timed + // out but it feels like the right thing to do to update it to the correct timeout value. + if (is_first) { + update_timeout(clock::now()); + } + } + } + + void update_timeout(time_point now) + { + if (!m_timed_events.empty()) { + auto& [tp, pi] = *m_timed_events.begin(); + + auto amount = tp - now; + + auto seconds = std::chrono::duration_cast(amount); + amount -= seconds; + auto nanoseconds = std::chrono::duration_cast(amount); + + // As a safeguard if both values end up as zero (or negative) then trigger the timeout + // immediately as zero disarms timerfd according to the man pages and negative values + // will result in an error return value. + if (seconds <= 0s) { + seconds = 0s; + if (nanoseconds <= 0ns) { + // just trigger "immediately"! + nanoseconds = 1ns; + } + } + + itimerspec ts{}; + ts.it_value.tv_sec = seconds.count(); + ts.it_value.tv_nsec = nanoseconds.count(); + + if (timerfd_settime(m_timer_fd, 0, &ts, nullptr) == -1) { + std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; + } + } + else { + // Setting these values to zero disables the timer. + itimerspec ts{}; + ts.it_value.tv_sec = 0; + ts.it_value.tv_nsec = 0; + if (timerfd_settime(m_timer_fd, 0, &ts, nullptr) == -1) { + std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; + } + } + } }; } From 2290426f3132bcba20f03d3ae9a4d359b8c47a66 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 09:36:04 -0500 Subject: [PATCH 144/309] cleaning --- include/glaze/coroutine/file_descriptor.hpp | 9 -- include/glaze/coroutine/io_scheduler.hpp | 67 ++++++------- include/glaze/coroutine/poll_info.hpp | 2 +- include/glaze/network/core.hpp | 104 ++++++++++++++++++++ 4 files changed, 133 insertions(+), 49 deletions(-) delete mode 100644 include/glaze/coroutine/file_descriptor.hpp create mode 100644 include/glaze/network/core.hpp diff --git a/include/glaze/coroutine/file_descriptor.hpp b/include/glaze/coroutine/file_descriptor.hpp deleted file mode 100644 index ac18cbea4e..0000000000 --- a/include/glaze/coroutine/file_descriptor.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// Glaze Library -// For the license information refer to glaze.hpp - -#pragma once - -namespace glz -{ - using fd_t = int; -} diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 16d75e8495..9dc5e9dda3 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -5,11 +5,11 @@ #pragma once -#include "glaze/coroutine/file_descriptor.hpp" #include "glaze/coroutine/poll.hpp" #include "glaze/coroutine/poll_info.hpp" #include "glaze/coroutine/task_container.hpp" #include "glaze/coroutine/thread_pool.hpp" +#include "glaze/network/core.hpp" #ifdef GLZ_FEATURE_NETWORKING #include "coro/net/socket.hpp" @@ -25,17 +25,6 @@ namespace glz { - // TODO: implement - using event_handle_t = int; - using poll_event_t = int; - - // Linux - // using poll_event_t = struct epoll_event; - // using event_handle_t = eventfd_t; - - // TODO: implement - int event_write(auto, auto) { return 0; } - struct io_scheduler final { using timed_events = poll_info::timed_events; @@ -92,24 +81,24 @@ namespace glz .on_thread_stop_functor = nullptr}, .execution_strategy = execution_strategy_t::process_tasks_on_thread_pool}) : m_opts(std::move(opts)), - m_epoll_fd(epoll_create1(EPOLL_CLOEXEC)), - m_shutdown_fd(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)), - m_timer_fd(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC)), - m_schedule_fd(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)), - m_owned_tasks(new coro::task_container(*this)) + event_fd(net::create_event_poll()), + shutdown_fd(net::create_shutdown_handle()), + timer_fd(net::create_timer_handle()), + schedule_fd(net::create_schedule_handle()), + m_owned_tasks(new glz::task_container(*this)) { if (opts.execution_strategy == execution_strategy_t::process_tasks_on_thread_pool) { m_thread_pool = std::make_unique(std::move(m_opts.pool)); } - epoll_event e{}; + net::poll_event_t e{}; e.events = EPOLLIN; e.data.ptr = const_cast(m_shutdown_ptr); - epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_shutdown_fd, &e); + epoll_ctl(event_fd, EPOLL_CTL_ADD, shutdown_fd, &e); e.data.ptr = const_cast(m_timer_ptr); - epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_timer_fd, &e); + epoll_ctl(event_fd, EPOLL_CTL_ADD, timer_fd, &e); e.data.ptr = const_cast(m_schedule_ptr); epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_schedule_fd, &e); @@ -133,17 +122,17 @@ namespace glz m_io_thread.join(); } - if (m_epoll_fd != -1) { - close(m_epoll_fd); - m_epoll_fd = -1; + if (event_fd != -1) { + close(event_fd); + event_fd = -1; } - if (m_timer_fd != -1) { - close(m_timer_fd); - m_timer_fd = -1; + if (timer_fd != -1) { + close(timer_fd); + timer_fd = -1; } - if (m_schedule_fd != -1) { - close(m_schedule_fd); - m_schedule_fd = -1; + if (schedule_fd != -1) { + close(schedule_fd); + schedule_fd = -1; } if (m_owned_tasks != nullptr) { @@ -196,7 +185,7 @@ namespace glz if (m_scheduler.m_schedule_fd_triggered.compare_exchange_strong( expected, true, std::memory_order::release, std::memory_order::relaxed)) { event_handle_t value{1}; - event_write(m_scheduler.m_schedule_fd, value); + event_write(m_scheduler.schedule_fd, value); } } else { @@ -395,7 +384,7 @@ namespace glz if (m_schedule_fd_triggered.compare_exchange_strong(expected, true, std::memory_order::release, std::memory_order::relaxed)) { event_handle_t value{1}; - event_write(m_schedule_fd, value); + event_write(schedule_fd, value); } return true; @@ -462,13 +451,13 @@ namespace glz options m_opts; /// The event loop epoll file descriptor. - fd_t m_epoll_fd{-1}; + fd_t event_fd{-1}; /// The event loop fd to trigger a shutdown. - fd_t m_shutdown_fd{-1}; + fd_t shutdown_fd{-1}; /// The event loop timer fd for timed events, e.g. yield_for() or scheduler_after(). - fd_t m_timer_fd{-1}; + fd_t timer_fd{-1}; /// The schedule file descriptor if the scheduler is in inline processing mode. - fd_t m_schedule_fd{-1}; + fd_t schedule_fd{-1}; std::atomic m_schedule_fd_triggered{false}; /// The number of tasks executing or awaiting events in this io scheduler. @@ -518,7 +507,7 @@ namespace glz void process_events_execute(std::chrono::milliseconds timeout) { - auto event_count = epoll_wait(m_epoll_fd, m_events.data(), m_max_events, timeout.count()); + auto event_count = epoll_wait(event_fd, m_events.data(), m_max_events, timeout.count()); if (event_count > 0) { for (std::size_t i = 0; i < static_cast(event_count); ++i) { epoll_event& event = m_events[i]; @@ -588,7 +577,7 @@ namespace glz // Clear the schedule eventfd if this is a scheduled task. eventfd_t value{0}; - eventfd_read(m_schedule_fd, &value); + eventfd_read(schedule_fd, &value); // Clear the in memory flag to reduce eventfd_* calls on scheduling. m_schedule_fd_triggered.exchange(false, std::memory_order::release); @@ -757,7 +746,7 @@ namespace glz ts.it_value.tv_sec = seconds.count(); ts.it_value.tv_nsec = nanoseconds.count(); - if (timerfd_settime(m_timer_fd, 0, &ts, nullptr) == -1) { + if (timerfd_settime(timer_fd, 0, &ts, nullptr) == -1) { std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; } } @@ -766,7 +755,7 @@ namespace glz itimerspec ts{}; ts.it_value.tv_sec = 0; ts.it_value.tv_nsec = 0; - if (timerfd_settime(m_timer_fd, 0, &ts, nullptr) == -1) { + if (timerfd_settime(timer_fd, 0, &ts, nullptr) == -1) { std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; } } diff --git a/include/glaze/coroutine/poll_info.hpp b/include/glaze/coroutine/poll_info.hpp index 68da5a8162..9ba11891f8 100644 --- a/include/glaze/coroutine/poll_info.hpp +++ b/include/glaze/coroutine/poll_info.hpp @@ -12,9 +12,9 @@ #include #include -#include "glaze/coroutine/file_descriptor.hpp" #include "glaze/coroutine/poll.hpp" #include "glaze/coroutine/time.hpp" +#include "glaze/network/core.hpp" namespace glz { diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp new file mode 100644 index 0000000000..6feb5a9ae5 --- /dev/null +++ b/include/glaze/network/core.hpp @@ -0,0 +1,104 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +#if defined(_WIN32) +#include +#include +#pragma comment(lib, "Ws2_32.lib") +#else +#include +#include +#include +#include +#include +#include +#endif + +#if __has_include() +#include +#endif + +#if defined(__APPLE__) +#include // for kqueue on macOS +#elif defined(__linux__) +#include // for epoll on Linux +#endif + +namespace glz::net +{ +#ifdef _WIN32 + using file_handle_t = unsigned int; + using ssize_t = int64_t; +#else + using file_handle_t = int; +#endif + +#if defined(__APPLE__) + using poll_event_t = struct kevent; +#elif defined(__linux__) + using poll_event_t = struct epoll_event; + using event_handle_t = eventfd_t; +#elif defined(_WIN32) +#endif + + // TODO: implement + int event_write(auto, auto) { return 0; } + + inline auto close_socket(file_handle_t fd) { +#ifdef _WIN32 + ::closesocket(fd); +#else + ::close(fd); +#endif + } + + inline auto event_close(file_handle_t fd) { +#ifdef _WIN32 + WSACloseEvent(fd); +#else + ::close(fd); +#endif + } + + inline file_handle_t create_event_poll() { +#if defined(__APPLE__) + return ::kqueue(); +#elif defined(__linux__) + return ::epoll_create1(EPOLL_CLOEXEC); +#elif defined(_WIN32) + return WSACreateEvent(); +#endif + } + + inline file_handle_t create_shutdown_handle() { +#if defined(__APPLE__) + return 0; +#elif defined(__linux__) + return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); +#elif defined(_WIN32) + return 0; +#endif + } + + inline file_handle_t create_timer_handle() { +#if defined(__APPLE__) + return 0; +#elif defined(__linux__) + return ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); +#elif defined(_WIN32) + return 0; +#endif + } + + inline file_handle_t create_schedule_handle() { +#if defined(__APPLE__) + return 0; +#elif defined(__linux__) + return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); +#elif defined(_WIN32) + return 0; +#endif + } +} From 58fa484149a535674781ecb2e1d85e298d44df8a Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 09:46:51 -0500 Subject: [PATCH 145/309] cleaning --- include/glaze/coroutine/concepts.hpp | 2 +- include/glaze/coroutine/io_scheduler.hpp | 28 ++++++++++++++---------- include/glaze/coroutine/poll.hpp | 18 +++++++-------- include/glaze/coroutine/poll_info.hpp | 2 +- include/glaze/network/core.hpp | 10 ++++++++- 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/include/glaze/coroutine/concepts.hpp b/include/glaze/coroutine/concepts.hpp index 41afaa878e..58ff0c10ad 100644 --- a/include/glaze/coroutine/concepts.hpp +++ b/include/glaze/coroutine/concepts.hpp @@ -38,7 +38,7 @@ namespace glz }; template - concept io_exceutor = executor;/* and requires(T t, std::coroutine_handle<> c, fd_t fd, glz::poll_op op, + concept io_exceutor = executor;/* and requires(T t, std::coroutine_handle<> c, net::file_handle_t fd, glz::poll_op op, std::chrono::milliseconds timeout) { { t.poll(fd, op, timeout) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 9dc5e9dda3..989e86be66 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -91,7 +91,8 @@ namespace glz m_thread_pool = std::make_unique(std::move(m_opts.pool)); } - net::poll_event_t e{}; + [[maybe_unused]] net::poll_event_t e{}; +#if defined(__linux__) e.events = EPOLLIN; e.data.ptr = const_cast(m_shutdown_ptr); @@ -102,6 +103,7 @@ namespace glz e.data.ptr = const_cast(m_schedule_ptr); epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_schedule_fd, &e); +#endif if (m_opts.thread_strategy == thread_strategy_t::spawn) { m_io_thread = std::thread([this]() { process_events_dedicated_thread(); }); @@ -136,7 +138,7 @@ namespace glz } if (m_owned_tasks != nullptr) { - delete static_cast*>(m_owned_tasks); + delete static_cast*>(m_owned_tasks); m_owned_tasks = nullptr; } } @@ -184,8 +186,10 @@ namespace glz bool expected{false}; if (m_scheduler.m_schedule_fd_triggered.compare_exchange_strong( expected, true, std::memory_order::release, std::memory_order::relaxed)) { - event_handle_t value{1}; +#if defined(__linux__) + eventfd_t value{1}; event_write(m_scheduler.schedule_fd, value); +#endif } } else { @@ -307,7 +311,7 @@ namespace glz * block indefinitely until the event triggers. * @return The result of the poll operation. */ - [[nodiscard]] auto poll(fd_t fd, glz::poll_op op, + [[nodiscard]] auto poll(net::file_handle_t fd, glz::poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) -> glz::task { @@ -383,8 +387,10 @@ namespace glz bool expected{false}; if (m_schedule_fd_triggered.compare_exchange_strong(expected, true, std::memory_order::release, std::memory_order::relaxed)) { - event_handle_t value{1}; +#if defined(__linux__) + eventfd_t value{1}; event_write(schedule_fd, value); +#endif } return true; @@ -426,7 +432,7 @@ namespace glz // Signal the event loop to stop asap, triggering the event fd is safe. uint64_t value{1}; - auto written = ::write(m_shutdown_fd, &value, sizeof(value)); + auto written = ::write(shutdown_fd, &value, sizeof(value)); (void)written; if (m_io_thread.joinable()) { @@ -442,7 +448,7 @@ namespace glz */ void garbage_collect() noexcept { - auto* ptr = static_cast*>(m_owned_tasks); + auto* ptr = static_cast*>(m_owned_tasks); ptr->garbage_collect(); } @@ -451,13 +457,13 @@ namespace glz options m_opts; /// The event loop epoll file descriptor. - fd_t event_fd{-1}; + net::file_handle_t event_fd{-1}; /// The event loop fd to trigger a shutdown. - fd_t shutdown_fd{-1}; + net::file_handle_t shutdown_fd{-1}; /// The event loop timer fd for timed events, e.g. yield_for() or scheduler_after(). - fd_t timer_fd{-1}; + net::file_handle_t timer_fd{-1}; /// The schedule file descriptor if the scheduler is in inline processing mode. - fd_t schedule_fd{-1}; + net::file_handle_t schedule_fd{-1}; std::atomic m_schedule_fd_triggered{false}; /// The number of tasks executing or awaiting events in this io scheduler. diff --git a/include/glaze/coroutine/poll.hpp b/include/glaze/coroutine/poll.hpp index 9df7c4e3de..0f70934734 100644 --- a/include/glaze/coroutine/poll.hpp +++ b/include/glaze/coroutine/poll.hpp @@ -7,21 +7,19 @@ #include +#include "glaze/network/core.hpp" + namespace glz -{ - // TODO: handle poll flags - constexpr auto poll_in = 0b00000001; - constexpr auto poll_out = 0b00000010; - +{ enum struct poll_op : uint64_t { - read = poll_in, - write = poll_out, - read_write = poll_in | poll_out + read = net::poll_in, + write = net::poll_out, + read_write = net::poll_in | net::poll_out }; - inline bool poll_op_readable(poll_op op) { return (uint64_t(op) & poll_in); } + inline bool poll_op_readable(poll_op op) { return (uint64_t(op) & net::poll_in); } - inline bool poll_op_writeable(poll_op op) { return (uint64_t(op) & poll_out); } + inline bool poll_op_writeable(poll_op op) { return (uint64_t(op) & net::poll_out); } std::string to_string(poll_op op) { diff --git a/include/glaze/coroutine/poll_info.hpp b/include/glaze/coroutine/poll_info.hpp index 9ba11891f8..f1c4e9e585 100644 --- a/include/glaze/coroutine/poll_info.hpp +++ b/include/glaze/coroutine/poll_info.hpp @@ -62,7 +62,7 @@ namespace glz /// The file descriptor being polled on. This is needed so that if the timeout occurs first then /// the event loop can immediately disable the event within epoll. - fd_t m_fd{-1}; + net::file_handle_t m_fd{-1}; /// The timeout's position in the timeout map. A poll() with no timeout or yield() this is empty. /// This is needed so that if the event occurs first then the event loop can immediately disable /// the timeout within epoll. diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 6feb5a9ae5..426d520204 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -39,7 +39,15 @@ namespace glz::net using poll_event_t = struct kevent; #elif defined(__linux__) using poll_event_t = struct epoll_event; - using event_handle_t = eventfd_t; +#elif defined(_WIN32) +#endif + +#if defined(__APPLE__) + constexpr auto poll_in = 0b00000001; + constexpr auto poll_out = 0b00000010; +#elif defined(__linux__) + constexpr auto poll_in = EPOLLIN; + constexpr auto poll_out = EPOLLOUT; #elif defined(_WIN32) #endif From 001230458218cb4d47b30ed0d6a69906dd17a4e2 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 10:11:28 -0500 Subject: [PATCH 146/309] building without implementations --- include/glaze/coroutine/io_scheduler.hpp | 87 +++++++++++++++++------- include/glaze/network/core.hpp | 22 ++++++ 2 files changed, 85 insertions(+), 24 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 989e86be66..a7fefd6e93 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -5,6 +5,15 @@ #pragma once +#ifndef GLZ_THROW_OR_ABORT +#if __cpp_exceptions +#define GLZ_THROW_OR_ABORT(EXC) (throw(EXC)) +#include +#else +#define GLZ_THROW_OR_ABORT(EXC) (std::abort()) +#endif +#endif + #include "glaze/coroutine/poll.hpp" #include "glaze/coroutine/poll_info.hpp" #include "glaze/coroutine/task_container.hpp" @@ -102,7 +111,7 @@ namespace glz epoll_ctl(event_fd, EPOLL_CTL_ADD, timer_fd, &e); e.data.ptr = const_cast(m_schedule_ptr); - epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_schedule_fd, &e); + epoll_ctl(event_fd, EPOLL_CTL_ADD, m_schedule_fd, &e); #endif if (m_opts.thread_strategy == thread_strategy_t::spawn) { @@ -221,7 +230,7 @@ namespace glz */ void schedule(glz::task&& task) { - auto* ptr = static_cast*>(m_owned_tasks); + auto* ptr = static_cast*>(m_owned_tasks); ptr->start(std::move(task)); } @@ -254,7 +263,7 @@ namespace glz */ [[nodiscard]] auto yield_for(std::chrono::milliseconds amount) -> glz::task { - if (amount <= 0ms) { + if (amount <= std::chrono::milliseconds(0)) { co_await schedule(); } else { @@ -267,7 +276,7 @@ namespace glz // it doesn't have a corresponding 'event' that can trigger, it always waits for // the timeout to occur before resuming. - detail::poll_info pi{}; + poll_info pi{}; add_timer_token(clock::now() + amount, pi); co_await pi; @@ -294,7 +303,7 @@ namespace glz auto amount = std::chrono::duration_cast(time - now); - detail::poll_info pi{}; + poll_info pi{}; add_timer_token(now + amount, pi); co_await pi; @@ -323,21 +332,23 @@ namespace glz // Whichever triggers first will delete the other to guarantee only one wins. // The resume token will be set by the scheduler to what the event turned out to be. - bool timeout_requested = (timeout > 0ms); + bool timeout_requested = (timeout > std::chrono::milliseconds(0)); - detail::poll_info pi{}; + poll_info pi{}; pi.m_fd = fd; if (timeout_requested) { pi.m_timer_pos = add_timer_token(clock::now() + timeout, pi); } - epoll_event e{}; - e.events = static_cast(op) | EPOLLONESHOT | EPOLLRDHUP; + [[maybe_unused]] net::poll_event_t e{}; +#if defined(__linux__) + e.events = uint32_t(op) | EPOLLONESHOT | EPOLLRDHUP; e.data.ptr = π - if (epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, fd, &e) == -1) { + if (epoll_ctl(event_fd, EPOLL_CTL_ADD, fd, &e) == -1) { std::cerr << "epoll ctl error on fd " << fd << "\n"; } +#endif // The event loop will 'clean-up' whichever event didn't win since the coroutine is scheduled // onto the thread poll its possible the other type of event could trigger while its waiting @@ -513,11 +524,25 @@ namespace glz void process_events_execute(std::chrono::milliseconds timeout) { - auto event_count = epoll_wait(event_fd, m_events.data(), m_max_events, timeout.count()); +#if defined(__APPLE__) + struct timespec tlimit + { + 0, timeout.count() * 1'000'000 + }; + auto event_count = ::kevent(event_fd, nullptr, 0, m_events.data(), int(m_events.size()), &tlimit); +#elif defined(__linux__) + auto event_count = poll_wait(event_fd, m_events.data(), m_max_events, timeout.count()); +#elif defined(_WIN32) +#endif + if (event_count > 0) { - for (std::size_t i = 0; i < static_cast(event_count); ++i) { - epoll_event& event = m_events[i]; + for (size_t i = 0; i < size_t(event_count); ++i) { + auto& event = m_events[i]; +#if defined(__linux__) void* handle_ptr = event.data.ptr; +#elif defined(__APPLE__) + void* handle_ptr = (void*)event.data; +#endif if (handle_ptr == m_timer_ptr) { // Process all events that have timed out. @@ -532,8 +557,10 @@ namespace glz } else { // Individual poll task wake-up. - process_event_execute(static_cast(handle_ptr), +#if defined(__linux__) + process_event_execute(static_cast(handle_ptr), event_to_poll_status(event.events)); +#endif } } } @@ -560,17 +587,17 @@ namespace glz static poll_status event_to_poll_status(uint32_t events) { - if (events & EPOLLIN || events & EPOLLOUT) { + if (events & net::poll_in || events & net::poll_out) { return poll_status::event; } - else if (events & EPOLLERR) { + else if (events & net::poll_error) { return poll_status::error; } - else if (events & EPOLLRDHUP || events & EPOLLHUP) { + else if (net::event_closed(events)) { return poll_status::closed; } - throw std::runtime_error{"invalid epoll state"}; + GLZ_THROW_OR_ABORT(std::runtime_error{"invalid epoll state"}); } void process_scheduled_execute_inline() @@ -582,8 +609,10 @@ namespace glz tasks.swap(m_scheduled_tasks); // Clear the schedule eventfd if this is a scheduled task. +#if defined(__linux__) eventfd_t value{0}; eventfd_read(schedule_fd, &value); +#endif // Clear the in memory flag to reduce eventfd_* calls on scheduling. m_schedule_fd_triggered.exchange(false, std::memory_order::release); @@ -617,8 +646,8 @@ namespace glz static const constexpr std::chrono::milliseconds m_default_timeout{1000}; static const constexpr std::chrono::milliseconds m_no_timeout{0}; - static const constexpr std::size_t m_max_events = 16; - std::array m_events{}; + static const constexpr size_t m_max_events = 16; + std::array m_events{}; std::vector> m_handles_to_resume{}; void process_event_execute(poll_info* pi, poll_status status) @@ -631,7 +660,9 @@ namespace glz // Given a valid fd always remove it from epoll so the next poll can blindly EPOLL_CTL_ADD. if (pi->m_fd != -1) { - epoll_ctl(m_epoll_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); +#if defined(__linux__) + epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); +#endif } // Since this event triggered, remove its corresponding timeout if it has one. @@ -651,7 +682,7 @@ namespace glz void process_timeout_execute() { - std::vector poll_infos{}; + std::vector poll_infos{}; auto now = clock::now(); { @@ -678,7 +709,9 @@ namespace glz // Since this timed out, remove its corresponding event if it has one. if (pi->m_fd != -1) { - epoll_ctl(m_epoll_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); +#if defined(__linux__) + epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); +#endif } while (pi->m_awaiting_coroutine == nullptr) { @@ -687,7 +720,7 @@ namespace glz } m_handles_to_resume.emplace_back(pi->m_awaiting_coroutine); - pi->m_poll_status = coro::poll_status::timeout; + pi->m_poll_status = poll_status::timeout; } } @@ -736,6 +769,8 @@ namespace glz auto seconds = std::chrono::duration_cast(amount); amount -= seconds; auto nanoseconds = std::chrono::duration_cast(amount); + + using namespace std::chrono_literals; // As a safeguard if both values end up as zero (or negative) then trigger the timeout // immediately as zero disarms timerfd according to the man pages and negative values @@ -748,6 +783,7 @@ namespace glz } } +#if defined(__linux__) itimerspec ts{}; ts.it_value.tv_sec = seconds.count(); ts.it_value.tv_nsec = nanoseconds.count(); @@ -755,8 +791,10 @@ namespace glz if (timerfd_settime(timer_fd, 0, &ts, nullptr) == -1) { std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; } +#endif } else { +#if defined(__linux__) // Setting these values to zero disables the timer. itimerspec ts{}; ts.it_value.tv_sec = 0; @@ -764,6 +802,7 @@ namespace glz if (timerfd_settime(timer_fd, 0, &ts, nullptr) == -1) { std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; } +#endif } } }; diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 426d520204..a15a7453ea 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -45,9 +45,11 @@ namespace glz::net #if defined(__APPLE__) constexpr auto poll_in = 0b00000001; constexpr auto poll_out = 0b00000010; + constexpr auto poll_error = 0b00000100; #elif defined(__linux__) constexpr auto poll_in = EPOLLIN; constexpr auto poll_out = EPOLLOUT; + constexpr auto poll_error = EPOLLERR; #elif defined(_WIN32) #endif @@ -70,6 +72,16 @@ namespace glz::net #endif } + inline auto poll_wait(auto&&... args) { +#if defined(__APPLE__) + return ::kevent(args...); +#elif defined(__linux__) + return ::epoll_wait(args...); +#elif defined(_WIN32) + return 0; +#endif + } + inline file_handle_t create_event_poll() { #if defined(__APPLE__) return ::kqueue(); @@ -107,6 +119,16 @@ namespace glz::net return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) return 0; +#endif + } + + inline bool event_closed([[maybe_unused]] uint32_t events) { +#if defined(__APPLE__) + return true; +#elif defined(__linux__) + return events & EPOLLRDHUP || events & EPOLLHUP; +#elif defined(_WIN32) + return true; #endif } } From 0300ea7a3912b2926365f14eb5ad6d20ed44202a Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 10:18:16 -0500 Subject: [PATCH 147/309] updates --- include/glaze/coroutine/io_scheduler.hpp | 2 +- include/glaze/coroutine/poll.hpp | 12 ++++-------- include/glaze/network/core.hpp | 16 ++++++++++++---- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index a7fefd6e93..85c968bee8 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -590,7 +590,7 @@ namespace glz if (events & net::poll_in || events & net::poll_out) { return poll_status::event; } - else if (events & net::poll_error) { + else if (net::poll_error(events)) { return poll_status::error; } else if (net::event_closed(events)) { diff --git a/include/glaze/coroutine/poll.hpp b/include/glaze/coroutine/poll.hpp index 0f70934734..58299524eb 100644 --- a/include/glaze/coroutine/poll.hpp +++ b/include/glaze/coroutine/poll.hpp @@ -11,16 +11,12 @@ namespace glz { - enum struct poll_op : uint64_t { - read = net::poll_in, - write = net::poll_out, - read_write = net::poll_in | net::poll_out + enum struct poll_op : uint32_t { + read, + write, + read_write }; - inline bool poll_op_readable(poll_op op) { return (uint64_t(op) & net::poll_in); } - - inline bool poll_op_writeable(poll_op op) { return (uint64_t(op) & net::poll_out); } - std::string to_string(poll_op op) { switch (op) { diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index a15a7453ea..720f6c429d 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -43,13 +43,11 @@ namespace glz::net #endif #if defined(__APPLE__) - constexpr auto poll_in = 0b00000001; - constexpr auto poll_out = 0b00000010; - constexpr auto poll_error = 0b00000100; + constexpr auto poll_in = EVFILT_READ; + constexpr auto poll_out = EVFILT_WRITE; #elif defined(__linux__) constexpr auto poll_in = EPOLLIN; constexpr auto poll_out = EPOLLOUT; - constexpr auto poll_error = EPOLLERR; #elif defined(_WIN32) #endif @@ -122,6 +120,16 @@ namespace glz::net #endif } + inline bool poll_error([[maybe_unused]] uint32_t events) { +#if defined(__APPLE__) + return true; +#elif defined(__linux__) + return events & EPOLLERR; +#elif defined(_WIN32) + return true; +#endif + } + inline bool event_closed([[maybe_unused]] uint32_t events) { #if defined(__APPLE__) return true; From 76e63d5b1e7d5d34220f420cfb01a56f8f184630 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 10:38:27 -0500 Subject: [PATCH 148/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 85c968bee8..992f27d3c3 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -101,6 +101,8 @@ namespace glz } [[maybe_unused]] net::poll_event_t e{}; + + [[maybe_unused]] bool event_setup_failed{}; #if defined(__linux__) e.events = EPOLLIN; @@ -111,7 +113,19 @@ namespace glz epoll_ctl(event_fd, EPOLL_CTL_ADD, timer_fd, &e); e.data.ptr = const_cast(m_schedule_ptr); - epoll_ctl(event_fd, EPOLL_CTL_ADD, m_schedule_fd, &e); + epoll_ctl(event_fd, EPOLL_CTL_ADD, schedule_fd, &e); +#elif defined(__APPLE__) + e.filter = EVFILT_READ; + e.flags = EV_ADD; + + EV_SET(&e, shutdown_fd, EVFILT_READ, EV_ADD, 0, 0, const_cast(m_shutdown_ptr)); + kevent(event_fd, &e, 1, NULL, 0, NULL); + + EV_SET(&e, timer_fd, EVFILT_TIMER, EV_ADD, 0, 0, const_cast(m_timer_ptr)); + kevent(event_fd, &e, 1, NULL, 0, NULL); + + EV_SET(&e, schedule_fd, EVFILT_READ, EV_ADD, 0, 0, const_cast(m_schedule_ptr)); + kevent(event_fd, &e, 1, NULL, 0, NULL); #endif if (m_opts.thread_strategy == thread_strategy_t::spawn) { @@ -197,7 +211,11 @@ namespace glz expected, true, std::memory_order::release, std::memory_order::relaxed)) { #if defined(__linux__) eventfd_t value{1}; - event_write(m_scheduler.schedule_fd, value); + eventfd_write(m_scheduler.schedule_fd, value); +#elif defined(__APPLE__) + net::file_handle_t fd = m_scheduler.schedule_fd; + uint64_t value = 1; + ::write(fd, &value, sizeof(value)); #endif } } From 3095d891b1ab69659bfe6d11fe824f40b1bcd6fd Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 11:28:08 -0500 Subject: [PATCH 149/309] updates --- include/glaze/coroutine/io_scheduler.hpp | 89 ++++++++++++++++-------- include/glaze/network/core.hpp | 3 + 2 files changed, 64 insertions(+), 28 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 992f27d3c3..8de0c8e21b 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -101,7 +101,7 @@ namespace glz } [[maybe_unused]] net::poll_event_t e{}; - + [[maybe_unused]] bool event_setup_failed{}; #if defined(__linux__) e.events = EPOLLIN; @@ -213,9 +213,8 @@ namespace glz eventfd_t value{1}; eventfd_write(m_scheduler.schedule_fd, value); #elif defined(__APPLE__) - net::file_handle_t fd = m_scheduler.schedule_fd; uint64_t value = 1; - ::write(fd, &value, sizeof(value)); + ::write(m_scheduler.schedule_fd, &value, sizeof(value)); #endif } } @@ -366,6 +365,11 @@ namespace glz if (epoll_ctl(event_fd, EPOLL_CTL_ADD, fd, &e) == -1) { std::cerr << "epoll ctl error on fd " << fd << "\n"; } +#elif defined(__APPLE__) + EV_SET(&e, event_fd, EVFILT_READ, EV_ADD | EV_ONESHOT | EV_EOF, 0, 0, &pi); + if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + std::cerr << "kqueue ctl error on fd " << fd << "\n"; + } #endif // The event loop will 'clean-up' whichever event didn't win since the coroutine is scheduled @@ -419,6 +423,9 @@ namespace glz #if defined(__linux__) eventfd_t value{1}; event_write(schedule_fd, value); +#elif defined(__APPLE__) + uint64_t value = 1; + ::write(schedule_fd, &value, sizeof(value)); #endif } @@ -549,10 +556,10 @@ namespace glz }; auto event_count = ::kevent(event_fd, nullptr, 0, m_events.data(), int(m_events.size()), &tlimit); #elif defined(__linux__) - auto event_count = poll_wait(event_fd, m_events.data(), m_max_events, timeout.count()); + auto event_count = poll_wait(event_fd, m_events.data(), m_max_events, timeout.count()); #elif defined(_WIN32) #endif - + if (event_count > 0) { for (size_t i = 0; i < size_t(event_count); ++i) { auto& event = m_events[i]; @@ -576,8 +583,9 @@ namespace glz else { // Individual poll task wake-up. #if defined(__linux__) - process_event_execute(static_cast(handle_ptr), - event_to_poll_status(event.events)); + process_event_execute(static_cast(handle_ptr), event_to_poll_status(event.events)); +#elif defined(__APPLE__) + process_event_execute(static_cast(handle_ptr), event_to_poll_status(event.flags)); #endif } } @@ -630,6 +638,9 @@ namespace glz #if defined(__linux__) eventfd_t value{0}; eventfd_read(schedule_fd, &value); +#elif defined(__APPLE__) + uint64_t value = 1; + ::read(schedule_fd, &value, sizeof(value)); #endif // Clear the in memory flag to reduce eventfd_* calls on scheduling. @@ -680,6 +691,12 @@ namespace glz if (pi->m_fd != -1) { #if defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); +#elif defined(__APPLE__) + struct kevent e; + EV_SET(&e, pi->m_fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + std::cerr << "Failed to remove fd " << pi->m_fd << " from kqueue\n"; + } #endif } @@ -729,6 +746,16 @@ namespace glz if (pi->m_fd != -1) { #if defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); +#elif defined(__APPLE__) + struct kevent e; + + // Initialize the kevent structure for deletion + EV_SET(&e, pi->m_fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + + // Remove the event from the kqueue + if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + std::cerr << "Failed to remove fd " << pi->m_fd << " from kqueue\n"; + } #endif } @@ -782,33 +809,33 @@ namespace glz if (!m_timed_events.empty()) { auto& [tp, pi] = *m_timed_events.begin(); - auto amount = tp - now; - - auto seconds = std::chrono::duration_cast(amount); - amount -= seconds; - auto nanoseconds = std::chrono::duration_cast(amount); - - using namespace std::chrono_literals; - - // As a safeguard if both values end up as zero (or negative) then trigger the timeout - // immediately as zero disarms timerfd according to the man pages and negative values - // will result in an error return value. - if (seconds <= 0s) { - seconds = 0s; - if (nanoseconds <= 0ns) { - // just trigger "immediately"! - nanoseconds = 1ns; - } - } - #if defined(__linux__) + size_t seconds{}; + size_t nanoseconds{1}; + if (tp > now) { + const auto time_left_ns = tp - now; + const auto s = std::chrono::duration_cast(time_left); + seconds = s.count(); + nanoseconds = std::chrono::duration_cast(time_left - s).count(); + } + itimerspec ts{}; - ts.it_value.tv_sec = seconds.count(); - ts.it_value.tv_nsec = nanoseconds.count(); + ts.it_value.tv_sec = seconds; + ts.it_value.tv_nsec = nanoseconds; if (timerfd_settime(timer_fd, 0, &ts, nullptr) == -1) { std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; } +#elif defined(__APPLE__) + size_t milliseconds{}; + if (tp > now) { + milliseconds = std::chrono::duration_cast(tp - now).count(); + } + struct kevent e; + EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, milliseconds, NULL); + if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + perror("kevent (update timer)"); + } #endif } else { @@ -820,6 +847,12 @@ namespace glz if (timerfd_settime(timer_fd, 0, &ts, nullptr) == -1) { std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; } +#elif defined(__APPLE__) + struct kevent e; + EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 0, NULL); + if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + perror("kevent (update timer)"); + } #endif } } diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 720f6c429d..237e379527 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include #endif #if __has_include() From 1ea39528654a2366fbc65849195ea23f9464ad5f Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 11:34:10 -0500 Subject: [PATCH 150/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 8de0c8e21b..648d2b9447 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -213,8 +213,8 @@ namespace glz eventfd_t value{1}; eventfd_write(m_scheduler.schedule_fd, value); #elif defined(__APPLE__) - uint64_t value = 1; - ::write(m_scheduler.schedule_fd, &value, sizeof(value)); + struct kevent e; + ::kevent(m_scheduler.schedule_fd, NULL, 0, &e, 1, NULL); #endif } } @@ -424,8 +424,8 @@ namespace glz eventfd_t value{1}; event_write(schedule_fd, value); #elif defined(__APPLE__) - uint64_t value = 1; - ::write(schedule_fd, &value, sizeof(value)); + struct kevent e; + ::kevent(schedule_fd, NULL, 0, &e, 1, NULL); #endif } @@ -639,8 +639,9 @@ namespace glz eventfd_t value{0}; eventfd_read(schedule_fd, &value); #elif defined(__APPLE__) - uint64_t value = 1; - ::read(schedule_fd, &value, sizeof(value)); + struct kevent change; + EV_SET(&change, schedule_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL); + kevent(schedule_fd, &change, 1, NULL, 0, NULL); #endif // Clear the in memory flag to reduce eventfd_* calls on scheduling. From 14646d76213bf5e94104fda98bd8dae05f39c93b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 11:40:11 -0500 Subject: [PATCH 151/309] Update poll_info.hpp --- include/glaze/coroutine/poll_info.hpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/include/glaze/coroutine/poll_info.hpp b/include/glaze/coroutine/poll_info.hpp index f1c4e9e585..c0637bd035 100644 --- a/include/glaze/coroutine/poll_info.hpp +++ b/include/glaze/coroutine/poll_info.hpp @@ -31,7 +31,7 @@ namespace glz * first one processed sets m_processed to true and any subsequent events in the same epoll batch * are effectively discarded. */ - struct poll_info + struct poll_info final { using timed_events = std::multimap; @@ -43,19 +43,17 @@ namespace glz auto operator=(const poll_info&) -> poll_info& = delete; auto operator=(poll_info&&) -> poll_info& = delete; - struct poll_awaiter + struct poll_awaiter final { - explicit poll_awaiter(poll_info& pi) noexcept : m_pi(pi) {} + poll_info& m_pi; - auto await_ready() const noexcept -> bool { return false; } - auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> void + bool await_ready() const noexcept { return false; } + void await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept { m_pi.m_awaiting_coroutine = awaiting_coroutine; std::atomic_thread_fence(std::memory_order::release); } - auto await_resume() noexcept -> poll_status { return m_pi.m_poll_status; } - - poll_info& m_pi; + poll_status await_resume() noexcept { return m_pi.m_poll_status; } }; auto operator co_await() noexcept -> poll_awaiter { return poll_awaiter{*this}; } @@ -66,9 +64,9 @@ namespace glz /// The timeout's position in the timeout map. A poll() with no timeout or yield() this is empty. /// This is needed so that if the event occurs first then the event loop can immediately disable /// the timeout within epoll. - std::optional m_timer_pos{std::nullopt}; + std::optional m_timer_pos{}; /// The awaiting coroutine for this poll info to resume upon event or timeout. - std::coroutine_handle<> m_awaiting_coroutine; + std::coroutine_handle<> m_awaiting_coroutine{}; /// The status of the poll operation. poll_status m_poll_status{glz::poll_status::error}; /// Did the timeout and event trigger at the same time on the same epoll_wait call? From 461edc72049ffaf3a7fcf3dd4961445d2eaf0cb0 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 11:43:46 -0500 Subject: [PATCH 152/309] Update poll_info.hpp --- include/glaze/coroutine/poll_info.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/glaze/coroutine/poll_info.hpp b/include/glaze/coroutine/poll_info.hpp index c0637bd035..32bb1dc67d 100644 --- a/include/glaze/coroutine/poll_info.hpp +++ b/include/glaze/coroutine/poll_info.hpp @@ -45,18 +45,18 @@ namespace glz struct poll_awaiter final { - poll_info& m_pi; + glz::poll_info& poll_info; bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept { - m_pi.m_awaiting_coroutine = awaiting_coroutine; + poll_info.m_awaiting_coroutine = awaiting_coroutine; std::atomic_thread_fence(std::memory_order::release); } - poll_status await_resume() noexcept { return m_pi.m_poll_status; } + poll_status await_resume() noexcept { return poll_info.m_poll_status; } }; - auto operator co_await() noexcept -> poll_awaiter { return poll_awaiter{*this}; } + poll_awaiter operator co_await() noexcept { return {*this}; } /// The file descriptor being polled on. This is needed so that if the timeout occurs first then /// the event loop can immediately disable the event within epoll. From 8c3beb22c46b7d87ed0e5826a0c06b1e1a7c058a Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 11:58:13 -0500 Subject: [PATCH 153/309] updates --- include/glaze/coroutine/io_scheduler.hpp | 33 +++++++++++++----------- include/glaze/network/core.hpp | 10 ------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 648d2b9447..4508521c84 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -118,14 +118,17 @@ namespace glz e.filter = EVFILT_READ; e.flags = EV_ADD; - EV_SET(&e, shutdown_fd, EVFILT_READ, EV_ADD, 0, 0, const_cast(m_shutdown_ptr)); - kevent(event_fd, &e, 1, NULL, 0, NULL); + e.data = intptr_t(m_shutdown_ptr); + EV_SET(&e, shutdown_fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); + ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); - EV_SET(&e, timer_fd, EVFILT_TIMER, EV_ADD, 0, 0, const_cast(m_timer_ptr)); - kevent(event_fd, &e, 1, NULL, 0, NULL); + e.data = intptr_t(m_timer_ptr); + EV_SET(&e, timer_fd, EVFILT_TIMER, EV_ADD, 0, 0, nullptr); + ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); - EV_SET(&e, schedule_fd, EVFILT_READ, EV_ADD, 0, 0, const_cast(m_schedule_ptr)); - kevent(event_fd, &e, 1, NULL, 0, NULL); + e.data = intptr_t(m_schedule_ptr); + EV_SET(&e, schedule_fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); + ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); #endif if (m_opts.thread_strategy == thread_strategy_t::spawn) { @@ -556,7 +559,7 @@ namespace glz }; auto event_count = ::kevent(event_fd, nullptr, 0, m_events.data(), int(m_events.size()), &tlimit); #elif defined(__linux__) - auto event_count = poll_wait(event_fd, m_events.data(), m_max_events, timeout.count()); + auto event_count = ::epoll_wait(event_fd, m_events.data(), m_max_events, timeout.count()); #elif defined(_WIN32) #endif @@ -566,7 +569,7 @@ namespace glz #if defined(__linux__) void* handle_ptr = event.data.ptr; #elif defined(__APPLE__) - void* handle_ptr = (void*)event.data; + void* handle_ptr = reinterpret_cast(event.data); #endif if (handle_ptr == m_timer_ptr) { @@ -660,23 +663,23 @@ namespace glz /// Tasks that have their ownership passed into the scheduler. This is a bit strange for now /// but the concept doesn't pass since io_scheduler isn't fully defined yet. - /// The type is coro::task_container* + /// The type is coro::task_container* /// Do not inline any functions that use this in the io_scheduler header, it can cause the linker /// to complain about "defined in discarded section" because it gets defined multiple times void* m_owned_tasks{nullptr}; - static constexpr const int m_shutdown_object{0}; + static constexpr int m_shutdown_object{0}; static constexpr const void* m_shutdown_ptr = &m_shutdown_object; - static constexpr const int m_timer_object{0}; + static constexpr int m_timer_object{0}; static constexpr const void* m_timer_ptr = &m_timer_object; - static constexpr const int m_schedule_object{0}; + static constexpr int m_schedule_object{0}; static constexpr const void* m_schedule_ptr = &m_schedule_object; - static const constexpr std::chrono::milliseconds m_default_timeout{1000}; - static const constexpr std::chrono::milliseconds m_no_timeout{0}; - static const constexpr size_t m_max_events = 16; + static constexpr std::chrono::milliseconds m_default_timeout{1000}; + static constexpr std::chrono::milliseconds m_no_timeout{0}; + static constexpr size_t m_max_events = 16; std::array m_events{}; std::vector> m_handles_to_resume{}; diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 237e379527..f0f871f435 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -73,16 +73,6 @@ namespace glz::net #endif } - inline auto poll_wait(auto&&... args) { -#if defined(__APPLE__) - return ::kevent(args...); -#elif defined(__linux__) - return ::epoll_wait(args...); -#elif defined(_WIN32) - return 0; -#endif - } - inline file_handle_t create_event_poll() { #if defined(__APPLE__) return ::kqueue(); From dceb53d59d82edc91a35f239b9216d67b78a82c3 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:01:41 -0500 Subject: [PATCH 154/309] removed garbage collections --- include/glaze/coroutine/io_scheduler.hpp | 39 +----------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 4508521c84..afc0b99d22 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -93,8 +93,7 @@ namespace glz event_fd(net::create_event_poll()), shutdown_fd(net::create_shutdown_handle()), timer_fd(net::create_timer_handle()), - schedule_fd(net::create_schedule_handle()), - m_owned_tasks(new glz::task_container(*this)) + schedule_fd(net::create_schedule_handle()) { if (opts.execution_strategy == execution_strategy_t::process_tasks_on_thread_pool) { m_thread_pool = std::make_unique(std::move(m_opts.pool)); @@ -162,11 +161,6 @@ namespace glz close(schedule_fd); schedule_fd = -1; } - - if (m_owned_tasks != nullptr) { - delete static_cast*>(m_owned_tasks); - m_owned_tasks = nullptr; - } } /** @@ -241,19 +235,6 @@ namespace glz */ auto schedule() -> schedule_operation { return schedule_operation{*this}; } - /** - * Schedules a task onto the io_scheduler and moves ownership of the task to the io_scheduler. - * Only void return type tasks can be scheduled in this manner since the task submitter will no - * longer have control over the scheduled task. - * @param task The task to execute on this io_scheduler. It's lifetime ownership will be transferred - * to this io_scheduler. - */ - void schedule(glz::task&& task) - { - auto* ptr = static_cast*>(m_owned_tasks); - ptr->start(std::move(task)); - } - /** * Schedules the current task to run after the given amount of time has elapsed. * @param amount The amount of time to wait before resuming execution of this task. @@ -480,17 +461,6 @@ namespace glz } } - /** - * Scans for completed coroutines and destroys them freeing up resources. This is also done on starting - * new tasks but this allows the user to cleanup resources manually. One usage might be making sure fds - * are cleaned up as soon as possible. - */ - void garbage_collect() noexcept - { - auto* ptr = static_cast*>(m_owned_tasks); - ptr->garbage_collect(); - } - private: /// The configuration options. options m_opts; @@ -661,13 +631,6 @@ namespace glz std::mutex m_scheduled_tasks_mutex{}; std::vector> m_scheduled_tasks{}; - /// Tasks that have their ownership passed into the scheduler. This is a bit strange for now - /// but the concept doesn't pass since io_scheduler isn't fully defined yet. - /// The type is coro::task_container* - /// Do not inline any functions that use this in the io_scheduler header, it can cause the linker - /// to complain about "defined in discarded section" because it gets defined multiple times - void* m_owned_tasks{nullptr}; - static constexpr int m_shutdown_object{0}; static constexpr const void* m_shutdown_ptr = &m_shutdown_object; From 7f6834ee012c4e4e9ccc033b175f2bf60ff02718 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:09:48 -0500 Subject: [PATCH 155/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index afc0b99d22..4cc3bed094 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -196,7 +196,7 @@ namespace glz auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> void { if (m_scheduler.m_opts.execution_strategy == execution_strategy_t::process_tasks_inline) { - m_scheduler.m_size.fetch_add(1, std::memory_order::release); + m_scheduler.n_active_tasks.fetch_add(1, std::memory_order::release); { std::scoped_lock lk{m_scheduler.m_scheduled_tasks_mutex}; m_scheduler.m_scheduled_tasks.emplace_back(awaiting_coroutine); @@ -271,7 +271,7 @@ namespace glz // Yield/timeout tasks are considered live in the scheduler and must be accounted for. Note // that if the user gives an invalid amount and schedule() is directly called it will account // for the scheduled task there. - m_size.fetch_add(1, std::memory_order::release); + n_active_tasks.fetch_add(1, std::memory_order::release); // Yielding does not requiring setting the timer position on the poll info since // it doesn't have a corresponding 'event' that can trigger, it always waits for @@ -281,7 +281,7 @@ namespace glz add_timer_token(clock::now() + amount, pi); co_await pi; - m_size.fetch_sub(1, std::memory_order::release); + n_active_tasks.fetch_sub(1, std::memory_order::release); } co_return; } @@ -300,7 +300,7 @@ namespace glz co_await schedule(); } else { - m_size.fetch_add(1, std::memory_order::release); + n_active_tasks.fetch_add(1, std::memory_order::release); auto amount = std::chrono::duration_cast(time - now); @@ -308,7 +308,7 @@ namespace glz add_timer_token(now + amount, pi); co_await pi; - m_size.fetch_sub(1, std::memory_order::release); + n_active_tasks.fetch_sub(1, std::memory_order::release); } co_return; } @@ -327,7 +327,7 @@ namespace glz { // Because the size will drop when this coroutine suspends every poll needs to undo the subtraction // on the number of active tasks in the scheduler. When this task is resumed by the event loop. - m_size.fetch_add(1, std::memory_order::release); + n_active_tasks.fetch_add(1, std::memory_order::release); // Setup two events, a timeout event and the actual poll for op event. // Whichever triggers first will delete the other to guarantee only one wins. @@ -360,7 +360,7 @@ namespace glz // onto the thread poll its possible the other type of event could trigger while its waiting // to execute again, thus restarting the coroutine twice, that would be quite bad. auto result = co_await pi; - m_size.fetch_sub(1, std::memory_order::release); + n_active_tasks.fetch_sub(1, std::memory_order::release); co_return result; } @@ -426,10 +426,10 @@ namespace glz auto size() const noexcept -> std::size_t { if (m_opts.execution_strategy == execution_strategy_t::process_tasks_inline) { - return m_size.load(std::memory_order::acquire); + return n_active_tasks.load(std::memory_order::acquire); } else { - return m_size.load(std::memory_order::acquire) + m_thread_pool->size(); + return n_active_tasks.load(std::memory_order::acquire) + m_thread_pool->size(); } } @@ -476,7 +476,7 @@ namespace glz std::atomic m_schedule_fd_triggered{false}; /// The number of tasks executing or awaiting events in this io scheduler. - std::atomic m_size{0}; + std::atomic n_active_tasks{0}; /// The background io worker threads. std::thread m_io_thread; @@ -625,7 +625,7 @@ namespace glz for (auto& task : tasks) { task.resume(); } - m_size.fetch_sub(tasks.size(), std::memory_order::release); + n_active_tasks.fetch_sub(tasks.size(), std::memory_order::release); } std::mutex m_scheduled_tasks_mutex{}; From e4d93466b2de34b1d5d9930a56f2b2f6b6f52823 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:12:09 -0500 Subject: [PATCH 156/309] cleaning --- include/glaze/coroutine/io_scheduler.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 4cc3bed094..a102bb4ed3 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -65,9 +65,9 @@ namespace glz /// Should the io scheduler spawn a dedicated event processor? thread_strategy_t thread_strategy{thread_strategy_t::spawn}; /// If spawning a dedicated event processor a functor to call upon that thread starting. - std::function on_io_thread_start_functor{nullptr}; + std::function on_io_thread_start_functor{}; /// If spawning a dedicated event processor a functor to call upon that thread stopping. - std::function on_io_thread_stop_functor{nullptr}; + std::function on_io_thread_stop_functor{}; /// Thread pool options for the task processor threads. See thread pool for more details. thread_pool::options pool{ .thread_count = ((std::thread::hardware_concurrency() > 1) ? (std::thread::hardware_concurrency() - 1) : 1), @@ -423,7 +423,7 @@ namespace glz /** * @return The number of tasks waiting in the task queue + the executing tasks. */ - auto size() const noexcept -> std::size_t + size_t size() const noexcept { if (m_opts.execution_strategy == execution_strategy_t::process_tasks_inline) { return n_active_tasks.load(std::memory_order::acquire); @@ -436,7 +436,7 @@ namespace glz /** * @return True if the task queue is empty and zero tasks are currently executing. */ - auto empty() const noexcept -> bool { return size() == 0; } + bool empty() const noexcept { return size() == 0; } /** * Starts the shutdown of the io scheduler. All currently executing and pending tasks will complete @@ -446,7 +446,7 @@ namespace glz { // Only allow shutdown to occur once. if (m_shutdown_requested.exchange(true, std::memory_order::acq_rel) == false) { - if (m_thread_pool != nullptr) { + if (m_thread_pool) { m_thread_pool->shutdown(); } From 311ab6e74dc15676d25f9a105c9bd2989f7d00a5 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:14:07 -0500 Subject: [PATCH 157/309] cleaning --- include/glaze/coroutine/io_scheduler.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index a102bb4ed3..b98fd55c81 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -26,7 +26,6 @@ #include #include -#include #include #include #include @@ -648,7 +647,7 @@ namespace glz void process_event_execute(poll_info* pi, poll_status status) { - if (!pi->m_processed) { + if (not pi->m_processed) { std::atomic_thread_fence(std::memory_order::acquire); // Its possible the event and the timeout occurred in the same epoll, make sure only one // is ever processed, the other is discarded. From 24cbfb9fb4619d3d902293f5a80591bf46b6a39e Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:15:13 -0500 Subject: [PATCH 158/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index b98fd55c81..1891d92e45 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -320,9 +320,8 @@ namespace glz * block indefinitely until the event triggers. * @return The result of the poll operation. */ - [[nodiscard]] auto poll(net::file_handle_t fd, glz::poll_op op, + [[nodiscard]] glz::task poll(net::file_handle_t fd, glz::poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) - -> glz::task { // Because the size will drop when this coroutine suspends every poll needs to undo the subtraction // on the number of active tasks in the scheduler. When this task is resumed by the event loop. From 3950b5aeb2e22b7594270d63f94aa6dca7488cf3 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:17:44 -0500 Subject: [PATCH 159/309] removed time.hpp --- include/glaze/coroutine/io_scheduler.hpp | 2 ++ include/glaze/coroutine/poll_info.hpp | 3 +-- include/glaze/coroutine/time.hpp | 10 ---------- 3 files changed, 3 insertions(+), 12 deletions(-) delete mode 100644 include/glaze/coroutine/time.hpp diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 1891d92e45..cf5a64c960 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -35,6 +35,8 @@ namespace glz { struct io_scheduler final { + using clock = std::chrono::steady_clock; + using time_point = clock::time_point; using timed_events = poll_info::timed_events; struct schedule_operation; diff --git a/include/glaze/coroutine/poll_info.hpp b/include/glaze/coroutine/poll_info.hpp index 32bb1dc67d..ed5dcb2697 100644 --- a/include/glaze/coroutine/poll_info.hpp +++ b/include/glaze/coroutine/poll_info.hpp @@ -13,7 +13,6 @@ #include #include "glaze/coroutine/poll.hpp" -#include "glaze/coroutine/time.hpp" #include "glaze/network/core.hpp" namespace glz @@ -33,7 +32,7 @@ namespace glz */ struct poll_info final { - using timed_events = std::multimap; + using timed_events = std::multimap; poll_info() = default; ~poll_info() = default; diff --git a/include/glaze/coroutine/time.hpp b/include/glaze/coroutine/time.hpp deleted file mode 100644 index 3d133b2e6d..0000000000 --- a/include/glaze/coroutine/time.hpp +++ /dev/null @@ -1,10 +0,0 @@ -// Glaze Library -// For the license information refer to glaze.hpp - -#pragma once - -namespace glz -{ - using clock = std::chrono::steady_clock; - using time_point = clock::time_point; -} From 5c9673ca56181652d93c8548819d963900ef95bf Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:19:18 -0500 Subject: [PATCH 160/309] cleaning --- include/glaze/coroutine/io_scheduler.hpp | 2 +- include/glaze/coroutine/latch.hpp | 2 +- include/glaze/coroutine/task_container.hpp | 30 +++++++++++----------- include/glaze/coroutine/thread_pool.hpp | 12 ++++----- include/glaze/coroutine/when_all.hpp | 4 +-- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index cf5a64c960..09453798d2 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -174,7 +174,7 @@ namespace glz * indefinitely until an event happens. * @param return The number of tasks currently executing or waiting to execute. */ - std::size_t process_events(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) + size_t process_events(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { process_events_manual(timeout); return size(); diff --git a/include/glaze/coroutine/latch.hpp b/include/glaze/coroutine/latch.hpp index 8653692188..6dfa921045 100644 --- a/include/glaze/coroutine/latch.hpp +++ b/include/glaze/coroutine/latch.hpp @@ -43,7 +43,7 @@ namespace glz /** * @return The number of tasks this latch is still waiting to complete. */ - auto remaining() const noexcept -> std::size_t { return m_count.load(std::memory_order::acquire); } + size_t remaining() const noexcept { return m_count.load(std::memory_order::acquire); } /** * If the latch counter goes to zero then the task awaiting the latch is resumed. diff --git a/include/glaze/coroutine/task_container.hpp b/include/glaze/coroutine/task_container.hpp index 854cc204b4..1a0e12ab00 100644 --- a/include/glaze/coroutine/task_container.hpp +++ b/include/glaze/coroutine/task_container.hpp @@ -35,7 +35,7 @@ namespace glz struct options { /// The number of task spots to reserve space for upon creating the container. - std::size_t reserve_size{8}; + size_t reserve_size{8}; /// The growth factor for task space in the container when capacity is full. double growth_factor{2}; }; @@ -97,7 +97,7 @@ namespace glz } // Reserve a free task index - std::size_t index = m_free_task_indices.front(); + size_t index = m_free_task_indices.front(); m_free_task_indices.pop(); // We've reserved the slot, we can release the lock. @@ -115,7 +115,7 @@ namespace glz * the task container for newly stored tasks. * @return The number of tasks that were deleted. */ - auto garbage_collect() -> std::size_t + auto garbage_collect() -> size_t { std::scoped_lock lk{m_mutex}; return gc_internal(); @@ -124,7 +124,7 @@ namespace glz /** * @return The number of active tasks in the container. */ - auto size() const -> std::size_t { return m_size.load(std::memory_order::relaxed); } + auto size() const -> size_t { return m_size.load(std::memory_order::relaxed); } /** * @return True if there are no active tasks in the container. @@ -134,7 +134,7 @@ namespace glz /** * @return The capacity of this task manager before it will need to grow in size. */ - auto capacity() const -> std::size_t + auto capacity() const -> size_t { std::atomic_thread_fence(std::memory_order::acquire); return m_tasks.size(); @@ -163,8 +163,8 @@ namespace glz auto grow() -> void { // Save an index at the current last item. - std::size_t new_size = m_tasks.size() * m_growth_factor; - for (std::size_t i = m_tasks.size(); i < new_size; ++i) { + size_t new_size = m_tasks.size() * m_growth_factor; + for (size_t i = m_tasks.size(); i < new_size; ++i) { m_free_task_indices.emplace(i); } m_tasks.resize(new_size); @@ -173,9 +173,9 @@ namespace glz /** * Internal GC call, expects the public function to lock. */ - auto gc_internal() -> std::size_t + auto gc_internal() -> size_t { - std::size_t deleted{0}; + size_t deleted{0}; auto pos = std::begin(m_tasks_to_delete); while (pos != std::end(m_tasks_to_delete)) { // Skip tasks that are still running or have yet to start. @@ -208,7 +208,7 @@ namespace glz * @param index The index where the task data will be stored in the task manager. * @return The user's task wrapped in a self cleanup task. */ - auto make_cleanup_task(task user_task, std::size_t index) -> glz::task + auto make_cleanup_task(task user_task, size_t index) -> glz::task { // Immediately move the task onto the executor. co_await m_executor_ptr->schedule(); @@ -247,13 +247,13 @@ namespace glz /// thread pools for indeterminate lifetime requests. std::mutex m_mutex{}; /// The number of alive tasks. - std::atomic m_size{}; + std::atomic m_size{}; /// Maintains the lifetime of the tasks until they are completed. std::vector> m_tasks{}; /// The full set of free indicies into `m_tasks`. - std::queue m_free_task_indices{}; + std::queue m_free_task_indices{}; /// The set of tasks that have completed and need to be deleted. - std::list m_tasks_to_delete{}; + std::list m_tasks_to_delete{}; /// The amount to grow the containers by when all spaces are taken. double m_growth_factor{}; /// The executor to schedule tasks that have just started. @@ -272,10 +272,10 @@ namespace glz init(opts.reserve_size); } - auto init(std::size_t reserve_size) -> void + auto init(size_t reserve_size) -> void { m_tasks.resize(reserve_size); - for (std::size_t i = 0; i < reserve_size; ++i) { + for (size_t i = 0; i < reserve_size; ++i) { m_free_task_indices.emplace(i); } } diff --git a/include/glaze/coroutine/thread_pool.hpp b/include/glaze/coroutine/thread_pool.hpp index c980b2691d..def1e52e2c 100644 --- a/include/glaze/coroutine/thread_pool.hpp +++ b/include/glaze/coroutine/thread_pool.hpp @@ -94,10 +94,10 @@ namespace glz uint32_t thread_count = std::thread::hardware_concurrency(); /// Functor to call on each executor thread upon starting execution. The parameter is the /// thread's ID assigned to it by the thread pool. - std::function on_thread_start_functor = nullptr; + std::function on_thread_start_functor = nullptr; /// Functor to call on each executor thread upon stopping execution. The parameter is the /// thread's ID assigned to it by the thread pool. - std::function on_thread_stop_functor = nullptr; + std::function on_thread_stop_functor = nullptr; }; /** @@ -260,7 +260,7 @@ namespace glz /** * @return The number of tasks waiting in the task queue + the executing tasks. */ - auto size() const noexcept -> std::size_t { return m_size.load(std::memory_order::acquire); } + auto size() const noexcept -> size_t { return m_size.load(std::memory_order::acquire); } /** * @return True if the task queue is empty and zero tasks are currently executing. @@ -270,7 +270,7 @@ namespace glz /** * @return The number of tasks waiting in the task queue to be executed. */ - auto queue_size() const noexcept -> std::size_t + auto queue_size() const noexcept -> size_t { std::atomic_thread_fence(std::memory_order::acquire); return m_queue.size(); @@ -296,7 +296,7 @@ namespace glz * Each background thread runs from this function. * @param idx The executor's idx for internal data structure accesses. */ - void executor(std::size_t idx) + void executor(size_t idx) { if (m_opts.on_thread_start_functor != nullptr) { m_opts.on_thread_start_functor(idx); @@ -360,7 +360,7 @@ namespace glz } /// The number of tasks in the queue + currently executing. - std::atomic m_size{0}; + std::atomic m_size{0}; /// Has the thread pool been requested to shut down? std::atomic m_shutdown_requested{false}; }; diff --git a/include/glaze/coroutine/when_all.hpp b/include/glaze/coroutine/when_all.hpp index d91e89eadc..2e986e91e3 100644 --- a/include/glaze/coroutine/when_all.hpp +++ b/include/glaze/coroutine/when_all.hpp @@ -20,7 +20,7 @@ namespace glz { struct when_all_latch { - when_all_latch(std::size_t count) noexcept : m_count(count + 1) {} + when_all_latch(size_t count) noexcept : m_count(count + 1) {} when_all_latch(const when_all_latch&) = delete; when_all_latch(when_all_latch&& other) @@ -59,7 +59,7 @@ namespace glz private: /// The number of tasks that are being waited on. - std::atomic m_count; + std::atomic m_count; /// The when_all_task awaiting to be resumed upon all task completions. std::coroutine_handle<> m_awaiting_coroutine{nullptr}; }; From 0f9e33159ddc6e056a91c6c9b39fdbebb82bede3 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:22:48 -0500 Subject: [PATCH 161/309] net::invalid_file_handle --- include/glaze/coroutine/io_scheduler.hpp | 8 ++++---- include/glaze/coroutine/latch.hpp | 2 +- include/glaze/coroutine/poll_info.hpp | 2 +- include/glaze/network/core.hpp | 2 ++ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 09453798d2..34143c1285 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -466,13 +466,13 @@ namespace glz options m_opts; /// The event loop epoll file descriptor. - net::file_handle_t event_fd{-1}; + net::file_handle_t event_fd{net::invalid_file_handle}; /// The event loop fd to trigger a shutdown. - net::file_handle_t shutdown_fd{-1}; + net::file_handle_t shutdown_fd{net::invalid_file_handle}; /// The event loop timer fd for timed events, e.g. yield_for() or scheduler_after(). - net::file_handle_t timer_fd{-1}; + net::file_handle_t timer_fd{net::invalid_file_handle}; /// The schedule file descriptor if the scheduler is in inline processing mode. - net::file_handle_t schedule_fd{-1}; + net::file_handle_t schedule_fd{net::invalid_file_handle}; std::atomic m_schedule_fd_triggered{false}; /// The number of tasks executing or awaiting events in this io scheduler. diff --git a/include/glaze/coroutine/latch.hpp b/include/glaze/coroutine/latch.hpp index 6dfa921045..53b1f8829e 100644 --- a/include/glaze/coroutine/latch.hpp +++ b/include/glaze/coroutine/latch.hpp @@ -28,7 +28,7 @@ namespace glz * @param count The number of tasks to wait to complete, if this is zero or negative then the * latch starts 'completed' immediately and execution is resumed with no suspension. */ - latch(std::int64_t count) noexcept : m_count(count), m_event(count <= 0) {} + latch(int64_t count) noexcept : m_count(count), m_event(count <= 0) {} latch(const latch&) = delete; latch(latch&&) = delete; diff --git a/include/glaze/coroutine/poll_info.hpp b/include/glaze/coroutine/poll_info.hpp index ed5dcb2697..7e94d6f07d 100644 --- a/include/glaze/coroutine/poll_info.hpp +++ b/include/glaze/coroutine/poll_info.hpp @@ -59,7 +59,7 @@ namespace glz /// The file descriptor being polled on. This is needed so that if the timeout occurs first then /// the event loop can immediately disable the event within epoll. - net::file_handle_t m_fd{-1}; + net::file_handle_t m_fd{net::invalid_file_handle}; /// The timeout's position in the timeout map. A poll() with no timeout or yield() this is empty. /// This is needed so that if the event occurs first then the event loop can immediately disable /// the timeout within epoll. diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index f0f871f435..c1162897eb 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -33,9 +33,11 @@ namespace glz::net { #ifdef _WIN32 using file_handle_t = unsigned int; + constexpr unsigned int invalid_file_handle = 0; using ssize_t = int64_t; #else using file_handle_t = int; + constexpr int invalid_file_handle = -1; #endif #if defined(__APPLE__) From e7f0e6a5c3cd87c9eb5c0b4376978f1355f21239 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:28:42 -0500 Subject: [PATCH 162/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 34143c1285..16eedf4e5e 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -373,9 +373,9 @@ namespace glz * block indefinitely until the event triggers. * @return THe result of the poll operation. */ - [[nodiscard]] auto poll(const net::socket& sock, coro::poll_op op, + [[nodiscard]] auto poll(const net::socket& sock, glz::poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) - -> coro::task + -> glz::task { return poll(sock.native_handle(), op, timeout); } @@ -642,8 +642,8 @@ namespace glz static constexpr std::chrono::milliseconds m_default_timeout{1000}; static constexpr std::chrono::milliseconds m_no_timeout{0}; - static constexpr size_t m_max_events = 16; - std::array m_events{}; + static constexpr size_t max_events = 16; + std::array m_events{}; std::vector> m_handles_to_resume{}; void process_event_execute(poll_info* pi, poll_status status) From f4922a12441a8dfad4c97ebe0b6c9340e7b2f7fb Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:33:24 -0500 Subject: [PATCH 163/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 29 ++++++++++++------------ 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 16eedf4e5e..d85154d356 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -335,11 +335,11 @@ namespace glz bool timeout_requested = (timeout > std::chrono::milliseconds(0)); - poll_info pi{}; - pi.m_fd = fd; + glz::poll_info poll_info{}; + poll_info.m_fd = fd; if (timeout_requested) { - pi.m_timer_pos = add_timer_token(clock::now() + timeout, pi); + poll_info.m_timer_pos = add_timer_token(clock::now() + timeout, poll_info); } [[maybe_unused]] net::poll_event_t e{}; @@ -350,7 +350,8 @@ namespace glz std::cerr << "epoll ctl error on fd " << fd << "\n"; } #elif defined(__APPLE__) - EV_SET(&e, event_fd, EVFILT_READ, EV_ADD | EV_ONESHOT | EV_EOF, 0, 0, &pi); + e.data = intptr_t(&poll_info); + EV_SET(&e, event_fd, EVFILT_READ, EV_ADD | EV_ONESHOT | EV_EOF, 0, 0, nullptr); if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { std::cerr << "kqueue ctl error on fd " << fd << "\n"; } @@ -359,7 +360,7 @@ namespace glz // The event loop will 'clean-up' whichever event didn't win since the coroutine is scheduled // onto the thread poll its possible the other type of event could trigger while its waiting // to execute again, thus restarting the coroutine twice, that would be quite bad. - auto result = co_await pi; + auto result = co_await poll_info; n_active_tasks.fetch_sub(1, std::memory_order::release); co_return result; } @@ -613,8 +614,8 @@ namespace glz eventfd_read(schedule_fd, &value); #elif defined(__APPLE__) struct kevent change; - EV_SET(&change, schedule_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL); - kevent(schedule_fd, &change, 1, NULL, 0, NULL); + EV_SET(&change, schedule_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, nullptr); + kevent(schedule_fd, &change, 1, nullptr, 0, nullptr); #endif // Clear the in memory flag to reduce eventfd_* calls on scheduling. @@ -660,8 +661,8 @@ namespace glz epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); #elif defined(__APPLE__) struct kevent e; - EV_SET(&e, pi->m_fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); - if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + EV_SET(&e, pi->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "Failed to remove fd " << pi->m_fd << " from kqueue\n"; } #endif @@ -717,10 +718,10 @@ namespace glz struct kevent e; // Initialize the kevent structure for deletion - EV_SET(&e, pi->m_fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + EV_SET(&e, pi->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); // Remove the event from the kqueue - if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "Failed to remove fd " << pi->m_fd << " from kqueue\n"; } #endif @@ -799,8 +800,8 @@ namespace glz milliseconds = std::chrono::duration_cast(tp - now).count(); } struct kevent e; - EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, milliseconds, NULL); - if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, milliseconds, nullptr); + if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { perror("kevent (update timer)"); } #endif @@ -816,7 +817,7 @@ namespace glz } #elif defined(__APPLE__) struct kevent e; - EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 0, NULL); + EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 0, nullptr); if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { perror("kevent (update timer)"); } From 0eafdb259058e50a2813232906a4d58f6256e290 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:39:24 -0500 Subject: [PATCH 164/309] udata --- include/glaze/coroutine/io_scheduler.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index d85154d356..2bbd77fb90 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -118,15 +118,15 @@ namespace glz e.filter = EVFILT_READ; e.flags = EV_ADD; - e.data = intptr_t(m_shutdown_ptr); + e.udata = const_cast(m_shutdown_ptr); EV_SET(&e, shutdown_fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); - e.data = intptr_t(m_timer_ptr); + e.udata = const_cast(m_timer_ptr); EV_SET(&e, timer_fd, EVFILT_TIMER, EV_ADD, 0, 0, nullptr); ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); - e.data = intptr_t(m_schedule_ptr); + e.udata = const_cast(m_schedule_ptr); EV_SET(&e, schedule_fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); #endif @@ -350,8 +350,8 @@ namespace glz std::cerr << "epoll ctl error on fd " << fd << "\n"; } #elif defined(__APPLE__) - e.data = intptr_t(&poll_info); - EV_SET(&e, event_fd, EVFILT_READ, EV_ADD | EV_ONESHOT | EV_EOF, 0, 0, nullptr); + e.udata = &poll_info; + EV_SET(&e, fd, EVFILT_READ, EV_ADD | EV_ONESHOT | EV_EOF, 0, 0, nullptr); if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { std::cerr << "kqueue ctl error on fd " << fd << "\n"; } @@ -540,7 +540,7 @@ namespace glz #if defined(__linux__) void* handle_ptr = event.data.ptr; #elif defined(__APPLE__) - void* handle_ptr = reinterpret_cast(event.data); + void* handle_ptr = event.udata; #endif if (handle_ptr == m_timer_ptr) { From 4685ef1240e0cde6213c694148166d71568cd324 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:44:04 -0500 Subject: [PATCH 165/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 2bbd77fb90..c4b23116f8 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -345,7 +345,7 @@ namespace glz [[maybe_unused]] net::poll_event_t e{}; #if defined(__linux__) e.events = uint32_t(op) | EPOLLONESHOT | EPOLLRDHUP; - e.data.ptr = π + e.data.ptr = &poll_info; if (epoll_ctl(event_fd, EPOLL_CTL_ADD, fd, &e) == -1) { std::cerr << "epoll ctl error on fd " << fd << "\n"; } From 52afdea826e0d7cd31beb81834cfca9629495108 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:45:18 -0500 Subject: [PATCH 166/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index c4b23116f8..a48bfd1513 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -649,6 +649,10 @@ namespace glz void process_event_execute(poll_info* pi, poll_status status) { + if (not pi) { + GLZ_THROW_OR_ABORT(std::runtime_error{"invalid poll_info"}); + } + if (not pi->m_processed) { std::atomic_thread_fence(std::memory_order::acquire); // Its possible the event and the timeout occurred in the same epoll, make sure only one From e5504e8df18c3bf31f7ddc0491f4e2ec13d5f48b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:51:40 -0500 Subject: [PATCH 167/309] Update core.hpp --- include/glaze/network/core.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index c1162897eb..473b6047e6 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -27,6 +27,7 @@ #include // for kqueue on macOS #elif defined(__linux__) #include // for epoll on Linux +#include #endif namespace glz::net From f024c666866c3de8d7c8507c1fefa12813afab0c Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:52:54 -0500 Subject: [PATCH 168/309] Update core.hpp --- include/glaze/network/core.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 473b6047e6..af4f30f643 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -28,6 +28,7 @@ #elif defined(__linux__) #include // for epoll on Linux #include +#include #endif namespace glz::net From cddc7fa17a0c5aa1b9846095ca25dfc1db9c9731 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:55:30 -0500 Subject: [PATCH 169/309] Update poll_info.hpp --- include/glaze/coroutine/poll_info.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/glaze/coroutine/poll_info.hpp b/include/glaze/coroutine/poll_info.hpp index 7e94d6f07d..cc017d41a8 100644 --- a/include/glaze/coroutine/poll_info.hpp +++ b/include/glaze/coroutine/poll_info.hpp @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include From a500a975202a0d6c50f29435028455a26add6946 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 12:57:47 -0500 Subject: [PATCH 170/309] fix --- include/glaze/coroutine/io_scheduler.hpp | 2 +- include/glaze/network/core.hpp | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index a48bfd1513..f78ec0a295 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -407,7 +407,7 @@ namespace glz std::memory_order::relaxed)) { #if defined(__linux__) eventfd_t value{1}; - event_write(schedule_fd, value); + eventfd_write(schedule_fd, value); #elif defined(__APPLE__) struct kevent e; ::kevent(schedule_fd, NULL, 0, &e, 1, NULL); diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index af4f30f643..3e2a7658e3 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -57,9 +57,6 @@ namespace glz::net constexpr auto poll_out = EPOLLOUT; #elif defined(_WIN32) #endif - - // TODO: implement - int event_write(auto, auto) { return 0; } inline auto close_socket(file_handle_t fd) { #ifdef _WIN32 From cb7c4e1d46f5e3ad1b1a7deffc3d557e364a5669 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:00:16 -0500 Subject: [PATCH 171/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index f78ec0a295..11b604804f 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -530,7 +530,7 @@ namespace glz }; auto event_count = ::kevent(event_fd, nullptr, 0, m_events.data(), int(m_events.size()), &tlimit); #elif defined(__linux__) - auto event_count = ::epoll_wait(event_fd, m_events.data(), m_max_events, timeout.count()); + auto event_count = ::epoll_wait(event_fd, m_events.data(), max_events, timeout.count()); #elif defined(_WIN32) #endif @@ -569,7 +569,7 @@ namespace glz // and an event for the same handle happen in the same epoll_wait() call then inline processing // will destruct the poll_info object before the second event is handled. This is also possible // with thread pool processing, but probably has an extremely low chance of occuring due to - // the thread switch required. If m_max_events == 1 this would be unnecessary. + // the thread switch required. If max_events == 1 this would be unnecessary. if (!m_handles_to_resume.empty()) { if (m_opts.execution_strategy == execution_strategy_t::process_tasks_inline) { From 5ab77c011a66689b4d0c2999bb95edd5cb33e3d0 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:01:51 -0500 Subject: [PATCH 172/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 11b604804f..0a372813bf 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -785,7 +785,7 @@ namespace glz size_t seconds{}; size_t nanoseconds{1}; if (tp > now) { - const auto time_left_ns = tp - now; + const auto time_left = tp - now; const auto s = std::chrono::duration_cast(time_left); seconds = s.count(); nanoseconds = std::chrono::duration_cast(time_left - s).count(); From 170d9657ad2c0618dcecdb52cb63411ae2e2e19d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:03:33 -0500 Subject: [PATCH 173/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 0a372813bf..1872dd095a 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -25,6 +25,7 @@ #endif #include +#include #include #include #include @@ -796,7 +797,7 @@ namespace glz ts.it_value.tv_nsec = nanoseconds; if (timerfd_settime(timer_fd, 0, &ts, nullptr) == -1) { - std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; + std::cerr << "Failed to set timerfd errorno=[" << std::string{std::strerror(errno)} << "]."; } #elif defined(__APPLE__) size_t milliseconds{}; From 4294c9d5de1db1c45aa58a9c69792537bd162796 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:11:24 -0500 Subject: [PATCH 174/309] Update core.hpp --- include/glaze/network/core.hpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 3e2a7658e3..f36644766f 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -86,7 +86,10 @@ namespace glz::net inline file_handle_t create_shutdown_handle() { #if defined(__APPLE__) - return 0; + file_handle_t fd{}; + struct kevent e; + EV_SET(&e, fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); + return fd; #elif defined(__linux__) return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) @@ -96,7 +99,10 @@ namespace glz::net inline file_handle_t create_timer_handle() { #if defined(__APPLE__) - return 0; + file_handle_t fd{}; + struct kevent e; + EV_SET(&e, fd, EVFILT_TIMER, EV_ADD | EV_ENABLE, NOTE_SECONDS, 1, nullptr); + return fd; #elif defined(__linux__) return ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); #elif defined(_WIN32) @@ -106,7 +112,10 @@ namespace glz::net inline file_handle_t create_schedule_handle() { #if defined(__APPLE__) - return 0; + file_handle_t fd{}; + struct kevent e; + EV_SET(&e, fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); + return fd; #elif defined(__linux__) return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) From 65b43e8d515d8f52fd7349bc004f8f0548045f1a Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:11:59 -0500 Subject: [PATCH 175/309] Update core.hpp --- include/glaze/network/core.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index f36644766f..740be828e5 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -87,7 +87,7 @@ namespace glz::net inline file_handle_t create_shutdown_handle() { #if defined(__APPLE__) file_handle_t fd{}; - struct kevent e; + struct kevent e{}; EV_SET(&e, fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); return fd; #elif defined(__linux__) @@ -100,7 +100,7 @@ namespace glz::net inline file_handle_t create_timer_handle() { #if defined(__APPLE__) file_handle_t fd{}; - struct kevent e; + struct kevent e{}; EV_SET(&e, fd, EVFILT_TIMER, EV_ADD | EV_ENABLE, NOTE_SECONDS, 1, nullptr); return fd; #elif defined(__linux__) @@ -113,7 +113,7 @@ namespace glz::net inline file_handle_t create_schedule_handle() { #if defined(__APPLE__) file_handle_t fd{}; - struct kevent e; + struct kevent e{}; EV_SET(&e, fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); return fd; #elif defined(__linux__) From 7682b699ed5d0aa755dc5ec3e95b5c4971cb3acd Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:13:36 -0500 Subject: [PATCH 176/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 1872dd095a..272e5962ee 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -543,6 +543,9 @@ namespace glz #elif defined(__APPLE__) void* handle_ptr = event.udata; #endif + if (not handle_ptr) { + GLZ_THROW_OR_ABORT(std::runtime_error{"handle_ptr is null"}); + } if (handle_ptr == m_timer_ptr) { // Process all events that have timed out. From 6123c9db9d6b15b0109f806e5a1325a3b225a3e6 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:16:11 -0500 Subject: [PATCH 177/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 272e5962ee..6476c030d3 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -529,9 +529,9 @@ namespace glz { 0, timeout.count() * 1'000'000 }; - auto event_count = ::kevent(event_fd, nullptr, 0, m_events.data(), int(m_events.size()), &tlimit); + const auto event_count = ::kevent(event_fd, nullptr, 0, m_events.data(), int(m_events.size()), &tlimit); #elif defined(__linux__) - auto event_count = ::epoll_wait(event_fd, m_events.data(), max_events, timeout.count()); + const auto event_count = ::epoll_wait(event_fd, m_events.data(), max_events, timeout.count()); #elif defined(_WIN32) #endif From fddf1c44b41c2bbdd880e59a765f8c9a39d629e0 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:17:09 -0500 Subject: [PATCH 178/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 28 +++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 6476c030d3..4282f8772e 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -212,8 +212,8 @@ namespace glz eventfd_t value{1}; eventfd_write(m_scheduler.schedule_fd, value); #elif defined(__APPLE__) - struct kevent e; - ::kevent(m_scheduler.schedule_fd, NULL, 0, &e, 1, NULL); + struct kevent e; + ::kevent(m_scheduler.schedule_fd, NULL, 0, &e, 1, NULL); #endif } } @@ -324,7 +324,7 @@ namespace glz * @return The result of the poll operation. */ [[nodiscard]] glz::task poll(net::file_handle_t fd, glz::poll_op op, - std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) + std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { // Because the size will drop when this coroutine suspends every poll needs to undo the subtraction // on the number of active tasks in the scheduler. When this task is resumed by the event loop. @@ -656,7 +656,7 @@ namespace glz if (not pi) { GLZ_THROW_OR_ABORT(std::runtime_error{"invalid poll_info"}); } - + if (not pi->m_processed) { std::atomic_thread_fence(std::memory_order::acquire); // Its possible the event and the timeout occurred in the same epoll, make sure only one @@ -668,7 +668,8 @@ namespace glz #if defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); #elif defined(__APPLE__) - struct kevent e; + struct kevent e + {}; EV_SET(&e, pi->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "Failed to remove fd " << pi->m_fd << " from kqueue\n"; @@ -723,15 +724,16 @@ namespace glz #if defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); #elif defined(__APPLE__) - struct kevent e; + struct kevent e + {}; - // Initialize the kevent structure for deletion - EV_SET(&e, pi->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + // Initialize the kevent structure for deletion + EV_SET(&e, pi->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); - // Remove the event from the kqueue - if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { - std::cerr << "Failed to remove fd " << pi->m_fd << " from kqueue\n"; - } + // Remove the event from the kqueue + if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { + std::cerr << "Failed to remove fd " << pi->m_fd << " from kqueue\n"; + } #endif } @@ -794,7 +796,7 @@ namespace glz seconds = s.count(); nanoseconds = std::chrono::duration_cast(time_left - s).count(); } - + itimerspec ts{}; ts.it_value.tv_sec = seconds; ts.it_value.tv_nsec = nanoseconds; From 6b2c24ddb3349e3a06086867f6a3ad908dc06906 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:17:56 -0500 Subject: [PATCH 179/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 4282f8772e..61ca352bf9 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -767,18 +767,16 @@ namespace glz void remove_timer_token(timed_events::iterator pos) { - { - std::scoped_lock lk{m_timed_events_mutex}; - auto is_first = (m_timed_events.begin() == pos); + std::scoped_lock lk{m_timed_events_mutex}; + auto is_first = (m_timed_events.begin() == pos); - m_timed_events.erase(pos); + m_timed_events.erase(pos); - // If this was the first item, update the timeout. It would be acceptable to just let it - // also fire the timeout as the event loop will ignore it since nothing will have timed - // out but it feels like the right thing to do to update it to the correct timeout value. - if (is_first) { - update_timeout(clock::now()); - } + // If this was the first item, update the timeout. It would be acceptable to just let it + // also fire the timeout as the event loop will ignore it since nothing will have timed + // out but it feels like the right thing to do to update it to the correct timeout value. + if (is_first) { + update_timeout(clock::now()); } } From 94a7d508527b4172cce67f8d953f99b48e04f5c4 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:18:21 -0500 Subject: [PATCH 180/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 61ca352bf9..32188c8ec4 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -784,7 +784,7 @@ namespace glz { if (!m_timed_events.empty()) { auto& [tp, pi] = *m_timed_events.begin(); - + #if defined(__linux__) size_t seconds{}; size_t nanoseconds{1}; @@ -794,11 +794,11 @@ namespace glz seconds = s.count(); nanoseconds = std::chrono::duration_cast(time_left - s).count(); } - + itimerspec ts{}; ts.it_value.tv_sec = seconds; ts.it_value.tv_nsec = nanoseconds; - + if (timerfd_settime(timer_fd, 0, &ts, nullptr) == -1) { std::cerr << "Failed to set timerfd errorno=[" << std::string{std::strerror(errno)} << "]."; } @@ -807,7 +807,7 @@ namespace glz if (tp > now) { milliseconds = std::chrono::duration_cast(tp - now).count(); } - struct kevent e; + struct kevent e{}; EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, milliseconds, nullptr); if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { perror("kevent (update timer)"); @@ -824,7 +824,7 @@ namespace glz std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; } #elif defined(__APPLE__) - struct kevent e; + struct kevent e{}; EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 0, nullptr); if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { perror("kevent (update timer)"); From 167348d33bcdc6f0f9b58950bf6416b83863e0a2 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:18:44 -0500 Subject: [PATCH 181/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 32188c8ec4..89bab3b717 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -784,7 +784,7 @@ namespace glz { if (!m_timed_events.empty()) { auto& [tp, pi] = *m_timed_events.begin(); - + #if defined(__linux__) size_t seconds{}; size_t nanoseconds{1}; @@ -794,11 +794,11 @@ namespace glz seconds = s.count(); nanoseconds = std::chrono::duration_cast(time_left - s).count(); } - + itimerspec ts{}; ts.it_value.tv_sec = seconds; ts.it_value.tv_nsec = nanoseconds; - + if (timerfd_settime(timer_fd, 0, &ts, nullptr) == -1) { std::cerr << "Failed to set timerfd errorno=[" << std::string{std::strerror(errno)} << "]."; } @@ -807,7 +807,8 @@ namespace glz if (tp > now) { milliseconds = std::chrono::duration_cast(tp - now).count(); } - struct kevent e{}; + struct kevent e + {}; EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, milliseconds, nullptr); if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { perror("kevent (update timer)"); @@ -824,7 +825,8 @@ namespace glz std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; } #elif defined(__APPLE__) - struct kevent e{}; + struct kevent e + {}; EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 0, nullptr); if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { perror("kevent (update timer)"); From b4f8f415763e414e5e220337795b192571a8de68 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:20:45 -0500 Subject: [PATCH 182/309] Update core.hpp --- include/glaze/network/core.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 740be828e5..3eb807390e 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -125,7 +125,7 @@ namespace glz::net inline bool poll_error([[maybe_unused]] uint32_t events) { #if defined(__APPLE__) - return true; + return events & EV_ERROR; #elif defined(__linux__) return events & EPOLLERR; #elif defined(_WIN32) @@ -135,7 +135,7 @@ namespace glz::net inline bool event_closed([[maybe_unused]] uint32_t events) { #if defined(__APPLE__) - return true; + return events & EV_EOF; #elif defined(__linux__) return events & EPOLLRDHUP || events & EPOLLHUP; #elif defined(_WIN32) From 8e228e142823a2b2b0eba753a68c07b5f5894e6c Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:25:26 -0500 Subject: [PATCH 183/309] cleaning --- include/glaze/coroutine/event.hpp | 6 +++--- include/glaze/coroutine/generator.hpp | 2 +- include/glaze/coroutine/io_scheduler.hpp | 4 ++-- include/glaze/coroutine/task.hpp | 8 ++++---- include/glaze/coroutine/thread_pool.hpp | 6 +++--- include/glaze/coroutine/when_all.hpp | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/include/glaze/coroutine/event.hpp b/include/glaze/coroutine/event.hpp index 85a3048651..22b8156be4 100644 --- a/include/glaze/coroutine/event.hpp +++ b/include/glaze/coroutine/event.hpp @@ -136,7 +136,7 @@ namespace glz // else lifo nothing to do auto* waiters = static_cast(old_value); - while (waiters != nullptr) + while (waiters) { auto* next = waiters->m_next; waiters->m_awaiting_coroutine.resume(); @@ -161,7 +161,7 @@ namespace glz // else lifo nothing to do auto* waiters = static_cast(old_value); - while (waiters != nullptr) { + while (waiters) { auto* next = waiters->m_next; e.resume(waiters->m_awaiting_coroutine); waiters = next; @@ -207,7 +207,7 @@ namespace glz awaiter* prev = nullptr; awaiter* next = nullptr; - while (curr != nullptr) + while (curr) { next = curr->m_next; curr->m_next = prev; diff --git a/include/glaze/coroutine/generator.hpp b/include/glaze/coroutine/generator.hpp index 4d67d5de10..38c5664f94 100644 --- a/include/glaze/coroutine/generator.hpp +++ b/include/glaze/coroutine/generator.hpp @@ -153,7 +153,7 @@ namespace glz auto begin() -> iterator { - if (m_coroutine != nullptr) { + if (m_coroutine) { m_coroutine.resume(); if (m_coroutine.done()) { m_coroutine.promise().rethrow_if_exception(); diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 89bab3b717..e9b434f822 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -506,7 +506,7 @@ namespace glz void process_events_dedicated_thread() { - if (m_opts.on_io_thread_start_functor != nullptr) { + if (m_opts.on_io_thread_start_functor) { m_opts.on_io_thread_start_functor(); } @@ -517,7 +517,7 @@ namespace glz } m_io_processing.exchange(false, std::memory_order::release); - if (m_opts.on_io_thread_stop_functor != nullptr) { + if (m_opts.on_io_thread_stop_functor) { m_opts.on_io_thread_stop_functor(); } } diff --git a/include/glaze/coroutine/task.hpp b/include/glaze/coroutine/task.hpp index a61129737d..73d1a39ec5 100644 --- a/include/glaze/coroutine/task.hpp +++ b/include/glaze/coroutine/task.hpp @@ -37,7 +37,7 @@ namespace glz { // If there is a continuation call it, otherwise this is the end of the line. auto& promise = coroutine.promise(); - if (promise.m_continuation != nullptr) { + if (promise.m_continuation) { return promise.m_continuation; } else { @@ -244,7 +244,7 @@ namespace glz ~task() { - if (m_coroutine != nullptr) { + if (m_coroutine) { m_coroutine.destroy(); } } @@ -254,7 +254,7 @@ namespace glz auto operator=(task&& other) noexcept -> task& { if (std::addressof(other) != this) { - if (m_coroutine != nullptr) { + if (m_coroutine) { m_coroutine.destroy(); } @@ -279,7 +279,7 @@ namespace glz auto destroy() -> bool { - if (m_coroutine != nullptr) { + if (m_coroutine) { m_coroutine.destroy(); m_coroutine = nullptr; return true; diff --git a/include/glaze/coroutine/thread_pool.hpp b/include/glaze/coroutine/thread_pool.hpp index def1e52e2c..1b48d064b8 100644 --- a/include/glaze/coroutine/thread_pool.hpp +++ b/include/glaze/coroutine/thread_pool.hpp @@ -199,7 +199,7 @@ namespace glz { std::scoped_lock lk{m_wait_mutex}; for (const auto& handle : handles) { - if (handle != nullptr) [[likely]] { + if (handle) [[likely]] { m_queue.emplace_back(handle); } else { @@ -298,7 +298,7 @@ namespace glz */ void executor(size_t idx) { - if (m_opts.on_thread_start_functor != nullptr) { + if (m_opts.on_thread_start_functor) { m_opts.on_thread_start_functor(idx); } @@ -339,7 +339,7 @@ namespace glz m_size.fetch_sub(1, std::memory_order::release); } - if (m_opts.on_thread_stop_functor != nullptr) { + if (m_opts.on_thread_stop_functor) { m_opts.on_thread_stop_functor(idx); } } diff --git a/include/glaze/coroutine/when_all.hpp b/include/glaze/coroutine/when_all.hpp index 2e986e91e3..4b734d72aa 100644 --- a/include/glaze/coroutine/when_all.hpp +++ b/include/glaze/coroutine/when_all.hpp @@ -41,7 +41,7 @@ namespace glz auto is_ready() const noexcept -> bool { - return m_awaiting_coroutine != nullptr && m_awaiting_coroutine.done(); + return m_awaiting_coroutine && m_awaiting_coroutine.done(); } auto try_await(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool @@ -376,7 +376,7 @@ namespace glz ~when_all_task() { - if (m_coroutine != nullptr) { + if (m_coroutine) { m_coroutine.destroy(); } } From d0a89fa76c957c6ea60e5fded6bb54eb2f05e930 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:28:07 -0500 Subject: [PATCH 184/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index e9b434f822..a94b558ac5 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -389,7 +389,7 @@ namespace glz */ auto resume(std::coroutine_handle<> handle) -> bool { - if (handle == nullptr) { + if (not handle) { return false; } @@ -811,7 +811,7 @@ namespace glz {}; EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, milliseconds, nullptr); if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { - perror("kevent (update timer)"); + std::cerr << "kevent (update timer)\n"; } #endif } @@ -829,7 +829,7 @@ namespace glz {}; EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 0, nullptr); if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { - perror("kevent (update timer)"); + std::cerr << "kevent (update timer)\n"; } #endif } From 43b669d085b4113432085eae5c8a584b48fd4f09 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:33:22 -0500 Subject: [PATCH 185/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index a94b558ac5..c7af514ef8 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -183,13 +183,13 @@ namespace glz struct schedule_operation { - friend struct io_scheduler; - explicit schedule_operation(io_scheduler& scheduler) noexcept : m_scheduler(scheduler) {} + /// The thread pool that this operation will execute on. + io_scheduler& m_scheduler; /** * Operations always pause so the executing thread can be switched. */ - auto await_ready() noexcept -> bool { return false; } + bool await_ready() noexcept { return false; } /** * Suspending always returns to the caller (using void return of await_suspend()) and @@ -226,10 +226,6 @@ namespace glz * no-op as this is the function called first by the thread pool's executing thread. */ auto await_resume() noexcept -> void {} - - private: - /// The thread pool that this operation will execute on. - io_scheduler& m_scheduler; }; /** From 1fbe59cb15acd45efdc8391d6d5e55ecdf407bf1 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:37:39 -0500 Subject: [PATCH 186/309] checking for timeout --- include/glaze/coroutine/io_scheduler.hpp | 5 +++++ tests/coroutine_test/coroutine_test.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index c7af514ef8..ce9f71ea5a 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -530,6 +530,11 @@ namespace glz const auto event_count = ::epoll_wait(event_fd, m_events.data(), max_events, timeout.count()); #elif defined(_WIN32) #endif + + if (event_count == -1) { + net::event_close(event_fd); + GLZ_THROW_OR_ABORT(std::runtime_error{"wait for event failed"}); + } if (event_count > 0) { for (size_t i = 0; i < size_t(event_count); ++i) { diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 65b5f803e4..1b285b4ed6 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -167,7 +167,7 @@ suite latch = [] { co_return; }; - const int64_t num_tasks{5}; + const int64_t num_tasks{0}; glz::latch l{num_tasks}; std::vector> tasks{}; From c59c864da1e579922f9e3c5dee3abfa217b3171f Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:43:09 -0500 Subject: [PATCH 187/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index ce9f71ea5a..57c8e74ff7 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -543,6 +543,10 @@ namespace glz void* handle_ptr = event.data.ptr; #elif defined(__APPLE__) void* handle_ptr = event.udata; + + if (event.flags & EV_ERROR) { + GLZ_THROW_OR_ABORT(std::runtime_error{"event error"}); + } #endif if (not handle_ptr) { GLZ_THROW_OR_ABORT(std::runtime_error{"handle_ptr is null"}); From 31e5b4bbc4e0a718c0e3db6bc29cf69fd672b181 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 26 Jun 2024 13:45:06 -0500 Subject: [PATCH 188/309] Update coroutine_test.cpp --- tests/coroutine_test/coroutine_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 1b285b4ed6..65b5f803e4 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -167,7 +167,7 @@ suite latch = [] { co_return; }; - const int64_t num_tasks{0}; + const int64_t num_tasks{5}; glz::latch l{num_tasks}; std::vector> tasks{}; From 16b87b5b2e5244605f044140acb91fc9a101b167 Mon Sep 17 00:00:00 2001 From: Bill Berry Date: Wed, 26 Jun 2024 16:56:35 -0500 Subject: [PATCH 189/309] This I think points us in the right direction. --- include/glaze/coroutine/io_scheduler.hpp | 38 +++++++++++++++--------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 57c8e74ff7..6fb9b69bbf 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -116,20 +116,30 @@ namespace glz e.data.ptr = const_cast(m_schedule_ptr); epoll_ctl(event_fd, EPOLL_CTL_ADD, schedule_fd, &e); #elif defined(__APPLE__) - e.filter = EVFILT_READ; - e.flags = EV_ADD; - - e.udata = const_cast(m_shutdown_ptr); - EV_SET(&e, shutdown_fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); - ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); - - e.udata = const_cast(m_timer_ptr); - EV_SET(&e, timer_fd, EVFILT_TIMER, EV_ADD, 0, 0, nullptr); - ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); - - e.udata = const_cast(m_schedule_ptr); - EV_SET(&e, schedule_fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); - ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); + + net::poll_event_t e_shutdown{.filter = EVFILT_READ, .flags = EV_ADD, .udata = const_cast(m_shutdown_ptr)}; + net::poll_event_t e_timer{.filter = EVFILT_READ, .flags = EV_ADD, .udata = const_cast(m_timer_ptr) }; + net::poll_event_t e_schedule{.filter = EVFILT_READ, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; + /* +#define EV_SET(kevp, a, b, c, d, e, f) do { \ +struct kevent *__kevp__ = (kevp); \ +__kevp__->ident = (a); \ +__kevp__->filter = (b); \ +__kevp__->flags = (c); \ +__kevp__->fflags = (d); \ +__kevp__->data = (e); \ +__kevp__->udata = (f); \ +} while(0) +*/ + + //EV_SET(&e_shutdown, shutdown_fd, EVFILT_READ, EV_ADD, 0, 0, const_cast(m_shutdown_ptr)); + ::kevent(event_fd, &e_shutdown, 1, nullptr, 0, nullptr); + + //EV_SET(&e_timer, timer_fd, EVFILT_TIMER, EV_ADD, 0, 0, const_cast(m_timer_ptr)); + ::kevent(event_fd, &e_timer, 1, nullptr, 0, nullptr); + + //EV_SET(&e_schedule, schedule_fd, EVFILT_READ, EV_ADD, 0, 0, const_cast(m_schedule_ptr)); + ::kevent(event_fd, &e_schedule, 1, nullptr, 0, nullptr); #endif if (m_opts.thread_strategy == thread_strategy_t::spawn) { From 13e61f581d0e4b2cbaa7840a6dfc65fabda618b7 Mon Sep 17 00:00:00 2001 From: Bill Berry Date: Wed, 26 Jun 2024 22:17:40 -0500 Subject: [PATCH 190/309] Correct bad usage of 'EV_SET' where udata was set to null_ptr --- include/glaze/coroutine/io_scheduler.hpp | 49 ++++++++++++------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 6fb9b69bbf..09b1b037ea 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -116,30 +116,29 @@ namespace glz e.data.ptr = const_cast(m_schedule_ptr); epoll_ctl(event_fd, EPOLL_CTL_ADD, schedule_fd, &e); #elif defined(__APPLE__) + // e.filter = EVFILT_READ; + // e.flags = EV_ADD; + + // e.udata = const_cast(m_shutdown_ptr); + // EV_SET(&e, shutdown_fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); + // ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); + + // e.udata = const_cast(m_timer_ptr); + // EV_SET(&e, timer_fd, EVFILT_TIMER, EV_ADD, 0, 0, nullptr); + // ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); + + // e.udata = const_cast(m_schedule_ptr); + // EV_SET(&e, schedule_fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); + // ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); + net::poll_event_t e_timer{.filter = EVFILT_TIMER, .flags = EV_ADD, .udata = const_cast(m_timer_ptr)}; net::poll_event_t e_shutdown{.filter = EVFILT_READ, .flags = EV_ADD, .udata = const_cast(m_shutdown_ptr)}; - net::poll_event_t e_timer{.filter = EVFILT_READ, .flags = EV_ADD, .udata = const_cast(m_timer_ptr) }; net::poll_event_t e_schedule{.filter = EVFILT_READ, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; - /* -#define EV_SET(kevp, a, b, c, d, e, f) do { \ -struct kevent *__kevp__ = (kevp); \ -__kevp__->ident = (a); \ -__kevp__->filter = (b); \ -__kevp__->flags = (c); \ -__kevp__->fflags = (d); \ -__kevp__->data = (e); \ -__kevp__->udata = (f); \ -} while(0) -*/ - - //EV_SET(&e_shutdown, shutdown_fd, EVFILT_READ, EV_ADD, 0, 0, const_cast(m_shutdown_ptr)); - ::kevent(event_fd, &e_shutdown, 1, nullptr, 0, nullptr); - - //EV_SET(&e_timer, timer_fd, EVFILT_TIMER, EV_ADD, 0, 0, const_cast(m_timer_ptr)); - ::kevent(event_fd, &e_timer, 1, nullptr, 0, nullptr); - - //EV_SET(&e_schedule, schedule_fd, EVFILT_READ, EV_ADD, 0, 0, const_cast(m_schedule_ptr)); - ::kevent(event_fd, &e_schedule, 1, nullptr, 0, nullptr); + + ::kevent(event_fd, &e_schedule, 1, nullptr, 0, nullptr); + ::kevent(event_fd, &e_shutdown, 1, nullptr, 0, nullptr); + ::kevent(event_fd, &e_timer, 1, nullptr, 0, nullptr); + #endif if (m_opts.thread_strategy == thread_strategy_t::spawn) { @@ -357,8 +356,8 @@ __kevp__->udata = (f); \ std::cerr << "epoll ctl error on fd " << fd << "\n"; } #elif defined(__APPLE__) - e.udata = &poll_info; - EV_SET(&e, fd, EVFILT_READ, EV_ADD | EV_ONESHOT | EV_EOF, 0, 0, nullptr); + //e.udata = &poll_info; + EV_SET(&e, fd, EVFILT_READ, EV_ADD | EV_ONESHOT | EV_EOF, 0, 0, &poll_info); if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { std::cerr << "kqueue ctl error on fd " << fd << "\n"; } @@ -559,7 +558,8 @@ __kevp__->udata = (f); \ } #endif if (not handle_ptr) { - GLZ_THROW_OR_ABORT(std::runtime_error{"handle_ptr is null"}); + continue; + //GLZ_THROW_OR_ABORT(std::runtime_error{"handle_ptr is null"}); } if (handle_ptr == m_timer_ptr) { @@ -572,6 +572,7 @@ __kevp__->udata = (f); \ } else if (handle_ptr == m_shutdown_ptr) [[unlikely]] { // Nothing to do , just needed to wake-up and smell the flowers + std::cout << "Waking up and smelling flowers...\n"; } else { // Individual poll task wake-up. From 8f8ef81bfe923e1300dc767f525048ca5c82fbce Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 07:02:58 -0500 Subject: [PATCH 191/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 57c8e74ff7..ed73c65688 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -40,10 +40,7 @@ namespace glz using time_point = clock::time_point; using timed_events = poll_info::timed_events; - struct schedule_operation; - friend schedule_operation; - - enum class thread_strategy_t { + enum struct thread_strategy_t { /// Spawns a dedicated background thread for the scheduler to run on. spawn, /// Requires the user to call process_events() to drive the scheduler. From 15649f9d69d023d7df64b498c987967ac2b5905f Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 07:57:42 -0500 Subject: [PATCH 192/309] progress --- include/glaze/coroutine/io_scheduler.hpp | 4 ++ include/glaze/network/core.hpp | 53 ++++++++++++++++++------ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 75793eb261..074c6abd42 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -455,9 +455,13 @@ namespace glz } // Signal the event loop to stop asap, triggering the event fd is safe. +#if defined(__linux__) uint64_t value{1}; auto written = ::write(shutdown_fd, &value, sizeof(value)); (void)written; +#elif defined(__APPLE__) + net::trigger_user_kqueue(shutdown_fd); +#endif if (m_io_thread.joinable()) { m_io_thread.join(); diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 3eb807390e..4e1edce491 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -3,6 +3,15 @@ #pragma once +#ifndef GLZ_THROW_OR_ABORT +#if __cpp_exceptions +#define GLZ_THROW_OR_ABORT(EXC) (throw(EXC)) +#include +#else +#define GLZ_THROW_OR_ABORT(EXC) (std::abort()) +#endif +#endif + #if defined(_WIN32) #include #include @@ -84,12 +93,38 @@ namespace glz::net #endif } +#if defined(__APPLE__) + inline file_handle_t create_user_kqueue_handle() { + int kq = kqueue(); + if (kq == -1) { + GLZ_THROW_OR_ABORT(std::runtime_error("Failed to create kqueue")); + } + + struct kevent kev; + EV_SET(&kev, 1, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, NULL); + + if (kevent(kq, &kev, 1, NULL, 0, NULL) == -1) { + close(kq); + GLZ_THROW_OR_ABORT( std::runtime_error("Failed to add EVFILT_USER event to kqueue")); + } + + return kq; + } + + inline void trigger_user_kqueue(file_handle_t fd) + { + struct kevent kev; + EV_SET(&kev, 1, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL); + + if (kevent(fd, &kev, 1, NULL, 0, NULL) == -1) { + GLZ_THROW_OR_ABORT(std::runtime_error("Failed to signal shutdown event")); + } + } +#endif + inline file_handle_t create_shutdown_handle() { #if defined(__APPLE__) - file_handle_t fd{}; - struct kevent e{}; - EV_SET(&e, fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); - return fd; + return create_user_kqueue_handle(); #elif defined(__linux__) return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) @@ -99,10 +134,7 @@ namespace glz::net inline file_handle_t create_timer_handle() { #if defined(__APPLE__) - file_handle_t fd{}; - struct kevent e{}; - EV_SET(&e, fd, EVFILT_TIMER, EV_ADD | EV_ENABLE, NOTE_SECONDS, 1, nullptr); - return fd; + return create_user_kqueue_handle(); #elif defined(__linux__) return ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); #elif defined(_WIN32) @@ -112,10 +144,7 @@ namespace glz::net inline file_handle_t create_schedule_handle() { #if defined(__APPLE__) - file_handle_t fd{}; - struct kevent e{}; - EV_SET(&e, fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); - return fd; + return create_user_kqueue_handle(); #elif defined(__linux__) return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) From 759e36c14e89957fae9d448be36ad7b47203ad2d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 09:19:44 -0500 Subject: [PATCH 193/309] updates --- include/glaze/coroutine/io_scheduler.hpp | 59 ++++++++++-------------- include/glaze/network/core.hpp | 50 ++++++-------------- 2 files changed, 40 insertions(+), 69 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 074c6abd42..3211b575f8 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -113,29 +113,13 @@ namespace glz e.data.ptr = const_cast(m_schedule_ptr); epoll_ctl(event_fd, EPOLL_CTL_ADD, schedule_fd, &e); #elif defined(__APPLE__) - // e.filter = EVFILT_READ; - // e.flags = EV_ADD; - - // e.udata = const_cast(m_shutdown_ptr); - // EV_SET(&e, shutdown_fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); - // ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); - - // e.udata = const_cast(m_timer_ptr); - // EV_SET(&e, timer_fd, EVFILT_TIMER, EV_ADD, 0, 0, nullptr); - // ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); - - // e.udata = const_cast(m_schedule_ptr); - // EV_SET(&e, schedule_fd, EVFILT_READ, EV_ADD, 0, 0, nullptr); - // ::kevent(event_fd, &e, 1, nullptr, 0, nullptr); - - net::poll_event_t e_timer{.filter = EVFILT_TIMER, .flags = EV_ADD, .udata = const_cast(m_timer_ptr)}; - net::poll_event_t e_shutdown{.filter = EVFILT_READ, .flags = EV_ADD, .udata = const_cast(m_shutdown_ptr)}; + net::poll_event_t e_timer{.filter = EVFILT_TIMER, .flags = EV_ADD | EV_ENABLE, .udata = const_cast(m_timer_ptr)}; + net::poll_event_t e_shutdown{.filter = EVFILT_USER, .flags = EV_ADD | EV_CLEAR, .udata = const_cast(m_shutdown_ptr)}; net::poll_event_t e_schedule{.filter = EVFILT_READ, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; ::kevent(event_fd, &e_schedule, 1, nullptr, 0, nullptr); ::kevent(event_fd, &e_shutdown, 1, nullptr, 0, nullptr); ::kevent(event_fd, &e_timer, 1, nullptr, 0, nullptr); - #endif if (m_opts.thread_strategy == thread_strategy_t::spawn) { @@ -157,17 +141,23 @@ namespace glz m_io_thread.join(); } - if (event_fd != -1) { + if (event_fd != net::invalid_ident) { +#if defined(__linux__) close(event_fd); - event_fd = -1; +#endif + event_fd = net::invalid_ident; } - if (timer_fd != -1) { + if (timer_fd != net::invalid_ident) { +#if defined(__linux__) close(timer_fd); +#endif timer_fd = -1; } - if (schedule_fd != -1) { + if (schedule_fd != net::invalid_ident) { +#if defined(__linux__) close(schedule_fd); - schedule_fd = -1; +#endif + schedule_fd = net::invalid_ident; } } @@ -460,7 +450,10 @@ namespace glz auto written = ::write(shutdown_fd, &value, sizeof(value)); (void)written; #elif defined(__APPLE__) - net::trigger_user_kqueue(shutdown_fd); + net::poll_event_t e{.filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_shutdown_ptr)}; + if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + GLZ_THROW_OR_ABORT(std::runtime_error("Failed to signal shutdown event")); + } #endif if (m_io_thread.joinable()) { @@ -476,11 +469,11 @@ namespace glz /// The event loop epoll file descriptor. net::file_handle_t event_fd{net::invalid_file_handle}; /// The event loop fd to trigger a shutdown. - net::file_handle_t shutdown_fd{net::invalid_file_handle}; + ident_t shutdown_fd{net::invalid_file_handle}; /// The event loop timer fd for timed events, e.g. yield_for() or scheduler_after(). - net::file_handle_t timer_fd{net::invalid_file_handle}; + ident_t timer_fd{net::invalid_file_handle}; /// The schedule file descriptor if the scheduler is in inline processing mode. - net::file_handle_t schedule_fd{net::invalid_file_handle}; + ident_t schedule_fd{net::invalid_file_handle}; std::atomic m_schedule_fd_triggered{false}; /// The number of tasks executing or awaiting events in this io scheduler. @@ -559,8 +552,7 @@ namespace glz } #endif if (not handle_ptr) { - continue; - //GLZ_THROW_OR_ABORT(std::runtime_error{"handle_ptr is null"}); + GLZ_THROW_OR_ABORT(std::runtime_error{"handle_ptr is null"}); } if (handle_ptr == m_timer_ptr) { @@ -824,9 +816,8 @@ namespace glz if (tp > now) { milliseconds = std::chrono::duration_cast(tp - now).count(); } - struct kevent e - {}; - EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, milliseconds, nullptr); + + net::poll_event_t e{.filter = EVFILT_TIMER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_timer_ptr)}; if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "kevent (update timer)\n"; } @@ -844,8 +835,8 @@ namespace glz #elif defined(__APPLE__) struct kevent e {}; - EV_SET(&e, 1, EVFILT_TIMER, EV_ADD | EV_ENABLE, 0, 0, nullptr); - if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + EV_SET(&e, 1, EVFILT_TIMER, NOTE_TRIGGER, 0, 0, nullptr); + if (::kevent(timer_fd, &e, 1, NULL, 0, NULL) == -1) { std::cerr << "kevent (update timer)\n"; } #endif diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 4e1edce491..a4390bc87c 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -53,8 +53,12 @@ namespace glz::net #if defined(__APPLE__) using poll_event_t = struct kevent; + using ident_t = uintptr_t; + constexpr uintptr_t invalid_ident = 0; #elif defined(__linux__) using poll_event_t = struct epoll_event; + using ident_t = int; + constexpr int invalid_ident_handle = -1; #elif defined(_WIN32) #endif @@ -67,6 +71,11 @@ namespace glz::net #elif defined(_WIN32) #endif + inline uintptr_t unique_identifier() noexcept { + static std::atomic value{1}; + return value.fetch_add(1, std::memory_order_relaxed); + } + inline auto close_socket(file_handle_t fd) { #ifdef _WIN32 ::closesocket(fd); @@ -93,38 +102,9 @@ namespace glz::net #endif } + inline ident_t create_shutdown_handle() { #if defined(__APPLE__) - inline file_handle_t create_user_kqueue_handle() { - int kq = kqueue(); - if (kq == -1) { - GLZ_THROW_OR_ABORT(std::runtime_error("Failed to create kqueue")); - } - - struct kevent kev; - EV_SET(&kev, 1, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, NULL); - - if (kevent(kq, &kev, 1, NULL, 0, NULL) == -1) { - close(kq); - GLZ_THROW_OR_ABORT( std::runtime_error("Failed to add EVFILT_USER event to kqueue")); - } - - return kq; - } - - inline void trigger_user_kqueue(file_handle_t fd) - { - struct kevent kev; - EV_SET(&kev, 1, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL); - - if (kevent(fd, &kev, 1, NULL, 0, NULL) == -1) { - GLZ_THROW_OR_ABORT(std::runtime_error("Failed to signal shutdown event")); - } - } -#endif - - inline file_handle_t create_shutdown_handle() { -#if defined(__APPLE__) - return create_user_kqueue_handle(); + return unique_identifier(); #elif defined(__linux__) return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) @@ -132,9 +112,9 @@ namespace glz::net #endif } - inline file_handle_t create_timer_handle() { + inline ident_t create_timer_handle() { #if defined(__APPLE__) - return create_user_kqueue_handle(); + return unique_identifier(); #elif defined(__linux__) return ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); #elif defined(_WIN32) @@ -142,9 +122,9 @@ namespace glz::net #endif } - inline file_handle_t create_schedule_handle() { + inline ident_t create_schedule_handle() { #if defined(__APPLE__) - return create_user_kqueue_handle(); + return unique_identifier(); #elif defined(__linux__) return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) From 08e004fa67143ca1eafe881a3c7708ee7731d4e3 Mon Sep 17 00:00:00 2001 From: Bill Berry Date: Thu, 27 Jun 2024 10:21:14 -0500 Subject: [PATCH 194/309] Updated to build successfully on Apple --- include/glaze/coroutine/io_scheduler.hpp | 20 +++++++++++++------- include/glaze/network/core.hpp | 5 +++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 3211b575f8..7f76cfdb6b 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -39,6 +39,12 @@ namespace glz using clock = std::chrono::steady_clock; using time_point = clock::time_point; using timed_events = poll_info::timed_events; + + enum struct io_events : int16_t { + on_timed_out = 3000, + on_shutdown, + on_work_complete, + }; enum struct thread_strategy_t { /// Spawns a dedicated background thread for the scheduler to run on. @@ -113,9 +119,9 @@ namespace glz e.data.ptr = const_cast(m_schedule_ptr); epoll_ctl(event_fd, EPOLL_CTL_ADD, schedule_fd, &e); #elif defined(__APPLE__) - net::poll_event_t e_timer{.filter = EVFILT_TIMER, .flags = EV_ADD | EV_ENABLE, .udata = const_cast(m_timer_ptr)}; - net::poll_event_t e_shutdown{.filter = EVFILT_USER, .flags = EV_ADD | EV_CLEAR, .udata = const_cast(m_shutdown_ptr)}; - net::poll_event_t e_schedule{.filter = EVFILT_READ, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; + net::poll_event_t e_timer{.filter = int16_t(io_events::on_timed_out), .flags = EV_ADD | EV_ENABLE, .udata = const_cast(m_timer_ptr)}; + net::poll_event_t e_shutdown{.filter = int16_t(io_events::on_timed_out), .flags = EV_ADD | EV_CLEAR, .udata = const_cast(m_shutdown_ptr)}; + net::poll_event_t e_schedule{.filter = int16_t(io_events::on_work_complete), .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; ::kevent(event_fd, &e_schedule, 1, nullptr, 0, nullptr); ::kevent(event_fd, &e_shutdown, 1, nullptr, 0, nullptr); @@ -469,11 +475,11 @@ namespace glz /// The event loop epoll file descriptor. net::file_handle_t event_fd{net::invalid_file_handle}; /// The event loop fd to trigger a shutdown. - ident_t shutdown_fd{net::invalid_file_handle}; + net::ident_t shutdown_fd{net::invalid_file_handle}; /// The event loop timer fd for timed events, e.g. yield_for() or scheduler_after(). - ident_t timer_fd{net::invalid_file_handle}; + net::ident_t timer_fd{net::invalid_file_handle}; /// The schedule file descriptor if the scheduler is in inline processing mode. - ident_t schedule_fd{net::invalid_file_handle}; + net::ident_t schedule_fd{net::invalid_file_handle}; std::atomic m_schedule_fd_triggered{false}; /// The number of tasks executing or awaiting events in this io scheduler. @@ -812,7 +818,7 @@ namespace glz std::cerr << "Failed to set timerfd errorno=[" << std::string{std::strerror(errno)} << "]."; } #elif defined(__APPLE__) - size_t milliseconds{}; + [[maybe_unused]] size_t milliseconds{}; if (tp > now) { milliseconds = std::chrono::duration_cast(tp - now).count(); } diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index a4390bc87c..f03c9f5329 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -48,17 +48,18 @@ namespace glz::net using ssize_t = int64_t; #else using file_handle_t = int; - constexpr int invalid_file_handle = -1; #endif #if defined(__APPLE__) using poll_event_t = struct kevent; - using ident_t = uintptr_t; + using ident_t = uint16_t; + constexpr ident_t invalid_file_handle = ~1; // set all bits constexpr uintptr_t invalid_ident = 0; #elif defined(__linux__) using poll_event_t = struct epoll_event; using ident_t = int; constexpr int invalid_ident_handle = -1; + constexpr int invalid_file_handle = -1; #elif defined(_WIN32) #endif From a6cbaaf9d8208ca992cdc9d7b849bad4b15090a5 Mon Sep 17 00:00:00 2001 From: Bill Berry Date: Thu, 27 Jun 2024 10:30:00 -0500 Subject: [PATCH 195/309] Updated to build successfully on Apple --- include/glaze/coroutine/io_scheduler.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 7f76cfdb6b..eec132a85b 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -823,9 +823,9 @@ namespace glz milliseconds = std::chrono::duration_cast(tp - now).count(); } - net::poll_event_t e{.filter = EVFILT_TIMER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_timer_ptr)}; + net::poll_event_t e{.filter = uint16_t(io_events::on_timed_out), .fflags = NOTE_TRIGGER, .udata = const_cast(m_timer_ptr)}; if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { - std::cerr << "kevent (update timer)\n"; + std::cerr << "Error: kevent (update timer).\n"; } #endif } From 154ea130013fb788b00246d8d5f5daba7b9175b6 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 10:45:50 -0500 Subject: [PATCH 196/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index eec132a85b..8c68e9a92b 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -119,9 +119,9 @@ namespace glz e.data.ptr = const_cast(m_schedule_ptr); epoll_ctl(event_fd, EPOLL_CTL_ADD, schedule_fd, &e); #elif defined(__APPLE__) - net::poll_event_t e_timer{.filter = int16_t(io_events::on_timed_out), .flags = EV_ADD | EV_ENABLE, .udata = const_cast(m_timer_ptr)}; - net::poll_event_t e_shutdown{.filter = int16_t(io_events::on_timed_out), .flags = EV_ADD | EV_CLEAR, .udata = const_cast(m_shutdown_ptr)}; - net::poll_event_t e_schedule{.filter = int16_t(io_events::on_work_complete), .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; + net::poll_event_t e_timer{.ident = uintptr_t(io_events::on_timed_out), .filter = EVFILT_TIMER, .flags = EV_ADD | EV_ENABLE, .udata = const_cast(m_timer_ptr)}; + net::poll_event_t e_shutdown{.ident = uintptr_t(io_events::on_shutdown), .filter = EVFILT_USER, .flags = EV_ADD | EV_CLEAR, .udata = const_cast(m_shutdown_ptr)}; + net::poll_event_t e_schedule{.ident = uintptr_t(io_events::on_work_complete), .filter = EVFILT_READ, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; ::kevent(event_fd, &e_schedule, 1, nullptr, 0, nullptr); ::kevent(event_fd, &e_shutdown, 1, nullptr, 0, nullptr); @@ -349,7 +349,7 @@ namespace glz std::cerr << "epoll ctl error on fd " << fd << "\n"; } #elif defined(__APPLE__) - //e.udata = &poll_info; + e.udata = &poll_info; EV_SET(&e, fd, EVFILT_READ, EV_ADD | EV_ONESHOT | EV_EOF, 0, 0, &poll_info); if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { std::cerr << "kqueue ctl error on fd " << fd << "\n"; @@ -456,7 +456,7 @@ namespace glz auto written = ::write(shutdown_fd, &value, sizeof(value)); (void)written; #elif defined(__APPLE__) - net::poll_event_t e{.filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_shutdown_ptr)}; + net::poll_event_t e{.ident = uintptr_t(io_events::on_shutdown), .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_shutdown_ptr)}; if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { GLZ_THROW_OR_ABORT(std::runtime_error("Failed to signal shutdown event")); } @@ -818,12 +818,12 @@ namespace glz std::cerr << "Failed to set timerfd errorno=[" << std::string{std::strerror(errno)} << "]."; } #elif defined(__APPLE__) - [[maybe_unused]] size_t milliseconds{}; + size_t milliseconds{}; if (tp > now) { milliseconds = std::chrono::duration_cast(tp - now).count(); } - net::poll_event_t e{.filter = uint16_t(io_events::on_timed_out), .fflags = NOTE_TRIGGER, .udata = const_cast(m_timer_ptr)}; + net::poll_event_t e{.ident = uintptr_t(io_events::on_timed_out), .filter = EVFILT_TIMER, .fflags = NOTE_TRIGGER, .data = int64_t(milliseconds), .udata = const_cast(m_timer_ptr)}; if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "Error: kevent (update timer).\n"; } @@ -839,11 +839,9 @@ namespace glz std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; } #elif defined(__APPLE__) - struct kevent e - {}; - EV_SET(&e, 1, EVFILT_TIMER, NOTE_TRIGGER, 0, 0, nullptr); - if (::kevent(timer_fd, &e, 1, NULL, 0, NULL) == -1) { - std::cerr << "kevent (update timer)\n"; + net::poll_event_t e{.ident = uintptr_t(io_events::on_timed_out), .filter = EVFILT_TIMER, .fflags = NOTE_TRIGGER, .data = 0, .udata = const_cast(m_timer_ptr)}; + if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { + std::cerr << "Error: kevent (update timer).\n"; } #endif } From 95441d6b657f3d9c1063ebab1d95dfcee39ae2a1 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 10:47:38 -0500 Subject: [PATCH 197/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 8c68e9a92b..0a16d26502 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -666,44 +666,44 @@ namespace glz std::array m_events{}; std::vector> m_handles_to_resume{}; - void process_event_execute(poll_info* pi, poll_status status) + void process_event_execute(glz::poll_info* poll_info, poll_status status) { - if (not pi) { + if (not poll_info) { GLZ_THROW_OR_ABORT(std::runtime_error{"invalid poll_info"}); } - if (not pi->m_processed) { + if (not poll_info->m_processed) { std::atomic_thread_fence(std::memory_order::acquire); // Its possible the event and the timeout occurred in the same epoll, make sure only one // is ever processed, the other is discarded. - pi->m_processed = true; + poll_info->m_processed = true; // Given a valid fd always remove it from epoll so the next poll can blindly EPOLL_CTL_ADD. - if (pi->m_fd != -1) { + if (poll_info->m_fd != -1) { #if defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); #elif defined(__APPLE__) struct kevent e {}; - EV_SET(&e, pi->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + EV_SET(&e, poll_info->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { - std::cerr << "Failed to remove fd " << pi->m_fd << " from kqueue\n"; + std::cerr << "Failed to remove fd " << poll_info->m_fd << " from kqueue\n"; } #endif } // Since this event triggered, remove its corresponding timeout if it has one. - if (pi->m_timer_pos.has_value()) { - remove_timer_token(pi->m_timer_pos.value()); + if (poll_info->m_timer_pos.has_value()) { + remove_timer_token(poll_info->m_timer_pos.value()); } - pi->m_poll_status = status; + poll_info->m_poll_status = status; - while (pi->m_awaiting_coroutine == nullptr) { + while (poll_info->m_awaiting_coroutine == nullptr) { std::atomic_thread_fence(std::memory_order::acquire); } - m_handles_to_resume.emplace_back(pi->m_awaiting_coroutine); + m_handles_to_resume.emplace_back(poll_info->m_awaiting_coroutine); } } From 4cc310d921a5f89daba5d1cdbe6e7dc5b27ebeb4 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 11:29:58 -0500 Subject: [PATCH 198/309] working! --- include/glaze/coroutine/io_scheduler.hpp | 48 +++++++++++------------- include/glaze/network/core.hpp | 12 +++--- tests/coroutine_test/coroutine_test.cpp | 2 + 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 0a16d26502..c5eca5a616 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -43,7 +43,7 @@ namespace glz enum struct io_events : int16_t { on_timed_out = 3000, on_shutdown, - on_work_complete, + on_wake_up, }; enum struct thread_strategy_t { @@ -119,9 +119,9 @@ namespace glz e.data.ptr = const_cast(m_schedule_ptr); epoll_ctl(event_fd, EPOLL_CTL_ADD, schedule_fd, &e); #elif defined(__APPLE__) - net::poll_event_t e_timer{.ident = uintptr_t(io_events::on_timed_out), .filter = EVFILT_TIMER, .flags = EV_ADD | EV_ENABLE, .udata = const_cast(m_timer_ptr)}; + net::poll_event_t e_timer{.ident = uintptr_t(io_events::on_timed_out), .filter = EVFILT_TIMER, .flags = EV_ADD, .udata = const_cast(m_timer_ptr)}; net::poll_event_t e_shutdown{.ident = uintptr_t(io_events::on_shutdown), .filter = EVFILT_USER, .flags = EV_ADD | EV_CLEAR, .udata = const_cast(m_shutdown_ptr)}; - net::poll_event_t e_schedule{.ident = uintptr_t(io_events::on_work_complete), .filter = EVFILT_READ, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; + net::poll_event_t e_schedule{.ident = uintptr_t(io_events::on_wake_up), .filter = EVFILT_USER, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; ::kevent(event_fd, &e_schedule, 1, nullptr, 0, nullptr); ::kevent(event_fd, &e_shutdown, 1, nullptr, 0, nullptr); @@ -147,17 +147,17 @@ namespace glz m_io_thread.join(); } - if (event_fd != net::invalid_ident) { + if (event_fd != net::invalid_file_handle) { #if defined(__linux__) close(event_fd); #endif - event_fd = net::invalid_ident; + event_fd = net::invalid_file_handle; } if (timer_fd != net::invalid_ident) { #if defined(__linux__) close(timer_fd); #endif - timer_fd = -1; + timer_fd = net::invalid_ident; } if (schedule_fd != net::invalid_ident) { #if defined(__linux__) @@ -214,8 +214,10 @@ namespace glz eventfd_t value{1}; eventfd_write(m_scheduler.schedule_fd, value); #elif defined(__APPLE__) - struct kevent e; - ::kevent(m_scheduler.schedule_fd, NULL, 0, &e, 1, NULL); + net::poll_event_t e{.ident = uintptr_t(io_events::on_wake_up), .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; + if (::kevent(m_scheduler.event_fd, &e, 1, NULL, 0, NULL) == -1) { + GLZ_THROW_OR_ABORT(std::runtime_error("Failed to trigger wke up")); + } #endif } } @@ -408,8 +410,10 @@ namespace glz eventfd_t value{1}; eventfd_write(schedule_fd, value); #elif defined(__APPLE__) - struct kevent e; - ::kevent(schedule_fd, NULL, 0, &e, 1, NULL); + net::poll_event_t e{.ident = uintptr_t(io_events::on_wake_up), .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; + if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + GLZ_THROW_OR_ABORT(std::runtime_error("Failed to trigger wke up")); + } #endif } @@ -475,11 +479,11 @@ namespace glz /// The event loop epoll file descriptor. net::file_handle_t event_fd{net::invalid_file_handle}; /// The event loop fd to trigger a shutdown. - net::ident_t shutdown_fd{net::invalid_file_handle}; + net::ident_t shutdown_fd{net::invalid_ident}; /// The event loop timer fd for timed events, e.g. yield_for() or scheduler_after(). - net::ident_t timer_fd{net::invalid_file_handle}; + net::ident_t timer_fd{net::invalid_ident}; /// The schedule file descriptor if the scheduler is in inline processing mode. - net::ident_t schedule_fd{net::invalid_file_handle}; + net::ident_t schedule_fd{net::invalid_ident}; std::atomic m_schedule_fd_triggered{false}; /// The number of tasks executing or awaiting events in this io scheduler. @@ -632,9 +636,6 @@ namespace glz eventfd_t value{0}; eventfd_read(schedule_fd, &value); #elif defined(__APPLE__) - struct kevent change; - EV_SET(&change, schedule_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, nullptr); - kevent(schedule_fd, &change, 1, nullptr, 0, nullptr); #endif // Clear the in memory flag to reduce eventfd_* calls on scheduling. @@ -679,7 +680,7 @@ namespace glz poll_info->m_processed = true; // Given a valid fd always remove it from epoll so the next poll can blindly EPOLL_CTL_ADD. - if (poll_info->m_fd != -1) { + if (poll_info->m_fd != net::invalid_file_handle) { #if defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); #elif defined(__APPLE__) @@ -735,20 +736,15 @@ namespace glz pi->m_processed = true; // Since this timed out, remove its corresponding event if it has one. - if (pi->m_fd != -1) { + if (pi->m_fd != net::invalid_file_handle) { #if defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); #elif defined(__APPLE__) - struct kevent e - {}; - - // Initialize the kevent structure for deletion - EV_SET(&e, pi->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); - - // Remove the event from the kqueue + GLZ_THROW_OR_ABORT(std::runtime_error("TODO: Implement")); + /*net::poll_event_t e{.filter = EVFILT_READ, .flags = EV_DELETE}; if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "Failed to remove fd " << pi->m_fd << " from kqueue\n"; - } + }*/ #endif } diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index f03c9f5329..5127634106 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -44,7 +44,6 @@ namespace glz::net { #ifdef _WIN32 using file_handle_t = unsigned int; - constexpr unsigned int invalid_file_handle = 0; using ssize_t = int64_t; #else using file_handle_t = int; @@ -52,15 +51,16 @@ namespace glz::net #if defined(__APPLE__) using poll_event_t = struct kevent; - using ident_t = uint16_t; - constexpr ident_t invalid_file_handle = ~1; // set all bits - constexpr uintptr_t invalid_ident = 0; + constexpr int invalid_file_handle = -1; + using ident_t = uintptr_t; + constexpr uintptr_t invalid_ident = ~uintptr_t(0); // set all bits #elif defined(__linux__) using poll_event_t = struct epoll_event; - using ident_t = int; - constexpr int invalid_ident_handle = -1; constexpr int invalid_file_handle = -1; + using ident_t = int; + constexpr int invalid_ident = -1; #elif defined(_WIN32) + constexpr unsigned int invalid_file_handle = 0; #endif #if defined(__APPLE__) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 65b5f803e4..3ed15f86c0 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -109,6 +109,7 @@ suite when_all = [] { }; suite event = [] { + std::cout << "\nEvent test:\n"; glz::event e; // These tasks will wait until the given event has been set before advancing. @@ -132,6 +133,7 @@ suite event = [] { }; suite latch = [] { + std::cout << "\nLatch test:\n"; // Complete worker tasks faster on a thread pool, using the io_scheduler version so the worker // tasks can yield for a specific amount of time to mimic difficult work. The pool is only // setup with a single thread to showcase yield_for(). From caa516fca027497daf5ac7d701da687449c07b28 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 11:31:54 -0500 Subject: [PATCH 199/309] remove io_events --- include/glaze/coroutine/io_scheduler.hpp | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index c5eca5a616..c2c66ac5f1 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -39,12 +39,6 @@ namespace glz using clock = std::chrono::steady_clock; using time_point = clock::time_point; using timed_events = poll_info::timed_events; - - enum struct io_events : int16_t { - on_timed_out = 3000, - on_shutdown, - on_wake_up, - }; enum struct thread_strategy_t { /// Spawns a dedicated background thread for the scheduler to run on. @@ -119,9 +113,9 @@ namespace glz e.data.ptr = const_cast(m_schedule_ptr); epoll_ctl(event_fd, EPOLL_CTL_ADD, schedule_fd, &e); #elif defined(__APPLE__) - net::poll_event_t e_timer{.ident = uintptr_t(io_events::on_timed_out), .filter = EVFILT_TIMER, .flags = EV_ADD, .udata = const_cast(m_timer_ptr)}; - net::poll_event_t e_shutdown{.ident = uintptr_t(io_events::on_shutdown), .filter = EVFILT_USER, .flags = EV_ADD | EV_CLEAR, .udata = const_cast(m_shutdown_ptr)}; - net::poll_event_t e_schedule{.ident = uintptr_t(io_events::on_wake_up), .filter = EVFILT_USER, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; + net::poll_event_t e_timer{.filter = EVFILT_TIMER, .flags = EV_ADD, .udata = const_cast(m_timer_ptr)}; + net::poll_event_t e_shutdown{.filter = EVFILT_USER, .flags = EV_ADD | EV_CLEAR, .udata = const_cast(m_shutdown_ptr)}; + net::poll_event_t e_schedule{.filter = EVFILT_USER, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; ::kevent(event_fd, &e_schedule, 1, nullptr, 0, nullptr); ::kevent(event_fd, &e_shutdown, 1, nullptr, 0, nullptr); @@ -214,7 +208,7 @@ namespace glz eventfd_t value{1}; eventfd_write(m_scheduler.schedule_fd, value); #elif defined(__APPLE__) - net::poll_event_t e{.ident = uintptr_t(io_events::on_wake_up), .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; + net::poll_event_t e{.filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; if (::kevent(m_scheduler.event_fd, &e, 1, NULL, 0, NULL) == -1) { GLZ_THROW_OR_ABORT(std::runtime_error("Failed to trigger wke up")); } @@ -410,7 +404,7 @@ namespace glz eventfd_t value{1}; eventfd_write(schedule_fd, value); #elif defined(__APPLE__) - net::poll_event_t e{.ident = uintptr_t(io_events::on_wake_up), .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; + net::poll_event_t e{.filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { GLZ_THROW_OR_ABORT(std::runtime_error("Failed to trigger wke up")); } @@ -460,7 +454,7 @@ namespace glz auto written = ::write(shutdown_fd, &value, sizeof(value)); (void)written; #elif defined(__APPLE__) - net::poll_event_t e{.ident = uintptr_t(io_events::on_shutdown), .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_shutdown_ptr)}; + net::poll_event_t e{.filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_shutdown_ptr)}; if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { GLZ_THROW_OR_ABORT(std::runtime_error("Failed to signal shutdown event")); } @@ -819,7 +813,7 @@ namespace glz milliseconds = std::chrono::duration_cast(tp - now).count(); } - net::poll_event_t e{.ident = uintptr_t(io_events::on_timed_out), .filter = EVFILT_TIMER, .fflags = NOTE_TRIGGER, .data = int64_t(milliseconds), .udata = const_cast(m_timer_ptr)}; + net::poll_event_t e{.filter = EVFILT_TIMER, .fflags = NOTE_TRIGGER, .data = int64_t(milliseconds), .udata = const_cast(m_timer_ptr)}; if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "Error: kevent (update timer).\n"; } @@ -835,7 +829,7 @@ namespace glz std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; } #elif defined(__APPLE__) - net::poll_event_t e{.ident = uintptr_t(io_events::on_timed_out), .filter = EVFILT_TIMER, .fflags = NOTE_TRIGGER, .data = 0, .udata = const_cast(m_timer_ptr)}; + net::poll_event_t e{.filter = EVFILT_TIMER, .fflags = NOTE_TRIGGER, .data = 0, .udata = const_cast(m_timer_ptr)}; if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "Error: kevent (update timer).\n"; } From e35ab4ffb2b6d0b2daa49e24b447859c4bd4ce04 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 11:48:43 -0500 Subject: [PATCH 200/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index c2c66ac5f1..ee4d6b99fc 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -337,19 +337,18 @@ namespace glz poll_info.m_timer_pos = add_timer_token(clock::now() + timeout, poll_info); } - [[maybe_unused]] net::poll_event_t e{}; #if defined(__linux__) + net::poll_event_t e{}; e.events = uint32_t(op) | EPOLLONESHOT | EPOLLRDHUP; e.data.ptr = &poll_info; if (epoll_ctl(event_fd, EPOLL_CTL_ADD, fd, &e) == -1) { std::cerr << "epoll ctl error on fd " << fd << "\n"; } #elif defined(__APPLE__) - e.udata = &poll_info; - EV_SET(&e, fd, EVFILT_READ, EV_ADD | EV_ONESHOT | EV_EOF, 0, 0, &poll_info); - if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { - std::cerr << "kqueue ctl error on fd " << fd << "\n"; - } + net::poll_event_t e{.ident = uintptr_t(fd), .filter = EVFILT_READ, .flags = EV_ADD | EV_EOF, .udata = &poll_info}; + if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + std::cerr << "kqueue failed to register for fd: " << fd << "\n"; + } #endif // The event loop will 'clean-up' whichever event didn't win since the coroutine is scheduled @@ -406,7 +405,7 @@ namespace glz #elif defined(__APPLE__) net::poll_event_t e{.filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { - GLZ_THROW_OR_ABORT(std::runtime_error("Failed to trigger wke up")); + GLZ_THROW_OR_ABORT(std::runtime_error("Failed to trigger wake up")); } #endif } @@ -676,14 +675,8 @@ namespace glz // Given a valid fd always remove it from epoll so the next poll can blindly EPOLL_CTL_ADD. if (poll_info->m_fd != net::invalid_file_handle) { #if defined(__linux__) - epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); + epoll_ctl(event_fd, EPOLL_CTL_DEL, poll_info->m_fd, nullptr); #elif defined(__APPLE__) - struct kevent e - {}; - EV_SET(&e, poll_info->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); - if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { - std::cerr << "Failed to remove fd " << poll_info->m_fd << " from kqueue\n"; - } #endif } From e5ce1cb6d3f5fbdc9eee1600324b61fd2111c366 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 11:52:24 -0500 Subject: [PATCH 201/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index ee4d6b99fc..69b6d65452 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -727,17 +727,12 @@ namespace glz #if defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); #elif defined(__APPLE__) - GLZ_THROW_OR_ABORT(std::runtime_error("TODO: Implement")); - /*net::poll_event_t e{.filter = EVFILT_READ, .flags = EV_DELETE}; - if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { - std::cerr << "Failed to remove fd " << pi->m_fd << " from kqueue\n"; - }*/ #endif } - while (pi->m_awaiting_coroutine == nullptr) { + while (not pi->m_awaiting_coroutine) { std::atomic_thread_fence(std::memory_order::acquire); - // std::cerr << "process_event_execute() has a nullptr event\n"; + std::cerr << "process_event_execute() has a null event\n"; } m_handles_to_resume.emplace_back(pi->m_awaiting_coroutine); From 6e00ff9a62cec812ae663632977893663acf4449 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 11:54:50 -0500 Subject: [PATCH 202/309] Update core.hpp --- include/glaze/network/core.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 5127634106..67231e7434 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -12,6 +12,8 @@ #endif #endif +#include + #if defined(_WIN32) #include #include From e041c436f52c67efaf4bb47ebdd85c7fcb2ba180 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 11:56:02 -0500 Subject: [PATCH 203/309] Update core.hpp --- include/glaze/network/core.hpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 67231e7434..0abb6aaa7d 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -12,8 +12,6 @@ #endif #endif -#include - #if defined(_WIN32) #include #include @@ -74,11 +72,6 @@ namespace glz::net #elif defined(_WIN32) #endif - inline uintptr_t unique_identifier() noexcept { - static std::atomic value{1}; - return value.fetch_add(1, std::memory_order_relaxed); - } - inline auto close_socket(file_handle_t fd) { #ifdef _WIN32 ::closesocket(fd); @@ -107,7 +100,7 @@ namespace glz::net inline ident_t create_shutdown_handle() { #if defined(__APPLE__) - return unique_identifier(); + return invalid_ident; #elif defined(__linux__) return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) @@ -117,7 +110,7 @@ namespace glz::net inline ident_t create_timer_handle() { #if defined(__APPLE__) - return unique_identifier(); + return invalid_ident; #elif defined(__linux__) return ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); #elif defined(_WIN32) @@ -127,7 +120,7 @@ namespace glz::net inline ident_t create_schedule_handle() { #if defined(__APPLE__) - return unique_identifier(); + return invalid_ident; #elif defined(__linux__) return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) From 56540fae6ffebd736cf98592e7f1b5b08ad13c48 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 11:57:42 -0500 Subject: [PATCH 204/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 68 ++++++++++++++---------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 69b6d65452..e1bc4b8eb4 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -113,13 +113,15 @@ namespace glz e.data.ptr = const_cast(m_schedule_ptr); epoll_ctl(event_fd, EPOLL_CTL_ADD, schedule_fd, &e); #elif defined(__APPLE__) - net::poll_event_t e_timer{.filter = EVFILT_TIMER, .flags = EV_ADD, .udata = const_cast(m_timer_ptr)}; - net::poll_event_t e_shutdown{.filter = EVFILT_USER, .flags = EV_ADD | EV_CLEAR, .udata = const_cast(m_shutdown_ptr)}; - net::poll_event_t e_schedule{.filter = EVFILT_USER, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; - - ::kevent(event_fd, &e_schedule, 1, nullptr, 0, nullptr); - ::kevent(event_fd, &e_shutdown, 1, nullptr, 0, nullptr); - ::kevent(event_fd, &e_timer, 1, nullptr, 0, nullptr); + net::poll_event_t e_timer{.filter = EVFILT_TIMER, .flags = EV_ADD, .udata = const_cast(m_timer_ptr)}; + net::poll_event_t e_shutdown{ + .filter = EVFILT_USER, .flags = EV_ADD | EV_CLEAR, .udata = const_cast(m_shutdown_ptr)}; + net::poll_event_t e_schedule{ + .filter = EVFILT_USER, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; + + ::kevent(event_fd, &e_schedule, 1, nullptr, 0, nullptr); + ::kevent(event_fd, &e_shutdown, 1, nullptr, 0, nullptr); + ::kevent(event_fd, &e_timer, 1, nullptr, 0, nullptr); #endif if (m_opts.thread_strategy == thread_strategy_t::spawn) { @@ -208,10 +210,11 @@ namespace glz eventfd_t value{1}; eventfd_write(m_scheduler.schedule_fd, value); #elif defined(__APPLE__) - net::poll_event_t e{.filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; - if (::kevent(m_scheduler.event_fd, &e, 1, NULL, 0, NULL) == -1) { + net::poll_event_t e{ + .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; + if (::kevent(m_scheduler.event_fd, &e, 1, NULL, 0, NULL) == -1) { GLZ_THROW_OR_ABORT(std::runtime_error("Failed to trigger wke up")); - } + } #endif } } @@ -223,7 +226,7 @@ namespace glz /** * no-op as this is the function called first by the thread pool's executing thread. */ - auto await_resume() noexcept -> void {} + void await_resume() noexcept {} }; /** @@ -345,10 +348,11 @@ namespace glz std::cerr << "epoll ctl error on fd " << fd << "\n"; } #elif defined(__APPLE__) - net::poll_event_t e{.ident = uintptr_t(fd), .filter = EVFILT_READ, .flags = EV_ADD | EV_EOF, .udata = &poll_info}; - if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { - std::cerr << "kqueue failed to register for fd: " << fd << "\n"; - } + net::poll_event_t e{ + .ident = uintptr_t(fd), .filter = EVFILT_READ, .flags = EV_ADD | EV_EOF, .udata = &poll_info}; + if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + std::cerr << "kqueue failed to register for fd: " << fd << "\n"; + } #endif // The event loop will 'clean-up' whichever event didn't win since the coroutine is scheduled @@ -403,10 +407,11 @@ namespace glz eventfd_t value{1}; eventfd_write(schedule_fd, value); #elif defined(__APPLE__) - net::poll_event_t e{.filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; - if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + net::poll_event_t e{ + .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; + if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { GLZ_THROW_OR_ABORT(std::runtime_error("Failed to trigger wake up")); - } + } #endif } @@ -453,10 +458,11 @@ namespace glz auto written = ::write(shutdown_fd, &value, sizeof(value)); (void)written; #elif defined(__APPLE__) - net::poll_event_t e{.filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_shutdown_ptr)}; - if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { - GLZ_THROW_OR_ABORT(std::runtime_error("Failed to signal shutdown event")); - } + net::poll_event_t e{ + .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_shutdown_ptr)}; + if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + GLZ_THROW_OR_ABORT(std::runtime_error("Failed to signal shutdown event")); + } #endif if (m_io_thread.joinable()) { @@ -536,7 +542,7 @@ namespace glz const auto event_count = ::epoll_wait(event_fd, m_events.data(), max_events, timeout.count()); #elif defined(_WIN32) #endif - + if (event_count == -1) { net::event_close(event_fd); GLZ_THROW_OR_ABORT(std::runtime_error{"wait for event failed"}); @@ -549,7 +555,7 @@ namespace glz void* handle_ptr = event.data.ptr; #elif defined(__APPLE__) void* handle_ptr = event.udata; - + if (event.flags & EV_ERROR) { GLZ_THROW_OR_ABORT(std::runtime_error{"event error"}); } @@ -568,7 +574,7 @@ namespace glz } else if (handle_ptr == m_shutdown_ptr) [[unlikely]] { // Nothing to do , just needed to wake-up and smell the flowers - std::cout << "Waking up and smelling flowers...\n"; + std::cout << "Waking up and smelling flowers...\n"; } else { // Individual poll task wake-up. @@ -796,12 +802,15 @@ namespace glz std::cerr << "Failed to set timerfd errorno=[" << std::string{std::strerror(errno)} << "]."; } #elif defined(__APPLE__) - size_t milliseconds{}; + size_t milliseconds{}; if (tp > now) { milliseconds = std::chrono::duration_cast(tp - now).count(); } - - net::poll_event_t e{.filter = EVFILT_TIMER, .fflags = NOTE_TRIGGER, .data = int64_t(milliseconds), .udata = const_cast(m_timer_ptr)}; + + net::poll_event_t e{.filter = EVFILT_TIMER, + .fflags = NOTE_TRIGGER, + .data = int64_t(milliseconds), + .udata = const_cast(m_timer_ptr)}; if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "Error: kevent (update timer).\n"; } @@ -817,7 +826,8 @@ namespace glz std::cerr << "Failed to set timerfd errorno=[" << std::string{strerror(errno)} << "]."; } #elif defined(__APPLE__) - net::poll_event_t e{.filter = EVFILT_TIMER, .fflags = NOTE_TRIGGER, .data = 0, .udata = const_cast(m_timer_ptr)}; + net::poll_event_t e{ + .filter = EVFILT_TIMER, .fflags = NOTE_TRIGGER, .data = 0, .udata = const_cast(m_timer_ptr)}; if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "Error: kevent (update timer).\n"; } From 580174bcb119e01b9e3027ceb0fc499e3c4da425 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 12:00:55 -0500 Subject: [PATCH 205/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index e1bc4b8eb4..51649dfb68 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -47,7 +47,7 @@ namespace glz manual }; - enum class execution_strategy_t { + enum struct execution_strategy_t { /// Tasks will be FIFO queued to be executed on a thread pool. This is better for tasks that /// are long lived and will use lots of CPU because long lived tasks will block other i/o /// operations while they complete. This strategy is generally better for lower latency @@ -88,11 +88,7 @@ namespace glz .on_thread_start_functor = nullptr, .on_thread_stop_functor = nullptr}, .execution_strategy = execution_strategy_t::process_tasks_on_thread_pool}) - : m_opts(std::move(opts)), - event_fd(net::create_event_poll()), - shutdown_fd(net::create_shutdown_handle()), - timer_fd(net::create_timer_handle()), - schedule_fd(net::create_schedule_handle()) + : m_opts(std::move(opts)) { if (opts.execution_strategy == execution_strategy_t::process_tasks_on_thread_pool) { m_thread_pool = std::make_unique(std::move(m_opts.pool)); @@ -476,13 +472,13 @@ namespace glz options m_opts; /// The event loop epoll file descriptor. - net::file_handle_t event_fd{net::invalid_file_handle}; + net::file_handle_t event_fd{net::create_event_poll()}; /// The event loop fd to trigger a shutdown. - net::ident_t shutdown_fd{net::invalid_ident}; + net::ident_t shutdown_fd{net::create_shutdown_handle()}; /// The event loop timer fd for timed events, e.g. yield_for() or scheduler_after(). - net::ident_t timer_fd{net::invalid_ident}; + net::ident_t timer_fd{net::create_timer_handle()}; /// The schedule file descriptor if the scheduler is in inline processing mode. - net::ident_t schedule_fd{net::invalid_ident}; + net::ident_t schedule_fd{net::create_schedule_handle()}; std::atomic m_schedule_fd_triggered{false}; /// The number of tasks executing or awaiting events in this io scheduler. From 057e682e9419fdfe89ae01f6fe33558f8cd1ac41 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 12:20:26 -0500 Subject: [PATCH 206/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 92 ++++++++++++------------ 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 51649dfb68..76d0a92a88 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -77,53 +77,13 @@ namespace glz /// rather than scheduling them to be picked up by the thread pool. const execution_strategy_t execution_strategy{execution_strategy_t::process_tasks_on_thread_pool}; }; + + explicit io_scheduler() { + init(); + } - explicit io_scheduler( - options opts = options{.thread_strategy = thread_strategy_t::spawn, - .on_io_thread_start_functor = nullptr, - .on_io_thread_stop_functor = nullptr, - .pool = {.thread_count = ((std::thread::hardware_concurrency() > 1) - ? (std::thread::hardware_concurrency() - 1) - : 1), - .on_thread_start_functor = nullptr, - .on_thread_stop_functor = nullptr}, - .execution_strategy = execution_strategy_t::process_tasks_on_thread_pool}) - : m_opts(std::move(opts)) - { - if (opts.execution_strategy == execution_strategy_t::process_tasks_on_thread_pool) { - m_thread_pool = std::make_unique(std::move(m_opts.pool)); - } - - [[maybe_unused]] net::poll_event_t e{}; - - [[maybe_unused]] bool event_setup_failed{}; -#if defined(__linux__) - e.events = EPOLLIN; - - e.data.ptr = const_cast(m_shutdown_ptr); - epoll_ctl(event_fd, EPOLL_CTL_ADD, shutdown_fd, &e); - - e.data.ptr = const_cast(m_timer_ptr); - epoll_ctl(event_fd, EPOLL_CTL_ADD, timer_fd, &e); - - e.data.ptr = const_cast(m_schedule_ptr); - epoll_ctl(event_fd, EPOLL_CTL_ADD, schedule_fd, &e); -#elif defined(__APPLE__) - net::poll_event_t e_timer{.filter = EVFILT_TIMER, .flags = EV_ADD, .udata = const_cast(m_timer_ptr)}; - net::poll_event_t e_shutdown{ - .filter = EVFILT_USER, .flags = EV_ADD | EV_CLEAR, .udata = const_cast(m_shutdown_ptr)}; - net::poll_event_t e_schedule{ - .filter = EVFILT_USER, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; - - ::kevent(event_fd, &e_schedule, 1, nullptr, 0, nullptr); - ::kevent(event_fd, &e_shutdown, 1, nullptr, 0, nullptr); - ::kevent(event_fd, &e_timer, 1, nullptr, 0, nullptr); -#endif - - if (m_opts.thread_strategy == thread_strategy_t::spawn) { - m_io_thread = std::thread([this]() { process_events_dedicated_thread(); }); - } - // else manual mode, the user must call process_events. + explicit io_scheduler(options opts) : m_opts(std::move(opts)) { + init(); } io_scheduler(const io_scheduler&) = delete; @@ -469,7 +429,7 @@ namespace glz private: /// The configuration options. - options m_opts; + options m_opts{}; /// The event loop epoll file descriptor. net::file_handle_t event_fd{net::create_event_poll()}; @@ -507,6 +467,44 @@ namespace glz m_io_processing.exchange(false, std::memory_order::release); } } + + void init() + { + if (m_opts.execution_strategy == execution_strategy_t::process_tasks_on_thread_pool) { + m_thread_pool = std::make_unique(std::move(m_opts.pool)); + } + + [[maybe_unused]] net::poll_event_t e{}; + + [[maybe_unused]] bool event_setup_failed{}; +#if defined(__linux__) + e.events = EPOLLIN; + + e.data.ptr = const_cast(m_shutdown_ptr); + epoll_ctl(event_fd, EPOLL_CTL_ADD, shutdown_fd, &e); + + e.data.ptr = const_cast(m_timer_ptr); + epoll_ctl(event_fd, EPOLL_CTL_ADD, timer_fd, &e); + + e.data.ptr = const_cast(m_schedule_ptr); + epoll_ctl(event_fd, EPOLL_CTL_ADD, schedule_fd, &e); +#elif defined(__APPLE__) + net::poll_event_t e_timer{.filter = EVFILT_TIMER, .flags = EV_ADD, .udata = const_cast(m_timer_ptr)}; + net::poll_event_t e_shutdown{ + .filter = EVFILT_USER, .flags = EV_ADD | EV_CLEAR, .udata = const_cast(m_shutdown_ptr)}; + net::poll_event_t e_schedule{ + .filter = EVFILT_USER, .flags = EV_ADD, .udata = const_cast(m_schedule_ptr)}; + + ::kevent(event_fd, &e_schedule, 1, nullptr, 0, nullptr); + ::kevent(event_fd, &e_shutdown, 1, nullptr, 0, nullptr); + ::kevent(event_fd, &e_timer, 1, nullptr, 0, nullptr); +#endif + + if (m_opts.thread_strategy == thread_strategy_t::spawn) { + m_io_thread = std::thread([this]() { process_events_dedicated_thread(); }); + } + // else manual mode, the user must call process_events. + } void process_events_dedicated_thread() { From 10eaa87e24d66b42a15a7615bba4c8213816f570 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 12:28:15 -0500 Subject: [PATCH 207/309] cleaning --- include/glaze/coroutine/io_scheduler.hpp | 35 ++++++++---------------- include/glaze/network/core.hpp | 17 +++++++----- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 76d0a92a88..ecd246682f 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -78,18 +78,18 @@ namespace glz const execution_strategy_t execution_strategy{execution_strategy_t::process_tasks_on_thread_pool}; }; - explicit io_scheduler() { + io_scheduler() { init(); } - explicit io_scheduler(options opts) : m_opts(std::move(opts)) { + io_scheduler(options opts) : m_opts(std::move(opts)) { init(); } io_scheduler(const io_scheduler&) = delete; io_scheduler(io_scheduler&&) = delete; - auto operator=(const io_scheduler&) -> io_scheduler& = delete; - auto operator=(io_scheduler&&) -> io_scheduler& = delete; + io_scheduler& operator=(const io_scheduler&) = delete; + io_scheduler& operator=(io_scheduler&&) = delete; ~io_scheduler() { @@ -98,25 +98,12 @@ namespace glz if (m_io_thread.joinable()) { m_io_thread.join(); } - - if (event_fd != net::invalid_file_handle) { -#if defined(__linux__) - close(event_fd); -#endif - event_fd = net::invalid_file_handle; - } - if (timer_fd != net::invalid_ident) { + #if defined(__linux__) - close(timer_fd); + close_file_handle(event_fd); + close_file_handle(timer_fd); + close_file_handle(schedule_fd); #endif - timer_fd = net::invalid_ident; - } - if (schedule_fd != net::invalid_ident) { -#if defined(__linux__) - close(schedule_fd); -#endif - schedule_fd = net::invalid_ident; - } } /** @@ -434,11 +421,11 @@ namespace glz /// The event loop epoll file descriptor. net::file_handle_t event_fd{net::create_event_poll()}; /// The event loop fd to trigger a shutdown. - net::ident_t shutdown_fd{net::create_shutdown_handle()}; + net::file_handle_t shutdown_fd{net::create_shutdown_handle()}; /// The event loop timer fd for timed events, e.g. yield_for() or scheduler_after(). - net::ident_t timer_fd{net::create_timer_handle()}; + net::file_handle_t timer_fd{net::create_timer_handle()}; /// The schedule file descriptor if the scheduler is in inline processing mode. - net::ident_t schedule_fd{net::create_schedule_handle()}; + net::file_handle_t schedule_fd{net::create_schedule_handle()}; std::atomic m_schedule_fd_triggered{false}; /// The number of tasks executing or awaiting events in this io scheduler. diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 0abb6aaa7d..35ebf56ce9 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -72,12 +72,15 @@ namespace glz::net #elif defined(_WIN32) #endif - inline auto close_socket(file_handle_t fd) { + inline auto close_file_handle(file_handle_t& fd) { + if (fd != invalid_file_handle) { #ifdef _WIN32 ::closesocket(fd); #else ::close(fd); #endif + } + fd = invalid_file_handle; } inline auto event_close(file_handle_t fd) { @@ -98,9 +101,9 @@ namespace glz::net #endif } - inline ident_t create_shutdown_handle() { + inline file_handle_t create_shutdown_handle() { #if defined(__APPLE__) - return invalid_ident; + return invalid_file_handle; #elif defined(__linux__) return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) @@ -108,9 +111,9 @@ namespace glz::net #endif } - inline ident_t create_timer_handle() { + inline file_handle_t create_timer_handle() { #if defined(__APPLE__) - return invalid_ident; + return invalid_file_handle; #elif defined(__linux__) return ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); #elif defined(_WIN32) @@ -118,9 +121,9 @@ namespace glz::net #endif } - inline ident_t create_schedule_handle() { + inline file_handle_t create_schedule_handle() { #if defined(__APPLE__) - return invalid_ident; + return invalid_file_handle; #elif defined(__linux__) return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) From c99e8c9dcb4f961a3727754797c5b1892e23643f Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 12:29:26 -0500 Subject: [PATCH 208/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index ecd246682f..1d18c41104 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -663,7 +663,6 @@ namespace glz if (poll_info->m_fd != net::invalid_file_handle) { #if defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, poll_info->m_fd, nullptr); -#elif defined(__APPLE__) #endif } @@ -674,7 +673,7 @@ namespace glz poll_info->m_poll_status = status; - while (poll_info->m_awaiting_coroutine == nullptr) { + while (not poll_info->m_awaiting_coroutine) { std::atomic_thread_fence(std::memory_order::acquire); } From f85be999d7ea4e0f7b71c7a20758e695f5cf56a0 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 12:37:21 -0500 Subject: [PATCH 209/309] Update task.hpp --- include/glaze/coroutine/task.hpp | 78 ++++++++++++++++---------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/include/glaze/coroutine/task.hpp b/include/glaze/coroutine/task.hpp index 73d1a39ec5..32efbc33b8 100644 --- a/include/glaze/coroutine/task.hpp +++ b/include/glaze/coroutine/task.hpp @@ -20,7 +20,7 @@ namespace glz { - template + template struct task; namespace detail @@ -30,9 +30,9 @@ namespace glz friend struct final_awaitable; struct final_awaitable { - auto await_ready() const noexcept -> bool { return false; } + bool await_ready() const noexcept { return false; } - template + template auto await_suspend(std::coroutine_handle coroutine) noexcept -> std::coroutine_handle<> { // If there is a continuation call it, otherwise this is the end of the line. @@ -45,7 +45,7 @@ namespace glz } } - auto await_resume() noexcept -> void + void await_resume() noexcept { // no-op } @@ -61,10 +61,10 @@ namespace glz auto continuation(std::coroutine_handle<> continuation) noexcept -> void { m_continuation = continuation; } protected: - std::coroutine_handle<> m_continuation{nullptr}; + std::coroutine_handle<> m_continuation{}; }; - template + template struct promise final : public promise_base { private: @@ -78,11 +78,11 @@ namespace glz }; public: - using task_type = task; - using coroutine_handle = std::coroutine_handle>; - static constexpr bool return_type_is_reference = std::is_reference_v; - using stored_type = std::conditional_t*, - std::remove_const_t>; + using task_type = task; + using coroutine_handle = std::coroutine_handle>; + static constexpr bool return_type_is_reference = std::is_reference_v; + using stored_type = std::conditional_t*, + std::remove_const_t>; using variant_type = std::variant; promise() noexcept {} @@ -94,21 +94,21 @@ namespace glz auto get_return_object() noexcept -> task_type; - template - requires(return_type_is_reference and std::is_constructible_v) or - (not return_type_is_reference and std::is_constructible_v) - auto return_value(value_type&& value) -> void + template + requires(return_type_is_reference and std::is_constructible_v) or + (not return_type_is_reference and std::is_constructible_v) + void return_value(T&& value) { if constexpr (return_type_is_reference) { - return_type ref = static_cast(value); + Return ref = static_cast(value); m_storage.template emplace(std::addressof(ref)); } else { - m_storage.template emplace(std::forward(value)); + m_storage.template emplace(std::forward(value)); } } - auto return_value(stored_type value) -> void + void return_value(stored_type value) requires(not return_type_is_reference) { if constexpr (std::is_move_constructible_v) { @@ -119,16 +119,16 @@ namespace glz } } - auto unhandled_exception() noexcept -> void { new (&m_storage) variant_type(std::current_exception()); } + void unhandled_exception() noexcept { new (&m_storage) variant_type(std::current_exception()); } auto result() & -> decltype(auto) { if (std::holds_alternative(m_storage)) { if constexpr (return_type_is_reference) { - return static_cast(*std::get(m_storage)); + return static_cast(*std::get(m_storage)); } else { - return static_cast(std::get(m_storage)); + return static_cast(std::get(m_storage)); } } else if (std::holds_alternative(m_storage)) { @@ -143,10 +143,10 @@ namespace glz { if (std::holds_alternative(m_storage)) { if constexpr (return_type_is_reference) { - return static_cast>(*std::get(m_storage)); + return static_cast>(*std::get(m_storage)); } else { - return static_cast(std::get(m_storage)); + return static_cast(std::get(m_storage)); } } else if (std::holds_alternative(m_storage)) { @@ -161,13 +161,13 @@ namespace glz { if (std::holds_alternative(m_storage)) { if constexpr (return_type_is_reference) { - return static_cast(*std::get(m_storage)); + return static_cast(*std::get(m_storage)); } - else if constexpr (std::is_move_constructible_v) { - return static_cast(std::get(m_storage)); + else if constexpr (std::is_move_constructible_v) { + return static_cast(std::get(m_storage)); } else { - return static_cast(std::get(m_storage)); + return static_cast(std::get(m_storage)); } } else if (std::holds_alternative(m_storage)) { @@ -209,23 +209,23 @@ namespace glz } private: - std::exception_ptr m_exception_ptr{nullptr}; + std::exception_ptr m_exception_ptr{}; }; } // namespace detail - template + template struct [[nodiscard]] task { - using task_type = task; - using promise_type = detail::promise; + using task_type = task; + using promise_type = detail::promise; using coroutine_handle = std::coroutine_handle; struct awaitable_base { awaitable_base(coroutine_handle coroutine) noexcept : m_coroutine(coroutine) {} - auto await_ready() const noexcept -> bool { return !m_coroutine || m_coroutine.done(); } + bool await_ready() const noexcept { return !m_coroutine || m_coroutine.done(); } auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> std::coroutine_handle<> { @@ -233,7 +233,7 @@ namespace glz return m_coroutine; } - std::coroutine_handle m_coroutine{nullptr}; + std::coroutine_handle m_coroutine{}; }; task() noexcept : m_coroutine(nullptr) {} @@ -267,7 +267,7 @@ namespace glz /** * @return True if the task is in its final suspend or if the task has been destroyed. */ - auto is_ready() const noexcept -> bool { return m_coroutine == nullptr || m_coroutine.done(); } + bool is_ready() const noexcept { return m_coroutine == nullptr || m_coroutine.done(); } auto resume() -> bool { @@ -315,18 +315,18 @@ namespace glz auto handle() -> coroutine_handle { return m_coroutine; } private: - coroutine_handle m_coroutine{nullptr}; + coroutine_handle m_coroutine{}; }; namespace detail { - template - inline auto promise::get_return_object() noexcept -> task + template + task promise::get_return_object() noexcept { - return task{coroutine_handle::from_promise(*this)}; + return task{coroutine_handle::from_promise(*this)}; } - inline auto promise::get_return_object() noexcept -> task<> + inline task<> promise::get_return_object() noexcept { return task<>{coroutine_handle::from_promise(*this)}; } From f2e2595ee2c15acd019b8736d2238e549f8331d8 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 12:38:30 -0500 Subject: [PATCH 210/309] remove unset_return_value --- include/glaze/coroutine/sync_wait.hpp | 11 +---------- include/glaze/coroutine/task.hpp | 13 +------------ 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/include/glaze/coroutine/sync_wait.hpp b/include/glaze/coroutine/sync_wait.hpp index 02936ee22d..a3d6dad3f0 100644 --- a/include/glaze/coroutine/sync_wait.hpp +++ b/include/glaze/coroutine/sync_wait.hpp @@ -25,15 +25,6 @@ namespace glz { namespace detail { - struct unset_return_value - { - unset_return_value() {} - unset_return_value(unset_return_value&&) = delete; - unset_return_value(const unset_return_value&) = delete; - auto operator=(unset_return_value&&) = delete; - auto operator=(const unset_return_value&) = delete; - }; - struct sync_wait_event { sync_wait_event(bool initially_set = false) : m_set(initially_set) {} @@ -86,7 +77,7 @@ namespace glz static constexpr bool return_type_is_reference = std::is_reference_v; using stored_type = std::conditional_t*, std::remove_const_t>; - using variant_type = std::variant; + using variant_type = std::variant; sync_wait_task_promise() noexcept = default; sync_wait_task_promise(const sync_wait_task_promise&) = delete; diff --git a/include/glaze/coroutine/task.hpp b/include/glaze/coroutine/task.hpp index 32efbc33b8..fd596eddc8 100644 --- a/include/glaze/coroutine/task.hpp +++ b/include/glaze/coroutine/task.hpp @@ -67,23 +67,12 @@ namespace glz template struct promise final : public promise_base { - private: - struct unset_return_value - { - unset_return_value() {} - unset_return_value(unset_return_value&&) = delete; - unset_return_value(const unset_return_value&) = delete; - auto operator=(unset_return_value&&) = delete; - auto operator=(const unset_return_value&) = delete; - }; - - public: using task_type = task; using coroutine_handle = std::coroutine_handle>; static constexpr bool return_type_is_reference = std::is_reference_v; using stored_type = std::conditional_t*, std::remove_const_t>; - using variant_type = std::variant; + using variant_type = std::variant; promise() noexcept {} promise(const promise&) = delete; From 229bc605f5827cb3e7e0addfdd053b7f5065984d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 12:40:48 -0500 Subject: [PATCH 211/309] Update task.hpp --- include/glaze/coroutine/task.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/glaze/coroutine/task.hpp b/include/glaze/coroutine/task.hpp index fd596eddc8..453a061d56 100644 --- a/include/glaze/coroutine/task.hpp +++ b/include/glaze/coroutine/task.hpp @@ -110,7 +110,7 @@ namespace glz void unhandled_exception() noexcept { new (&m_storage) variant_type(std::current_exception()); } - auto result() & -> decltype(auto) + decltype(auto) result() & { if (std::holds_alternative(m_storage)) { if constexpr (return_type_is_reference) { @@ -128,7 +128,7 @@ namespace glz } } - auto result() const& -> decltype(auto) + decltype(auto) result() const& { if (std::holds_alternative(m_storage)) { if constexpr (return_type_is_reference) { @@ -146,7 +146,7 @@ namespace glz } } - auto result() && -> decltype(auto) + decltype(auto) result() && { if (std::holds_alternative(m_storage)) { if constexpr (return_type_is_reference) { From 0025b24ee630149471cdee0b97595f46fc42c2b3 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 12:42:21 -0500 Subject: [PATCH 212/309] Update task.hpp --- include/glaze/coroutine/task.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/glaze/coroutine/task.hpp b/include/glaze/coroutine/task.hpp index 453a061d56..58d9b41520 100644 --- a/include/glaze/coroutine/task.hpp +++ b/include/glaze/coroutine/task.hpp @@ -258,7 +258,7 @@ namespace glz */ bool is_ready() const noexcept { return m_coroutine == nullptr || m_coroutine.done(); } - auto resume() -> bool + bool resume() { if (!m_coroutine.done()) { m_coroutine.resume(); @@ -266,7 +266,7 @@ namespace glz return !m_coroutine.done(); } - auto destroy() -> bool + bool destroy() { if (m_coroutine) { m_coroutine.destroy(); From 3b8b22eef55740ff5c8a37da5a233ed8c50b10ad Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 13:14:21 -0500 Subject: [PATCH 213/309] adding coroutine mutex --- include/glaze/coroutine.hpp | 1 + include/glaze/coroutine/mutex.hpp | 223 ++++++++++++++++++++++++ tests/coroutine_test/coroutine_test.cpp | 35 ++++ 3 files changed, 259 insertions(+) create mode 100644 include/glaze/coroutine/mutex.hpp diff --git a/include/glaze/coroutine.hpp b/include/glaze/coroutine.hpp index 5188a3e03e..b9be17711e 100644 --- a/include/glaze/coroutine.hpp +++ b/include/glaze/coroutine.hpp @@ -7,6 +7,7 @@ #include "glaze/coroutine/generator.hpp" #include "glaze/coroutine/io_scheduler.hpp" #include "glaze/coroutine/latch.hpp" +#include "glaze/coroutine/mutex.hpp" #include "glaze/coroutine/sync_wait.hpp" #include "glaze/coroutine/task.hpp" #include "glaze/coroutine/thread_pool.hpp" diff --git a/include/glaze/coroutine/mutex.hpp b/include/glaze/coroutine/mutex.hpp new file mode 100644 index 0000000000..7353a43c5a --- /dev/null +++ b/include/glaze/coroutine/mutex.hpp @@ -0,0 +1,223 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include +#include +#include +#include + +namespace glz +{ + struct mutex; + + /** + * A scoped RAII lock holder, just like std::lock_guard or std::scoped_lock in that the coro::mutex + * is always unlocked unpon this coro::scoped_lock going out of scope. It is possible to unlock the + * coro::mutex prior to the end of its current scope by manually calling the unlock() function. + */ + struct scoped_lock + { + friend struct mutex; + + public: + enum struct lock_strategy { + /// The lock is already acquired, adopt it as the new owner. + adopt + }; + + explicit scoped_lock(mutex& m, lock_strategy strategy = lock_strategy::adopt) : m_mutex(&m) + { + // Future -> support acquiring the lock? Not sure how to do that without being able to + // co_await in the constructor. + (void)strategy; + } + + /** + * Unlocks the mutex upon this shared lock destructing. + */ + ~scoped_lock() + { + unlock(); + } + + scoped_lock(const scoped_lock&) = delete; + scoped_lock(scoped_lock&& other) : m_mutex(std::exchange(other.m_mutex, nullptr)) {} + auto operator=(const scoped_lock&) -> scoped_lock& = delete; + auto operator=(scoped_lock&& other) noexcept -> scoped_lock& + { + if (std::addressof(other) != this) { + m_mutex = std::exchange(other.m_mutex, nullptr); + } + return *this; + } + + /** + * Unlocks the scoped lock prior to it going out of scope. Calling this multiple times has no + * additional affect after the first call. + */ + void unlock(); + + private: + mutex* m_mutex{}; + }; + + struct mutex + { + explicit mutex() noexcept : m_state(const_cast(unlocked_value())) {} + ~mutex() = default; + + mutex(const mutex&) = delete; + mutex(mutex&&) = delete; + auto operator=(const mutex&) -> mutex& = delete; + auto operator=(mutex&&) -> mutex& = delete; + + struct lock_operation + { + explicit lock_operation(mutex& m) : m_mutex(m) {} + + bool await_ready() const noexcept + { + if (m_mutex.try_lock()) + { + // Since there is no mutex acquired, insert a memory fence to act like it. + std::atomic_thread_fence(std::memory_order::acquire); + return true; + } + return false; + } + + bool await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept + { + m_awaiting_coroutine = awaiting_coroutine; + void* current = m_mutex.m_state.load(std::memory_order::acquire); + void* new_value; + + const void* unlocked_value = m_mutex.unlocked_value(); + do + { + if (current == unlocked_value) + { + // If the current value is 'unlocked' then attempt to lock it. + new_value = nullptr; + } + else + { + // If the current value is a waiting lock operation, or nullptr, set our next to that + // lock op and attempt to set ourself as the head of the waiter list. + m_next = static_cast(current); + new_value = static_cast(this); + } + } while (!m_mutex.m_state.compare_exchange_weak(current, new_value, std::memory_order::acq_rel)); + + // Don't suspend if the state went from unlocked -> locked with zero waiters. + if (current == unlocked_value) + { + std::atomic_thread_fence(std::memory_order::acquire); + m_awaiting_coroutine = nullptr; // nothing to await later since this doesn't suspend + return false; + } + + return true; + } + + scoped_lock await_resume() noexcept { return scoped_lock{m_mutex}; } + + private: + friend struct mutex; + + mutex& m_mutex; + std::coroutine_handle<> m_awaiting_coroutine; + lock_operation* m_next{nullptr}; + }; + + /** + * To acquire the mutex's lock co_await this function. Upon acquiring the lock it returns + * a coro::scoped_lock which will hold the mutex until the coro::scoped_lock destructs. + * @return A co_await'able operation to acquire the mutex. + */ + [[nodiscard]] auto lock() -> lock_operation { return lock_operation{*this}; }; + + /** + * Attempts to lock the mutex. + * @return True if the mutex lock was acquired, otherwise false. + */ + bool try_lock() + { + void* expected = const_cast(unlocked_value()); + return m_state.compare_exchange_strong(expected, nullptr, std::memory_order::acq_rel, std::memory_order::relaxed); + } + + /** + * Releases the mutex's lock. + */ + void unlock() + { + if (m_internal_waiters == nullptr) + { + void* current = m_state.load(std::memory_order::relaxed); + if (current == nullptr) + { + // If there are no internal waiters and there are no atomic waiters, attempt to set the + // mutex as unlocked. + if (m_state.compare_exchange_strong( + current, + const_cast(unlocked_value()), + std::memory_order::release, + std::memory_order::relaxed)) + { + return; // The mutex is now unlocked with zero waiters. + } + // else we failed to unlock, someone added themself as a waiter. + } + + // There are waiters on the atomic list, acquire them and update the state for all others. + m_internal_waiters = static_cast(m_state.exchange(nullptr, std::memory_order::acq_rel)); + + // Should internal waiters be reversed to allow for true FIFO, or should they be resumed + // in this reverse order to maximum throuhgput? If this list ever gets 'long' the reversal + // will take some time, but it might guarantee better latency across waiters. This LIFO + // middle ground on the atomic waiters means the best throughput at the cost of the first + // waiter possibly having added latency based on the queue length of waiters. Either way + // incurs a cost but this way for short lists will most likely be faster even though it + // isn't completely fair. + } + + // assert m_internal_waiters != nullptr + + lock_operation* to_resume = m_internal_waiters; + m_internal_waiters = m_internal_waiters->m_next; + to_resume->m_awaiting_coroutine.resume(); + } + + private: + friend struct lock_operation; + + /// unlocked -> state == unlocked_value() + /// locked but empty waiter list == nullptr + /// locked with waiters == lock_operation* + std::atomic m_state; + + /// A list of grabbed internal waiters that are only accessed by the unlock()'er. + lock_operation* m_internal_waiters{nullptr}; + + /// Inactive value, this cannot be nullptr since we want nullptr to signify that the mutex + /// is locked but there are zero waiters, this makes it easy to CAS new waiters into the + /// m_state linked list. + auto unlocked_value() const noexcept -> const void* { return &m_state; } + }; + + void scoped_lock::unlock() + { + if (m_mutex) + { + std::atomic_thread_fence(std::memory_order::release); + m_mutex->unlock(); + // Only allow a scoped lock to unlock the mutex a single time. + m_mutex = nullptr; + } + } +} diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 3ed15f86c0..f378ef1b21 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -183,4 +183,39 @@ suite latch = [] { glz::sync_wait(glz::when_all(std::move(tasks))); }; +suite mutex_test = [] { + std::cout << "\nMutex test:\n"; + + glz::thread_pool tp{glz::thread_pool::options{.thread_count = 4}}; + std::vector output{}; + glz::mutex mutex; + + auto make_critical_section_task = [&](uint64_t i) -> glz::task { + co_await tp.schedule(); + // To acquire a mutex lock co_await its lock() function. Upon acquiring the lock the + // lock() function returns a coro::scoped_lock that holds the mutex and automatically + // unlocks the mutex upon destruction. This behaves just like std::scoped_lock. + { + auto scoped_lock = co_await mutex.lock(); + output.emplace_back(i); + } // <-- scoped lock unlocks the mutex here. + co_return; + }; + + const size_t num_tasks{100}; + std::vector> tasks{}; + tasks.reserve(num_tasks); + for (size_t i = 1; i <= num_tasks; ++i) { + tasks.emplace_back(make_critical_section_task(i)); + } + + glz::sync_wait(glz::when_all(std::move(tasks))); + + // The output will be variable per run depending on how the tasks are picked up on the + // thread pool workers. + for (const auto& value : output) { + std::cout << value << ", "; + } +}; + int main() { return 0; } From f5cef0ba0aba447e3fb51244d8e66663d9c28dc5 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 13:16:43 -0500 Subject: [PATCH 214/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 1d18c41104..9f797addab 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -100,9 +100,9 @@ namespace glz } #if defined(__linux__) - close_file_handle(event_fd); - close_file_handle(timer_fd); - close_file_handle(schedule_fd); + net::close_file_handle(event_fd); + net::close_file_handle(timer_fd); + net::close_file_handle(schedule_fd); #endif } From 70f886061f78420f8c2ecd78f165d7128e7e080c Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 13:27:39 -0500 Subject: [PATCH 215/309] Adding coroutine shared_mutex.hpp --- include/glaze/coroutine.hpp | 1 + include/glaze/coroutine/shared_mutex.hpp | 340 +++++++++++++++++++++++ tests/coroutine_test/coroutine_test.cpp | 50 ++++ 3 files changed, 391 insertions(+) create mode 100644 include/glaze/coroutine/shared_mutex.hpp diff --git a/include/glaze/coroutine.hpp b/include/glaze/coroutine.hpp index b9be17711e..1a5797b2a5 100644 --- a/include/glaze/coroutine.hpp +++ b/include/glaze/coroutine.hpp @@ -8,6 +8,7 @@ #include "glaze/coroutine/io_scheduler.hpp" #include "glaze/coroutine/latch.hpp" #include "glaze/coroutine/mutex.hpp" +#include "glaze/coroutine/shared_mutex.hpp" #include "glaze/coroutine/sync_wait.hpp" #include "glaze/coroutine/task.hpp" #include "glaze/coroutine/thread_pool.hpp" diff --git a/include/glaze/coroutine/shared_mutex.hpp b/include/glaze/coroutine/shared_mutex.hpp new file mode 100644 index 0000000000..3aee3c9294 --- /dev/null +++ b/include/glaze/coroutine/shared_mutex.hpp @@ -0,0 +1,340 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#ifndef GLZ_THROW_OR_ABORT +#if __cpp_exceptions +#define GLZ_THROW_OR_ABORT(EXC) (throw(EXC)) +#include +#else +#define GLZ_THROW_OR_ABORT(EXC) (std::abort()) +#endif +#endif + +#include +#include +#include + +#include "glaze/coroutine/concepts.hpp" + +namespace glz +{ + template + struct shared_mutex; + + /** + * A scoped RAII lock holder for a coro::shared_mutex. It will call the appropriate unlock() or + * unlock_shared() based on how the coro::shared_mutex was originally acquired, either shared or + * exclusive modes. + */ + template + struct shared_scoped_lock + { + shared_scoped_lock(shared_mutex& sm, bool exclusive) : m_shared_mutex(&sm), m_exclusive(exclusive) + {} + + /** + * Unlocks the mutex upon this shared scoped lock destructing. + */ + ~shared_scoped_lock() { unlock(); } + + shared_scoped_lock(const shared_scoped_lock&) = delete; + shared_scoped_lock(shared_scoped_lock&& other) + : m_shared_mutex(std::exchange(other.m_shared_mutex, nullptr)), m_exclusive(other.m_exclusive) + {} + + auto operator=(const shared_scoped_lock&) -> shared_scoped_lock& = delete; + auto operator=(shared_scoped_lock&& other) noexcept -> shared_scoped_lock& + { + if (std::addressof(other) != this) { + m_shared_mutex = std::exchange(other.m_shared_mutex, nullptr); + m_exclusive = other.m_exclusive; + } + return *this; + } + + /** + * Unlocks the shared mutex prior to this lock going out of scope. + */ + auto unlock() -> void + { + if (m_shared_mutex != nullptr) { + if (m_exclusive) { + m_shared_mutex->unlock(); + } + else { + m_shared_mutex->unlock_shared(); + } + + m_shared_mutex = nullptr; + } + } + + private: + shared_mutex* m_shared_mutex{nullptr}; + bool m_exclusive{false}; + }; + + template + struct shared_mutex + { + /** + * @param e The executor for when multiple shared waiters can be woken up at the same time, + * each shared waiter will be scheduled to immediately run on this executor in + * parallel. + */ + explicit shared_mutex(std::shared_ptr e) : m_executor(std::move(e)) + { + if (m_executor == nullptr) { + GLZ_THROW_OR_ABORT(std::runtime_error{"shared_mutex cannot have a nullptr executor"}); + } + } + ~shared_mutex() = default; + + shared_mutex(const shared_mutex&) = delete; + shared_mutex(shared_mutex&&) = delete; + auto operator=(const shared_mutex&) -> shared_mutex& = delete; + auto operator=(shared_mutex&&) -> shared_mutex& = delete; + + struct lock_operation + { + lock_operation(shared_mutex& sm, bool exclusive) : m_shared_mutex(sm), m_exclusive(exclusive) {} + + auto await_ready() const noexcept -> bool + { + if (m_exclusive) { + return m_shared_mutex.try_lock(); + } + else { + return m_shared_mutex.try_lock_shared(); + } + } + + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool + { + std::unique_lock lk{m_shared_mutex.m_mutex}; + // Its possible the lock has been released between await_ready() and await_suspend(), double + // check and make sure we are not going to suspend when nobody holds the lock. + if (m_exclusive) { + if (m_shared_mutex.try_lock_locked(lk)) { + return false; + } + } + else { + if (m_shared_mutex.try_lock_shared_locked(lk)) { + return false; + } + } + + // For sure the lock is currently held in a manner that it cannot be acquired, suspend ourself + // at the end of the waiter list. + + if (m_shared_mutex.m_tail_waiter == nullptr) { + m_shared_mutex.m_head_waiter = this; + m_shared_mutex.m_tail_waiter = this; + } + else { + m_shared_mutex.m_tail_waiter->m_next = this; + m_shared_mutex.m_tail_waiter = this; + } + + // If this is an exclusive lock acquire then mark it as so so that shared locks after this + // exclusive one will also suspend so this exclusive lock doens't get starved. + if (m_exclusive) { + ++m_shared_mutex.m_exclusive_waiters; + } + + m_awaiting_coroutine = awaiting_coroutine; + return true; + } + auto await_resume() noexcept -> shared_scoped_lock + { + return shared_scoped_lock{m_shared_mutex, m_exclusive}; + } + + private: + friend struct shared_mutex; + + shared_mutex& m_shared_mutex; + bool m_exclusive{false}; + std::coroutine_handle<> m_awaiting_coroutine; + lock_operation* m_next{nullptr}; + }; + + /** + * Locks the mutex in a shared state. If there are any exclusive waiters then the shared waiters + * will also wait so the exclusive waiters are not starved. + */ + [[nodiscard]] auto lock_shared() -> lock_operation { return lock_operation{*this, false}; } + + /** + * Locks the mutex in an exclusive state. + */ + [[nodiscard]] auto lock() -> lock_operation { return lock_operation{*this, true}; } + + /** + * @return True if the lock could immediately be acquired in a shared state. + */ + auto try_lock_shared() -> bool + { + // To acquire the shared lock the state must be one of two states: + // 1) unlocked + // 2) shared locked with zero exclusive waiters + // Zero exclusive waiters prevents exclusive starvation if shared locks are + // always continuously happening. + + std::unique_lock lk{m_mutex}; + return try_lock_shared_locked(lk); + } + + /** + * @return True if the lock could immediately be acquired in an exclusive state. + */ + auto try_lock() -> bool + { + // To acquire the exclusive lock the state must be unlocked. + std::unique_lock lk{m_mutex}; + return try_lock_locked(lk); + } + + /** + * Unlocks a single shared state user. *REQUIRES* that the lock was first acquired exactly once + * via `lock_shared()` or `try_lock_shared() -> True` before being called, otherwise undefined + * behavior. + * + * If the shared user count drops to zero and this lock has an exclusive waiter then the exclusive + * waiter acquires the lock. + */ + auto unlock_shared() -> void + { + std::unique_lock lk{m_mutex}; + --m_shared_users; + + // Only wake waiters from shared state if all shared users have completed. + if (m_shared_users == 0) { + if (m_head_waiter != nullptr) { + wake_waiters(lk); + } + else { + m_state = state::unlocked; + } + } + } + + /** + * Unlocks the mutex from its exclusive state. If there is a following exclusive watier then + * that exclusive waiter acquires the lock. If there are 1 or more shared waiters then all the + * shared waiters acquire the lock in a shared state in parallel and are resumed on the original + * thread pool this shared mutex was created with. + */ + auto unlock() -> void + { + std::unique_lock lk{m_mutex}; + if (m_head_waiter != nullptr) { + wake_waiters(lk); + } + else { + m_state = state::unlocked; + } + } + + private: + friend struct lock_operation; + + enum class state { unlocked, locked_shared, locked_exclusive }; + + /// This executor is for resuming multiple shared waiters. + std::shared_ptr m_executor{nullptr}; + + std::mutex m_mutex; + + state m_state{state::unlocked}; + + /// The current number of shared users that have acquired the lock. + uint64_t m_shared_users{0}; + /// The current number of exclusive waiters waiting to acquire the lock. This is used to block + /// new incoming shared lock attempts so the exclusive waiter is not starved. + uint64_t m_exclusive_waiters{0}; + + lock_operation* m_head_waiter{nullptr}; + lock_operation* m_tail_waiter{nullptr}; + + auto try_lock_shared_locked(std::unique_lock& lk) -> bool + { + if (m_state == state::unlocked) { + // If the shared mutex is unlocked put it into shared mode and add ourself as using the lock. + m_state = state::locked_shared; + ++m_shared_users; + lk.unlock(); + return true; + } + else if (m_state == state::locked_shared && m_exclusive_waiters == 0) { + // If the shared mutex is in a shared locked state and there are no exclusive waiters + // the add ourself as using the lock. + ++m_shared_users; + lk.unlock(); + return true; + } + + // If the lock is in shared mode but there are exclusive waiters then we will also wait so + // the writers are not starved. + + // If the lock is in exclusive mode already then we need to wait. + + return false; + } + + auto try_lock_locked(std::unique_lock& lk) -> bool + { + if (m_state == state::unlocked) { + m_state = state::locked_exclusive; + lk.unlock(); + return true; + } + return false; + } + + auto wake_waiters(std::unique_lock& lk) -> void + { + // First determine what the next lock state will be based on the first waiter. + if (m_head_waiter->m_exclusive) { + // If its exclusive then only this waiter can be woken up. + m_state = state::locked_exclusive; + lock_operation* to_resume = m_head_waiter; + m_head_waiter = m_head_waiter->m_next; + --m_exclusive_waiters; + if (m_head_waiter == nullptr) { + m_tail_waiter = nullptr; + } + + // Since this is an exclusive lock waiting we can resume it directly. + lk.unlock(); + to_resume->m_awaiting_coroutine.resume(); + } + else { + // If its shared then we will scan forward and awake all shared waiters onto the given + // thread pool so they can run in parallel. + m_state = state::locked_shared; + do { + lock_operation* to_resume = m_head_waiter; + m_head_waiter = m_head_waiter->m_next; + if (m_head_waiter == nullptr) { + m_tail_waiter = nullptr; + } + ++m_shared_users; + + m_executor->resume(to_resume->m_awaiting_coroutine); + } while (m_head_waiter != nullptr && !m_head_waiter->m_exclusive); + + // Cannot unlock until the entire set of shared waiters has been traversed. I think this + // makes more sense than allocating space for all the shared waiters, unlocking, and then + // resuming in a batch? + lk.unlock(); + } + } + }; + +} // namespace coro diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index f378ef1b21..e53c66f88c 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -218,4 +218,54 @@ suite mutex_test = [] { } }; +suite shared_mutex_test = [] { + // Shared mutexes require an excutor type to be able to wake up multiple shared waiters when + // there is an exclusive lock holder releasing the lock. This example uses a single thread + // to also show the interleaving of coroutines acquiring the shared lock in shared and + // exclusive mode as they resume and suspend in a linear manner. Ideally the thread pool + // executor would have more than 1 thread to resume all shared waiters in parallel. + auto tp = std::make_shared(glz::thread_pool::options{.thread_count = 1}); + glz::shared_mutex mutex{tp}; + + auto make_shared_task = [&](uint64_t i) -> glz::task { + co_await tp->schedule(); + { + std::cerr << "shared task " << i << " lock_shared()\n"; + auto scoped_lock = co_await mutex.lock_shared(); + std::cerr << "shared task " << i << " lock_shared() acquired\n"; + /// Immediately yield so the other shared tasks also acquire in shared state + /// while this task currently holds the mutex in shared state. + co_await tp->yield(); + std::cerr << "shared task " << i << " unlock_shared()\n"; + } + co_return; + }; + + auto make_exclusive_task = [&]() -> glz::task { + co_await tp->schedule(); + + std::cerr << "exclusive task lock()\n"; + auto scoped_lock = co_await mutex.lock(); + std::cerr << "exclusive task lock() acquired\n"; + // Do the exclusive work.. + std::cerr << "exclusive task unlock()\n"; + co_return; + }; + + // Create 3 shared tasks that will acquire the mutex in a shared state. + const size_t num_tasks{3}; + std::vector> tasks{}; + for (size_t i = 1; i <= num_tasks; ++i) { + tasks.emplace_back(make_shared_task(i)); + } + // Create an exclusive task. + tasks.emplace_back(make_exclusive_task()); + // Create 3 more shared tasks that will be blocked until the exclusive task completes. + for (size_t i = num_tasks + 1; i <= num_tasks * 2; ++i) { + tasks.emplace_back(make_shared_task(i)); + } + + glz::sync_wait(glz::when_all(std::move(tasks))); +}; + int main() { return 0; } From d1c95583787b88760079427f841999709a7c829d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 13:49:35 -0500 Subject: [PATCH 216/309] Added semaphore.hpp --- include/glaze/coroutine.hpp | 1 + include/glaze/coroutine/event.hpp | 2 +- include/glaze/coroutine/poll.hpp | 2 +- include/glaze/coroutine/semaphore.hpp | 208 +++++++++++++++++++++ include/glaze/coroutine/shared_mutex.hpp | 2 +- include/glaze/coroutine/task_container.hpp | 2 +- include/glaze/util/expected.hpp | 4 +- tests/coroutine_test/coroutine_test.cpp | 32 ++++ 8 files changed, 246 insertions(+), 7 deletions(-) create mode 100644 include/glaze/coroutine/semaphore.hpp diff --git a/include/glaze/coroutine.hpp b/include/glaze/coroutine.hpp index 1a5797b2a5..8f4e50b424 100644 --- a/include/glaze/coroutine.hpp +++ b/include/glaze/coroutine.hpp @@ -8,6 +8,7 @@ #include "glaze/coroutine/io_scheduler.hpp" #include "glaze/coroutine/latch.hpp" #include "glaze/coroutine/mutex.hpp" +#include "glaze/coroutine/semaphore.hpp" #include "glaze/coroutine/shared_mutex.hpp" #include "glaze/coroutine/sync_wait.hpp" #include "glaze/coroutine/task.hpp" diff --git a/include/glaze/coroutine/event.hpp b/include/glaze/coroutine/event.hpp index 22b8156be4..d829bda544 100644 --- a/include/glaze/coroutine/event.hpp +++ b/include/glaze/coroutine/event.hpp @@ -12,7 +12,7 @@ namespace glz { - enum class resume_order_policy { + enum struct resume_order_policy { /// Last in first out, this is the default policy and will execute the fastest /// if you do not need the first waiter to execute first upon the event being set. lifo, diff --git a/include/glaze/coroutine/poll.hpp b/include/glaze/coroutine/poll.hpp index 58299524eb..2e42babd89 100644 --- a/include/glaze/coroutine/poll.hpp +++ b/include/glaze/coroutine/poll.hpp @@ -31,7 +31,7 @@ namespace glz } } - enum class poll_status { + enum struct poll_status { /// The poll operation was was successful. event, /// The poll operation timed out. diff --git a/include/glaze/coroutine/semaphore.hpp b/include/glaze/coroutine/semaphore.hpp new file mode 100644 index 0000000000..e0556ce9dc --- /dev/null +++ b/include/glaze/coroutine/semaphore.hpp @@ -0,0 +1,208 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include +#include "glaze/util/expected.hpp" +#include +#include +#include + +namespace glz +{ + struct semaphore + { + enum struct acquire_result { acquired, semaphore_stopped }; + + static std::string to_string(acquire_result ar) + { + switch (ar) { + case acquire_result::acquired: + return "acquired"; + case acquire_result::semaphore_stopped: + return "semaphore_stopped"; + } + + return "unknown"; + } + + explicit semaphore(std::ptrdiff_t least_max_value_and_starting_value) + : semaphore(least_max_value_and_starting_value, least_max_value_and_starting_value) + {} + + explicit semaphore(std::ptrdiff_t least_max_value, std::ptrdiff_t starting_value) + : m_least_max_value(least_max_value), + m_counter(starting_value <= least_max_value ? starting_value : least_max_value) + {} + + + ~semaphore() { + notify_waiters(); + } + + semaphore(const semaphore&) = delete; + semaphore(semaphore&&) = delete; + + auto operator=(const semaphore&) noexcept -> semaphore& = delete; + auto operator=(semaphore&&) noexcept -> semaphore& = delete; + + struct acquire_operation + { + explicit acquire_operation(semaphore& s) : m_semaphore(s) + {} + + bool await_ready() const noexcept + { + if (m_semaphore.m_notify_all_set.load(std::memory_order::relaxed)) + { + return true; + } + return m_semaphore.try_acquire(); + } + + bool await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept + { + std::unique_lock lk{m_semaphore.m_waiter_mutex}; + if (m_semaphore.m_notify_all_set.load(std::memory_order::relaxed)) + { + return false; + } + + if (m_semaphore.try_acquire()) + { + return false; + } + + if (m_semaphore.m_acquire_waiters == nullptr) + { + m_semaphore.m_acquire_waiters = this; + } + else + { + // This is LIFO, but semaphores are not meant to be fair. + + // Set our next to the current head. + m_next = m_semaphore.m_acquire_waiters; + // Set the semaphore head to this. + m_semaphore.m_acquire_waiters = this; + } + + m_awaiting_coroutine = awaiting_coroutine; + return true; + } + + acquire_result await_resume() const + { + if (m_semaphore.m_notify_all_set.load(std::memory_order::relaxed)) + { + return acquire_result::semaphore_stopped; + } + return acquire_result::acquired; + } + + private: + friend semaphore; + + semaphore& m_semaphore; + std::coroutine_handle<> m_awaiting_coroutine; + acquire_operation* m_next{nullptr}; + }; + + void release() + { + // It seems like the atomic counter could be incremented, but then resuming a waiter could have + // a race between a new acquirer grabbing the just incremented resource value from us. So its + // best to check if there are any waiters first, and transfer owernship of the resource thats + // being released directly to the waiter to avoid this problem. + + std::unique_lock lk{m_waiter_mutex}; + if (m_acquire_waiters != nullptr) + { + acquire_operation* to_resume = m_acquire_waiters; + m_acquire_waiters = m_acquire_waiters->m_next; + lk.unlock(); + + // This will transfer ownership of the resource to the resumed waiter. + to_resume->m_awaiting_coroutine.resume(); + } + else + { + // Normally would be release but within a lock use releaxed. + m_counter.fetch_add(1, std::memory_order::relaxed); + } + } + + /** + * Acquires a resource from the semaphore, if the semaphore has no resources available then + * this will wait until a resource becomes available. + */ + [[nodiscard]] acquire_operation acquire() { return acquire_operation{*this}; } + + /** + * Attemtps to acquire a resource if there is any resources available. + * @return True if the acquire operation was able to acquire a resource. + */ + bool try_acquire() + { + // Optimistically grab the resource. + auto previous = m_counter.fetch_sub(1, std::memory_order::acq_rel); + if (previous <= 0) + { + // If it wasn't available undo the acquisition. + m_counter.fetch_add(1, std::memory_order::release); + return false; + } + return true; + } + + /** + * @return The maximum number of resources the semaphore can contain. + */ + std::ptrdiff_t max() const noexcept { return m_least_max_value; } + + /** + * The current number of resources available in this semaphore. + */ + std::ptrdiff_t value() const noexcept { return m_counter.load(std::memory_order::relaxed); } + + /** + * Stops the semaphore and will notify all release/acquire waiters to wake up in a failed state. + * Once this is set it cannot be un-done and all future oprations on the semaphore will fail. + */ + void notify_waiters() noexcept + { + m_notify_all_set.exchange(true, std::memory_order::release); + while (true) + { + std::unique_lock lk{m_waiter_mutex}; + if (m_acquire_waiters != nullptr) + { + acquire_operation* to_resume = m_acquire_waiters; + m_acquire_waiters = m_acquire_waiters->m_next; + lk.unlock(); + + to_resume->m_awaiting_coroutine.resume(); + } + else + { + break; + } + } + } + + private: + friend struct release_operation; + friend struct acquire_operation; + + const std::ptrdiff_t m_least_max_value; + std::atomic m_counter; + + std::mutex m_waiter_mutex{}; + acquire_operation* m_acquire_waiters{nullptr}; + + std::atomic m_notify_all_set{false}; + }; +} diff --git a/include/glaze/coroutine/shared_mutex.hpp b/include/glaze/coroutine/shared_mutex.hpp index 3aee3c9294..3e58e41559 100644 --- a/include/glaze/coroutine/shared_mutex.hpp +++ b/include/glaze/coroutine/shared_mutex.hpp @@ -244,7 +244,7 @@ namespace glz private: friend struct lock_operation; - enum class state { unlocked, locked_shared, locked_exclusive }; + enum struct state { unlocked, locked_shared, locked_exclusive }; /// This executor is for resuming multiple shared waiters. std::shared_ptr m_executor{nullptr}; diff --git a/include/glaze/coroutine/task_container.hpp b/include/glaze/coroutine/task_container.hpp index 1a0e12ab00..8089d07a5c 100644 --- a/include/glaze/coroutine/task_container.hpp +++ b/include/glaze/coroutine/task_container.hpp @@ -67,7 +67,7 @@ namespace glz } } - enum class garbage_collect_t { + enum struct garbage_collect_t { /// Execute garbage collection. yes, /// Do not execute garbage collection. diff --git a/include/glaze/util/expected.hpp b/include/glaze/util/expected.hpp index 43d14bdbf2..ba8d738a8e 100644 --- a/include/glaze/util/expected.hpp +++ b/include/glaze/util/expected.hpp @@ -29,10 +29,8 @@ #ifndef GLZ_THROW_OR_ABORT #if __cpp_exceptions #define GLZ_THROW_OR_ABORT(EXC) (throw(EXC)) -#define GLZ_NOEXCEPT noexcept(false) #else #define GLZ_THROW_OR_ABORT(EXC) (std::abort()) -#define GLZ_NOEXCEPT noexcept(true) #endif #endif @@ -42,7 +40,7 @@ namespace glz { - inline void glaze_error([[maybe_unused]] const char* msg) GLZ_NOEXCEPT + inline void glaze_error([[maybe_unused]] const char* msg) { GLZ_THROW_OR_ABORT(std::runtime_error(msg)); } diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index e53c66f88c..34c8e96045 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -219,6 +219,7 @@ suite mutex_test = [] { }; suite shared_mutex_test = [] { + std::cout << "\nShared Mutex test:\n"; // Shared mutexes require an excutor type to be able to wake up multiple shared waiters when // there is an exclusive lock holder releasing the lock. This example uses a single thread // to also show the interleaving of coroutines acquiring the shared lock in shared and @@ -268,4 +269,35 @@ suite shared_mutex_test = [] { glz::sync_wait(glz::when_all(std::move(tasks))); }; +suite semaphore_test = [] { + std::cout << "\nSemaphore test:\n"; + // Have more threads/tasks than the semaphore will allow for at any given point in time. + glz::thread_pool tp{glz::thread_pool::options{.thread_count = 8}}; + glz::semaphore semaphore{1}; + + auto make_rate_limited_task = [&](uint64_t task_num) -> glz::task { + co_await tp.schedule(); + + // This will only allow 1 task through at any given point in time, all other tasks will + // await the resource to be available before proceeding. + auto result = co_await semaphore.acquire(); + if (result == glz::semaphore::acquire_result::acquired) { + std::cout << task_num << ", "; + semaphore.release(); + } + else { + std::cout << task_num << " failed to acquire semaphore [" << glz::semaphore::to_string(result) << "],"; + } + co_return; + }; + + const size_t num_tasks{100}; + std::vector> tasks{}; + for (size_t i = 1; i <= num_tasks; ++i) { + tasks.emplace_back(make_rate_limited_task(i)); + } + + glz::sync_wait(glz::when_all(std::move(tasks))); +}; + int main() { return 0; } From aed5ed1aa25f7e879c0193c5e35923beb9ec1d64 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 13:57:13 -0500 Subject: [PATCH 217/309] Added ring_buffer.hpp --- include/glaze/coroutine.hpp | 1 + include/glaze/coroutine/ring_buffer.hpp | 301 ++++++++++++++++++++++++ tests/coroutine_test/coroutine_test.cpp | 71 +++++- 3 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 include/glaze/coroutine/ring_buffer.hpp diff --git a/include/glaze/coroutine.hpp b/include/glaze/coroutine.hpp index 8f4e50b424..e4f803c0a5 100644 --- a/include/glaze/coroutine.hpp +++ b/include/glaze/coroutine.hpp @@ -8,6 +8,7 @@ #include "glaze/coroutine/io_scheduler.hpp" #include "glaze/coroutine/latch.hpp" #include "glaze/coroutine/mutex.hpp" +#include "glaze/coroutine/ring_buffer.hpp" #include "glaze/coroutine/semaphore.hpp" #include "glaze/coroutine/shared_mutex.hpp" #include "glaze/coroutine/sync_wait.hpp" diff --git a/include/glaze/coroutine/ring_buffer.hpp b/include/glaze/coroutine/ring_buffer.hpp new file mode 100644 index 0000000000..f700092904 --- /dev/null +++ b/include/glaze/coroutine/ring_buffer.hpp @@ -0,0 +1,301 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include +#include +#include +#include +#include + +#include "glaze/util/expected.hpp" + +namespace glz +{ + namespace rb + { + enum struct produce_result { produced, ring_buffer_stopped }; + + enum struct consume_result { ring_buffer_stopped }; + } + + /** + * @tparam element The type of element the ring buffer will store. Note that this type should be + * cheap to move if possible as it is moved into and out of the buffer upon produce and + * consume operations. + * @tparam num_elements The maximum number of elements the ring buffer can store, must be >= 1. + */ + template + class ring_buffer + { + public: + /** + * static_assert If `num_elements` == 0. + */ + ring_buffer() { static_assert(num_elements != 0, "num_elements cannot be zero"); } + + ~ring_buffer() + { + // Wake up anyone still using the ring buffer. + notify_waiters(); + } + + ring_buffer(const ring_buffer&) = delete; + ring_buffer(ring_buffer&&) = delete; + + auto operator=(const ring_buffer&) noexcept + -> ring_buffer& = delete; + auto operator=(ring_buffer&&) noexcept -> ring_buffer& = delete; + + struct produce_operation + { + produce_operation(ring_buffer& rb, element e) : m_rb(rb), m_e(std::move(e)) {} + + auto await_ready() noexcept -> bool + { + std::unique_lock lk{m_rb.m_mutex}; + return m_rb.try_produce_locked(lk, m_e); + } + + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool + { + std::unique_lock lk{m_rb.m_mutex}; + // Its possible a consumer on another thread consumed an item between await_ready() and await_suspend() + // so we must check to see if there is space again. + if (m_rb.try_produce_locked(lk, m_e)) { + return false; + } + + // Don't suspend if the stop signal has been set. + if (m_rb.m_stopped.load(std::memory_order::acquire)) { + m_stopped = true; + return false; + } + + m_awaiting_coroutine = awaiting_coroutine; + m_next = m_rb.m_produce_waiters; + m_rb.m_produce_waiters = this; + return true; + } + + /** + * @return produce_result + */ + auto await_resume() -> rb::produce_result + { + return !m_stopped ? rb::produce_result::produced : rb::produce_result::ring_buffer_stopped; + } + + private: + template + friend class ring_buffer; + + /// The ring buffer the element is being produced into. + ring_buffer& m_rb; + /// If the operation needs to suspend, the coroutine to resume when the element can be produced. + std::coroutine_handle<> m_awaiting_coroutine; + /// Linked list of produce operations that are awaiting to produce their element. + produce_operation* m_next{nullptr}; + /// The element this produce operation is producing into the ring buffer. + element m_e; + /// Was the operation stopped? + bool m_stopped{false}; + }; + + struct consume_operation + { + explicit consume_operation(ring_buffer& rb) : m_rb(rb) {} + + auto await_ready() noexcept -> bool + { + std::unique_lock lk{m_rb.m_mutex}; + return m_rb.try_consume_locked(lk, this); + } + + auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool + { + std::unique_lock lk{m_rb.m_mutex}; + // We have to check again as there is a race condition between await_ready() and now on the mutex acquire. + // It is possible that a producer added items between await_ready() and await_suspend(). + if (m_rb.try_consume_locked(lk, this)) { + return false; + } + + // Don't suspend if the stop signal has been set. + if (m_rb.m_stopped.load(std::memory_order::acquire)) { + m_stopped = true; + return false; + } + m_awaiting_coroutine = awaiting_coroutine; + m_next = m_rb.m_consume_waiters; + m_rb.m_consume_waiters = this; + return true; + } + + /** + * @return The consumed element or std::nullopt if the consume has failed. + */ + auto await_resume() -> expected + { + if (m_stopped) { + return unexpected(rb::consume_result::ring_buffer_stopped); + } + + return std::move(m_e); + } + + private: + template + friend class ring_buffer; + + /// The ring buffer to consume an element from. + ring_buffer& m_rb; + /// If the operation needs to suspend, the coroutine to resume when the element can be consumed. + std::coroutine_handle<> m_awaiting_coroutine; + /// Linked list of consume operations that are awaiting to consume an element. + consume_operation* m_next{nullptr}; + /// The element this consume operation will consume. + element m_e; + /// Was the operation stopped? + bool m_stopped{false}; + }; + + /** + * Produces the given element into the ring buffer. This operation will suspend until a slot + * in the ring buffer becomes available. + * @param e The element to produce. + */ + [[nodiscard]] auto produce(element e) -> produce_operation { return produce_operation{*this, std::move(e)}; } + + /** + * Consumes an element from the ring buffer. This operation will suspend until an element in + * the ring buffer becomes available. + */ + [[nodiscard]] auto consume() -> consume_operation { return consume_operation{*this}; } + + /** + * @return The current number of elements contained in the ring buffer. + */ + auto size() const -> size_t + { + std::atomic_thread_fence(std::memory_order::acquire); + return m_used; + } + + /** + * @return True if the ring buffer contains zero elements. + */ + auto empty() const -> bool { return size() == 0; } + + /** + * Wakes up all currently awaiting producers and consumers. Their await_resume() function + * will return an expected consume result that the ring buffer has stopped. + */ + auto notify_waiters() -> void + { + std::unique_lock lk{m_mutex}; + // Only wake up waiters once. + if (m_stopped.load(std::memory_order::acquire)) { + return; + } + + m_stopped.exchange(true, std::memory_order::release); + + while (m_produce_waiters != nullptr) { + auto* to_resume = m_produce_waiters; + to_resume->m_stopped = true; + m_produce_waiters = m_produce_waiters->m_next; + + lk.unlock(); + to_resume->m_awaiting_coroutine.resume(); + lk.lock(); + } + + while (m_consume_waiters != nullptr) { + auto* to_resume = m_consume_waiters; + to_resume->m_stopped = true; + m_consume_waiters = m_consume_waiters->m_next; + + lk.unlock(); + to_resume->m_awaiting_coroutine.resume(); + lk.lock(); + } + } + + private: + friend produce_operation; + friend consume_operation; + + std::mutex m_mutex{}; + + std::array m_elements{}; + /// The current front pointer to an open slot if not full. + size_t m_front{0}; + /// The current back pointer to the oldest item in the buffer if not empty. + size_t m_back{0}; + /// The number of items in the ring buffer. + size_t m_used{0}; + + /// The LIFO list of produce waiters. + produce_operation* m_produce_waiters{nullptr}; + /// The LIFO list of consume watier. + consume_operation* m_consume_waiters{nullptr}; + + std::atomic m_stopped{false}; + + auto try_produce_locked(std::unique_lock& lk, element& e) -> bool + { + if (m_used == num_elements) { + return false; + } + + m_elements[m_front] = std::move(e); + m_front = (m_front + 1) % num_elements; + ++m_used; + + if (m_consume_waiters != nullptr) { + consume_operation* to_resume = m_consume_waiters; + m_consume_waiters = m_consume_waiters->m_next; + + // Since the consume operation suspended it needs to be provided an element to consume. + to_resume->m_e = std::move(m_elements[m_back]); + m_back = (m_back + 1) % num_elements; + --m_used; // And we just consumed up another item. + + lk.unlock(); + to_resume->m_awaiting_coroutine.resume(); + } + + return true; + } + + auto try_consume_locked(std::unique_lock& lk, consume_operation* op) -> bool + { + if (m_used == 0) { + return false; + } + + op->m_e = std::move(m_elements[m_back]); + m_back = (m_back + 1) % num_elements; + --m_used; + + if (m_produce_waiters != nullptr) { + produce_operation* to_resume = m_produce_waiters; + m_produce_waiters = m_produce_waiters->m_next; + + // Since the produce operation suspended it needs to be provided a slot to place its element. + m_elements[m_front] = std::move(to_resume->m_e); + m_front = (m_front + 1) % num_elements; + ++m_used; // And we just produced another item. + + lk.unlock(); + to_resume->m_awaiting_coroutine.resume(); + } + + return true; + } + }; +} diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 34c8e96045..cb96106958 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -300,4 +300,73 @@ suite semaphore_test = [] { glz::sync_wait(glz::when_all(std::move(tasks))); }; -int main() { return 0; } +suite ring_buffer_test = [] { + std::cout << "\nRing Buffer test:\n"; + + const size_t iterations = 100; + const size_t consumers = 4; + glz::thread_pool tp{glz::thread_pool::options{.thread_count = 4}}; + glz::ring_buffer rb{}; + glz::mutex m{}; + + std::vector> tasks{}; + + auto make_producer_task = [&]() -> glz::task { + co_await tp.schedule(); + + for (size_t i = 1; i <= iterations; ++i) { + co_await rb.produce(i); + } + + // Wait for the ring buffer to clear all items so its a clean stop. + while (!rb.empty()) { + co_await tp.yield(); + } + + // Now that the ring buffer is empty signal to all the consumers its time to stop. Note that + // the stop signal works on producers as well, but this example only uses 1 producer. + { + auto scoped_lock = co_await m.lock(); + std::cerr << "\nproducer is sending stop signal"; + } + rb.notify_waiters(); + co_return; + }; + + auto make_consumer_task = [&](size_t id) -> glz::task { + co_await tp.schedule(); + + while (true) { + auto expected = co_await rb.consume(); + auto scoped_lock = co_await m.lock(); // just for synchronizing std::cout/cerr + if (!expected) { + std::cerr << "\nconsumer " << id << " shutting down, stop signal received"; + break; // while + } + else { + auto item = std::move(*expected); + std::cout << "(id=" << id << ", v=" << item << "), "; + } + + // Mimic doing some work on the consumed value. + co_await tp.yield(); + } + + co_return; + }; + + // Create N consumers + for (size_t i = 0; i < consumers; ++i) { + tasks.emplace_back(make_consumer_task(i)); + } + // Create 1 producer. + tasks.emplace_back(make_producer_task()); + + // Wait for all the values to be produced and consumed through the ring buffer. + glz::sync_wait(glz::when_all(std::move(tasks))); +}; + +int main() { + std::cout << '\n'; + return 0; +} From 590e9b928d843c9161ea464693eabb9c19cffcd8 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 14:49:06 -0500 Subject: [PATCH 218/309] adding socket.hpp --- include/glaze/coroutine.hpp | 1 + include/glaze/coroutine/socket.hpp | 111 +++++++++++++++++++ include/glaze/network/ip_address.hpp | 29 +++++ tests/coroutine_test/coroutine_test.cpp | 141 +++++++++++++++++++++++- 4 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 include/glaze/coroutine/socket.hpp create mode 100644 include/glaze/network/ip_address.hpp diff --git a/include/glaze/coroutine.hpp b/include/glaze/coroutine.hpp index e4f803c0a5..85b74e190c 100644 --- a/include/glaze/coroutine.hpp +++ b/include/glaze/coroutine.hpp @@ -11,6 +11,7 @@ #include "glaze/coroutine/ring_buffer.hpp" #include "glaze/coroutine/semaphore.hpp" #include "glaze/coroutine/shared_mutex.hpp" +#include "glaze/coroutine/socket.hpp" #include "glaze/coroutine/sync_wait.hpp" #include "glaze/coroutine/task.hpp" #include "glaze/coroutine/thread_pool.hpp" diff --git a/include/glaze/coroutine/socket.hpp b/include/glaze/coroutine/socket.hpp new file mode 100644 index 0000000000..84cebd00e0 --- /dev/null +++ b/include/glaze/coroutine/socket.hpp @@ -0,0 +1,111 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include "glaze/coroutine/poll.hpp" +#include "glaze/network/ip_address.hpp" + +namespace glz +{ + struct socket + { + enum class type_t { + /// udp datagram socket + udp, + /// tcp streaming socket + tcp + }; + + enum class blocking_t { + /// This socket should block on system calls. + yes, + /// This socket should not block on system calls. + no + }; + + struct options + { + /// The domain for the socket. + ip_version domain; + /// The type of socket. + type_t type; + /// If the socket should be blocking or non-blocking. + blocking_t blocking; + }; + + static auto type_to_os(type_t type) -> int; + + socket() = default; + explicit socket(int fd) : m_fd(fd) {} + + socket(const socket& other) : m_fd(dup(other.m_fd)) {} + socket(socket&& other) : m_fd(std::exchange(other.m_fd, -1)) {} + auto operator=(const socket& other) noexcept -> socket&; + auto operator=(socket&& other) noexcept -> socket&; + + ~socket() { close(); } + + /** + * This function returns true if the socket's file descriptor is a valid number, however it does + * not imply if the socket is still usable. + * @return True if the socket file descriptor is > 0. + */ + auto is_valid() const -> bool { return m_fd != -1; } + + /** + * @param block Sets the socket to the given blocking mode. + */ + auto blocking(blocking_t block) -> bool; + + /** + * @param how Shuts the socket down with the given operations. + * @param Returns true if the sockets given operations were shutdown. + */ + auto shutdown(poll_op how = poll_op::read_write) -> bool; + + /** + * Closes the socket and sets this socket to an invalid state. + */ + auto close() -> void; + + /** + * @return The native handle (file descriptor) for this socket. + */ + auto native_handle() const -> int { return m_fd; } + + private: + int m_fd{-1}; + }; + + /** + * Creates a socket with the given socket options, this typically is used for creating sockets to + * use within client objects, e.g. tcp::client and udp::client. + * @param opts See socket::options for more details. + */ + auto make_socket(const socket::options& opts) -> socket; + + /** + * Creates a socket that can accept connections or packets with the given socket options, address, + * port and backlog. This is used for creating sockets to use within server objects, e.g. + * tcp::server and udp::server. + * @param opts See socket::options for more details + * @param address The ip address to bind to. If the type of socket is tcp then it will also listen. + * @param port The port to bind to. + * @param backlog If the type of socket is tcp then the backlog of connections to allow. Does nothing + * for udp types. + */ + auto make_accept_socket(const socket::options& opts, const std::string& address, uint16_t port, + int32_t backlog = 128) -> socket; + +} // namespace coro::net diff --git a/include/glaze/network/ip_address.hpp b/include/glaze/network/ip_address.hpp new file mode 100644 index 0000000000..f10f8c4a92 --- /dev/null +++ b/include/glaze/network/ip_address.hpp @@ -0,0 +1,29 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include + +#include +#include +#include + +namespace glz +{ + enum struct ip_version : int { ipv4 = AF_INET, ipv6 = AF_INET6 }; + + std::optional binary_to_ip_string(const std::string_view binary_address, ip_version ipv = ip_version::ipv6) { + std::string output{}; + output.resize(16); + + auto success = ::inet_ntop(int(ipv), binary_address.data(), output.data(), uint32_t(output.size())); + if (success) { + output.resize(::strnlen(success, output.length())); + return {output}; + } + return {}; + } +} diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index cb96106958..f8662eb05a 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -302,7 +302,7 @@ suite semaphore_test = [] { suite ring_buffer_test = [] { std::cout << "\nRing Buffer test:\n"; - + const size_t iterations = 100; const size_t consumers = 4; glz::thread_pool tp{glz::thread_pool::options{.thread_count = 4}}; @@ -366,7 +366,144 @@ suite ring_buffer_test = [] { glz::sync_wait(glz::when_all(std::move(tasks))); }; -int main() { +/*suite io_scheduler_test = [] { + auto scheduler = std::make_shared(glz::io_scheduler::options{ + // The scheduler will spawn a dedicated event processing thread. This is the default, but + // it is possible to use 'manual' and call 'process_events()' to drive the scheduler yourself. + .thread_strategy = glz::io_scheduler::thread_strategy_t::spawn, + // If the scheduler is in spawn mode this functor is called upon starting the dedicated + // event processor thread. + .on_io_thread_start_functor = [] { std::cout << "io_scheduler::process event thread start\n"; }, + // If the scheduler is in spawn mode this functor is called upon stopping the dedicated + // event process thread. + .on_io_thread_stop_functor = [] { std::cout << "io_scheduler::process event thread stop\n"; }, + // The io scheduler can use a coro::thread_pool to process the events or tasks it is given. + // You can use an execution strategy of `process_tasks_inline` to have the event loop thread + // directly process the tasks, this might be desirable for small tasks vs a thread pool for large tasks. + .pool = + glz::thread_pool::options{ + .thread_count = 2, + .on_thread_start_functor = + [](size_t i) { std::cout << "io_scheduler::thread_pool worker " << i << " starting\n"; }, + .on_thread_stop_functor = + [](size_t i) { std::cout << "io_scheduler::thread_pool worker " << i << " stopping\n"; }, + }, + .execution_strategy = glz::io_scheduler::execution_strategy_t::process_tasks_on_thread_pool}); + + auto make_server_task = [&]() -> glz::task { + // Start by creating a tcp server, we'll do this before putting it into the scheduler so + // it is immediately available for the client to connect since this will create a socket, + // bind the socket and start listening on that socket. See tcp::server for more details on + // how to specify the local address and port to bind to as well as enabling SSL/TLS. + glz::net::tcp::server server{scheduler}; + + // Now scheduler this task onto the scheduler. + co_await scheduler->schedule(); + + // Wait for an incoming connection and accept it. + auto poll_status = co_await server.poll(); + if (poll_status != glz::poll_status::event) { + co_return; // Handle error, see poll_status for detailed error states. + } + + // Accept the incoming client connection. + auto client = server.accept(); + + // Verify the incoming connection was accepted correctly. + if (!client.socket().is_valid()) { + co_return; // Handle error. + } + + // Now wait for the client message, this message is small enough it should always arrive + // with a single recv() call. + poll_status = co_await client.poll(glz::poll_op::read); + if (poll_status != glz::poll_status::event) { + co_return; // Handle error. + } + + // Prepare a buffer and recv() the client's message. This function returns the recv() status + // as well as a span that overlaps the given buffer for the bytes that were read. This + // can be used to resize the buffer or work with the bytes without modifying the buffer at all. + std::string request(256, '\0'); + auto [recv_status, recv_bytes] = client.recv(request); + if (recv_status != glz::net::recv_status::ok) { + co_return; // Handle error, see net::recv_status for detailed error states. + } + + request.resize(recv_bytes.size()); + std::cout << "server: " << request << "\n"; + + // Make sure the client socket can be written to. + poll_status = co_await client.poll(glz::poll_op::write); + if (poll_status != glz::poll_status::event) { + co_return; // Handle error. + } + + // Send the server response to the client. + // This message is small enough that it will be sent in a single send() call, but to demonstrate + // how to use the 'remaining' portion of the send() result this is wrapped in a loop until + // all the bytes are sent. + std::string response = "Hello from server."; + std::span remaining = response; + do { + // Optimistically send() prior to polling. + auto [send_status, r] = client.send(remaining); + if (send_status != glz::net::send_status::ok) { + co_return; // Handle error, see net::send_status for detailed error states. + } + + if (r.empty()) { + break; // The entire message has been sent. + } + + // Re-assign remaining bytes for the next loop iteration and poll for the socket to be + // able to be written to again. + remaining = r; + auto pstatus = co_await client.poll(glz::poll_op::write); + if (pstatus != glz::poll_status::event) { + co_return; // Handle error. + } + } while (true); + + co_return; + }; + + auto make_client_task = [&]() -> glz::task { + // Immediately schedule onto the scheduler. + co_await scheduler->schedule(); + + // Create the tcp::client with the default settings, see tcp::client for how to set the + // ip address, port, and optionally enabling SSL/TLS. + glz::net::tcp::client client{scheduler}; + + // Ommitting error checking code for the client, each step should check the status and + // verify the number of bytes sent or received. + + // Connect to the server. + co_await client.connect(); + + // Make sure the client socket can be written to. + co_await client.poll(glz::poll_op::write); + + // Send the request data. + client.send(std::string_view{"Hello from client."}); + + // Wait for the response and receive it. + co_await client.poll(glz::poll_op::read); + std::string response(256, '\0'); + auto [recv_status, recv_bytes] = client.recv(response); + response.resize(recv_bytes.size()); + + std::cout << "client: " << response << "\n"; + co_return; + }; + + // Create and wait for the server and client tasks to complete. + glz::sync_wait(glz::when_all(make_server_task(), make_client_task())); +};*/ + +int main() +{ std::cout << '\n'; return 0; } From 225b0f5d5d50f13f909b3824e2d5c2ffa2959724 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 14:58:49 -0500 Subject: [PATCH 219/309] Update socket.hpp --- include/glaze/coroutine/socket.hpp | 158 ++++++++++++++++++++++++----- 1 file changed, 133 insertions(+), 25 deletions(-) diff --git a/include/glaze/coroutine/socket.hpp b/include/glaze/coroutine/socket.hpp index 84cebd00e0..305b4e32d3 100644 --- a/include/glaze/coroutine/socket.hpp +++ b/include/glaze/coroutine/socket.hpp @@ -5,14 +5,6 @@ #pragma once -#include -#include -#include - -#include -#include -#include - #include "glaze/coroutine/poll.hpp" #include "glaze/network/ip_address.hpp" @@ -20,14 +12,14 @@ namespace glz { struct socket { - enum class type_t { + enum struct type { /// udp datagram socket udp, /// tcp streaming socket tcp }; - enum class blocking_t { + enum struct blocking { /// This socket should block on system calls. yes, /// This socket should not block on system calls. @@ -39,20 +31,46 @@ namespace glz /// The domain for the socket. ip_version domain; /// The type of socket. - type_t type; + socket::type type; /// If the socket should be blocking or non-blocking. - blocking_t blocking; + socket::blocking blocking; }; - static auto type_to_os(type_t type) -> int; + static int type_to_os(socket::type type) + { + switch (type) + { + case type::udp: + return SOCK_DGRAM; + case type::tcp: + return SOCK_STREAM; + default: + GLZ_THROW_OR_ABORT(std::runtime_error{"Unknown socket::type_t."}); + } + } socket() = default; explicit socket(int fd) : m_fd(fd) {} socket(const socket& other) : m_fd(dup(other.m_fd)) {} socket(socket&& other) : m_fd(std::exchange(other.m_fd, -1)) {} - auto operator=(const socket& other) noexcept -> socket&; - auto operator=(socket&& other) noexcept -> socket&; + + socket& operator=(const socket& other) noexcept + { + this->close(); + this->m_fd = dup(other.m_fd); + return *this; + } + + socket& operator=(socket&& other) noexcept + { + if (std::addressof(other) != this) + { + m_fd = std::exchange(other.m_fd, -1); + } + + return *this; + } ~socket() { close(); } @@ -61,28 +79,73 @@ namespace glz * not imply if the socket is still usable. * @return True if the socket file descriptor is > 0. */ - auto is_valid() const -> bool { return m_fd != -1; } + bool is_valid() const { return m_fd != -1; } /** * @param block Sets the socket to the given blocking mode. */ - auto blocking(blocking_t block) -> bool; + bool blocking(socket::blocking block) + { + if (m_fd < 0) + { + return false; + } + + int flags = fcntl(m_fd, F_GETFL, 0); + if (flags == -1) + { + return false; + } + + // Add or subtract non-blocking flag. + flags = (block == blocking::yes) ? flags & ~O_NONBLOCK : (flags | O_NONBLOCK); + + return (fcntl(m_fd, F_SETFL, flags) == 0); + } /** * @param how Shuts the socket down with the given operations. * @param Returns true if the sockets given operations were shutdown. */ - auto shutdown(poll_op how = poll_op::read_write) -> bool; + bool shutdown(poll_op how = poll_op::read_write) + { + if (m_fd != -1) + { + int h{0}; + switch (how) + { + case poll_op::read: + h = SHUT_RD; + break; + case poll_op::write: + h = SHUT_WR; + break; + case poll_op::read_write: + h = SHUT_RDWR; + break; + } + + return (::shutdown(m_fd, h) == 0); + } + return false; + } /** * Closes the socket and sets this socket to an invalid state. */ - auto close() -> void; + void close() + { + if (m_fd != -1) + { + ::close(m_fd); + m_fd = -1; + } + } /** * @return The native handle (file descriptor) for this socket. */ - auto native_handle() const -> int { return m_fd; } + int native_handle() const { return m_fd; } private: int m_fd{-1}; @@ -93,7 +156,24 @@ namespace glz * use within client objects, e.g. tcp::client and udp::client. * @param opts See socket::options for more details. */ - auto make_socket(const socket::options& opts) -> socket; + socket make_socket(const socket::options& opts) + { + socket s{::socket(static_cast(opts.domain), socket::type_to_os(opts.type), 0)}; + if (s.native_handle() < 0) + { + GLZ_THROW_OR_ABORT(std::runtime_error{"Failed to create socket."}); + } + + if (opts.blocking == socket::blocking::no) + { + if (s.blocking(socket::blocking::no) == false) + { + GLZ_THROW_OR_ABORT(std::runtime_error{"Failed to set socket to non-blocking mode."}); + } + } + + return s; + } /** * Creates a socket that can accept connections or packets with the given socket options, address, @@ -105,7 +185,35 @@ namespace glz * @param backlog If the type of socket is tcp then the backlog of connections to allow. Does nothing * for udp types. */ - auto make_accept_socket(const socket::options& opts, const std::string& address, uint16_t port, - int32_t backlog = 128) -> socket; - -} // namespace coro::net + socket make_accept_socket(const socket::options& opts, const std::string& address, uint16_t port, + int32_t backlog = 128) + { + socket s = make_socket(opts); + + int sock_opt{1}; + if (setsockopt(s.native_handle(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &sock_opt, sizeof(sock_opt)) < 0) + { + GLZ_THROW_OR_ABORT(std::runtime_error{"Failed to setsockopt(SO_REUSEADDR | SO_REUSEPORT)"}); + } + + sockaddr_in server{}; + server.sin_family = static_cast(opts.domain); + server.sin_port = htons(port); + server.sin_addr = *reinterpret_cast(address.data()); + + if (bind(s.native_handle(), (struct sockaddr*)&server, sizeof(server)) < 0) + { + GLZ_THROW_OR_ABORT(std::runtime_error{"Failed to bind."}); + } + + if (opts.type == socket::type::tcp) + { + if (listen(s.native_handle(), backlog) < 0) + { + GLZ_THROW_OR_ABORT(std::runtime_error{"Failed to listen."}); + } + } + + return s; + } +} From 84e43f982f95f113b230818014f5a4b08b1bf897 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 15:00:45 -0500 Subject: [PATCH 220/309] Update socket.hpp --- include/glaze/coroutine/socket.hpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/include/glaze/coroutine/socket.hpp b/include/glaze/coroutine/socket.hpp index 305b4e32d3..6d1329de89 100644 --- a/include/glaze/coroutine/socket.hpp +++ b/include/glaze/coroutine/socket.hpp @@ -13,27 +13,23 @@ namespace glz struct socket { enum struct type { - /// udp datagram socket udp, - /// tcp streaming socket tcp }; enum struct blocking { - /// This socket should block on system calls. yes, - /// This socket should not block on system calls. no }; struct options { /// The domain for the socket. - ip_version domain; + ip_version domain{}; /// The type of socket. - socket::type type; + socket::type type{}; /// If the socket should be blocking or non-blocking. - socket::blocking blocking; + socket::blocking blocking{}; }; static int type_to_os(socket::type type) From a672c1747645603c70ee732c5dc03f31fdc41020 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Thu, 27 Jun 2024 16:18:51 -0500 Subject: [PATCH 221/309] updates --- include/glaze/coroutine/socket.hpp | 12 +-- include/glaze/network/ip.hpp | 111 +++++++++++++++++++++++++++ include/glaze/network/ip_address.hpp | 29 ------- 3 files changed, 117 insertions(+), 35 deletions(-) create mode 100644 include/glaze/network/ip.hpp delete mode 100644 include/glaze/network/ip_address.hpp diff --git a/include/glaze/coroutine/socket.hpp b/include/glaze/coroutine/socket.hpp index 6d1329de89..2483e2683e 100644 --- a/include/glaze/coroutine/socket.hpp +++ b/include/glaze/coroutine/socket.hpp @@ -6,7 +6,7 @@ #pragma once #include "glaze/coroutine/poll.hpp" -#include "glaze/network/ip_address.hpp" +#include "glaze/network/ip.hpp" namespace glz { @@ -75,7 +75,7 @@ namespace glz * not imply if the socket is still usable. * @return True if the socket file descriptor is > 0. */ - bool is_valid() const { return m_fd != -1; } + bool valid() const { return m_fd != -1; } /** * @param block Sets the socket to the given blocking mode. @@ -154,15 +154,15 @@ namespace glz */ socket make_socket(const socket::options& opts) { - socket s{::socket(static_cast(opts.domain), socket::type_to_os(opts.type), 0)}; - if (s.native_handle() < 0) + socket s{::socket(int(opts.domain), socket::type_to_os(opts.type), 0)}; + if (not s.valid()) { GLZ_THROW_OR_ABORT(std::runtime_error{"Failed to create socket."}); } if (opts.blocking == socket::blocking::no) { - if (s.blocking(socket::blocking::no) == false) + if (not s.blocking(socket::blocking::no)) { GLZ_THROW_OR_ABORT(std::runtime_error{"Failed to set socket to non-blocking mode."}); } @@ -193,7 +193,7 @@ namespace glz } sockaddr_in server{}; - server.sin_family = static_cast(opts.domain); + server.sin_family = int(opts.domain); server.sin_port = htons(port); server.sin_addr = *reinterpret_cast(address.data()); diff --git a/include/glaze/network/ip.hpp b/include/glaze/network/ip.hpp new file mode 100644 index 0000000000..d182d600b5 --- /dev/null +++ b/include/glaze/network/ip.hpp @@ -0,0 +1,111 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Modified from the awesome: https://github.com/jbaldwin/libcoro + +#pragma once + +#include + +#include +#include +#include + +namespace glz +{ + enum struct ip_version : int { ipv4 = AF_INET, ipv6 = AF_INET6 }; + + std::optional binary_to_ip_string(const std::string_view binary_address, ip_version ipv = ip_version::ipv6) { + std::string output{}; + output.resize(16); + + auto success = ::inet_ntop(int(ipv), binary_address.data(), output.data(), uint32_t(output.size())); + if (success) { + output.resize(::strnlen(success, output.length())); + return {output}; + } + return {}; + } + + enum struct connect_status + { + connected, + invalid_ip_address, + timeout, + error + }; + + inline std::string to_string(const connect_status& status) noexcept + { + using enum connect_status; + switch (status) + { + case connected: + return "connected"; + case invalid_ip_address: + return "invalid_ip_address"; + case timeout: + return "timeout"; + case error: + return "error"; + default: + return "unknown"; + } + } + + enum struct recv_status : int64_t + { + ok = 0, + /// The peer closed the socket. + closed = -1, + /// The udp socket has not been bind()'ed to a local port. + udp_not_bound = -2, + try_again = EAGAIN, + // Note: that only the tcp::client will return this, a tls::client returns the specific ssl_would_block_* status'. + would_block = EWOULDBLOCK, + bad_file_descriptor = EBADF, + connection_refused = ECONNREFUSED, + memory_fault = EFAULT, + interrupted = EINTR, + invalid_argument = EINVAL, + no_memory = ENOMEM, + not_connected = ENOTCONN, + not_a_socket = ENOTSOCK, + }; + + std::string to_string(recv_status status) + { + using enum recv_status; + switch (status) + { + case ok: + return "ok"; + case closed: + return "closed"; + case udp_not_bound: + return "udp_not_bound"; + //case try_again: + // return "try_again"; + case would_block: + return "would_block"; + case bad_file_descriptor: + return "bad_file_descriptor"; + case connection_refused: + return "connection_refused"; + case memory_fault: + return "memory_fault"; + case interrupted: + return "interrupted"; + case invalid_argument: + return "invalid_argument"; + case no_memory: + return "no_memory"; + case not_connected: + return "not_connected"; + case not_a_socket: + return "not_a_socket"; + default: + return "unknown"; + } + } +} diff --git a/include/glaze/network/ip_address.hpp b/include/glaze/network/ip_address.hpp deleted file mode 100644 index f10f8c4a92..0000000000 --- a/include/glaze/network/ip_address.hpp +++ /dev/null @@ -1,29 +0,0 @@ -// Glaze Library -// For the license information refer to glaze.hpp - -// Modified from the awesome: https://github.com/jbaldwin/libcoro - -#pragma once - -#include - -#include -#include -#include - -namespace glz -{ - enum struct ip_version : int { ipv4 = AF_INET, ipv6 = AF_INET6 }; - - std::optional binary_to_ip_string(const std::string_view binary_address, ip_version ipv = ip_version::ipv6) { - std::string output{}; - output.resize(16); - - auto success = ::inet_ntop(int(ipv), binary_address.data(), output.data(), uint32_t(output.size())); - if (success) { - output.resize(::strnlen(success, output.length())); - return {output}; - } - return {}; - } -} From b8a6a2e30a863767ce1ea6f341cc0264999761c1 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Fri, 28 Jun 2024 09:00:49 -0500 Subject: [PATCH 222/309] Initial Windows implementation of coro. --- include/glaze/coroutine/io_scheduler.hpp | 114 +++++++++++++++++------ include/glaze/coroutine/poll_info.hpp | 2 +- include/glaze/network/core.hpp | 50 +++++----- 3 files changed, 113 insertions(+), 53 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 1d18c41104..28d61355cb 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -24,6 +24,7 @@ #include "coro/net/socket.hpp" #endif +#include #include #include #include @@ -77,18 +78,14 @@ namespace glz /// rather than scheduling them to be picked up by the thread pool. const execution_strategy_t execution_strategy{execution_strategy_t::process_tasks_on_thread_pool}; }; - - io_scheduler() { - init(); - } - io_scheduler(options opts) : m_opts(std::move(opts)) { - init(); - } + io_scheduler() { init(); } + + io_scheduler(options opts) : m_opts(std::move(opts)) { init(); } io_scheduler(const io_scheduler&) = delete; io_scheduler(io_scheduler&&) = delete; - io_scheduler& operator=(const io_scheduler&) = delete; + io_scheduler& operator=(const io_scheduler&) = delete; io_scheduler& operator=(io_scheduler&&) = delete; ~io_scheduler() @@ -98,11 +95,11 @@ namespace glz if (m_io_thread.joinable()) { m_io_thread.join(); } - + #if defined(__linux__) - close_file_handle(event_fd); - close_file_handle(timer_fd); - close_file_handle(schedule_fd); + close_socket(event_fd); + close_socket(timer_fd); + close_socket(schedule_fd); #endif } @@ -155,7 +152,7 @@ namespace glz #elif defined(__APPLE__) net::poll_event_t e{ .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; - if (::kevent(m_scheduler.event_fd, &e, 1, NULL, 0, NULL) == -1) { + if (::kevent(m_scheduler.event_fd, &e, 1, nullptr, 0, nullptr) == -1) { GLZ_THROW_OR_ABORT(std::runtime_error("Failed to trigger wke up")); } #endif @@ -263,7 +260,7 @@ namespace glz * block indefinitely until the event triggers. * @return The result of the poll operation. */ - [[nodiscard]] glz::task poll(net::file_handle_t fd, glz::poll_op op, + [[nodiscard]] glz::task poll(net::event_handle_t fd, glz::poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { // Because the size will drop when this coroutine suspends every poll needs to undo the subtraction @@ -293,9 +290,11 @@ namespace glz #elif defined(__APPLE__) net::poll_event_t e{ .ident = uintptr_t(fd), .filter = EVFILT_READ, .flags = EV_ADD | EV_EOF, .udata = &poll_info}; - if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "kqueue failed to register for fd: " << fd << "\n"; } +#elf defined(_WIN32) + #endif // The event loop will 'clean-up' whichever event didn't win since the coroutine is scheduled @@ -352,7 +351,7 @@ namespace glz #elif defined(__APPLE__) net::poll_event_t e{ .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; - if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { GLZ_THROW_OR_ABORT(std::runtime_error("Failed to trigger wake up")); } #endif @@ -403,9 +402,11 @@ namespace glz #elif defined(__APPLE__) net::poll_event_t e{ .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_shutdown_ptr)}; - if (::kevent(event_fd, &e, 1, NULL, 0, NULL) == -1) { + if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { GLZ_THROW_OR_ABORT(std::runtime_error("Failed to signal shutdown event")); } +#elif defined(_WIN32) + #endif if (m_io_thread.joinable()) { @@ -419,13 +420,13 @@ namespace glz options m_opts{}; /// The event loop epoll file descriptor. - net::file_handle_t event_fd{net::create_event_poll()}; + net::event_handle_t event_fd{net::create_event_poll()}; /// The event loop fd to trigger a shutdown. - net::file_handle_t shutdown_fd{net::create_shutdown_handle()}; + net::event_handle_t shutdown_fd{net::create_shutdown_handle()}; /// The event loop timer fd for timed events, e.g. yield_for() or scheduler_after(). - net::file_handle_t timer_fd{net::create_timer_handle()}; + net::event_handle_t timer_fd{net::create_timer_handle()}; /// The schedule file descriptor if the scheduler is in inline processing mode. - net::file_handle_t schedule_fd{net::create_schedule_handle()}; + net::event_handle_t schedule_fd{net::create_schedule_handle()}; std::atomic m_schedule_fd_triggered{false}; /// The number of tasks executing or awaiting events in this io scheduler. @@ -454,7 +455,7 @@ namespace glz m_io_processing.exchange(false, std::memory_order::release); } } - + void init() { if (m_opts.execution_strategy == execution_strategy_t::process_tasks_on_thread_pool) { @@ -522,10 +523,45 @@ namespace glz #elif defined(__linux__) const auto event_count = ::epoll_wait(event_fd, m_events.data(), max_events, timeout.count()); #elif defined(_WIN32) + + m_events = {shutdown_fd, timer_fd, schedule_fd}; + + enum struct event_id : DWORD { + shutdown, + timer, + schedual + }; + + const auto event_obj = + WaitForMultipleObjects(3 /*number of object handles*/, m_events.data(), FALSE, DWORD(timeout.count())); + + if (WAIT_FAILED == event_obj) { + GLZ_THROW_OR_ABORT(std::runtime_error{"WaitForMultipleObjects for event failed"}); + } + using enum event_id; + + switch (event_id(event_obj)) { + case shutdown: + // TODO: + break; + + case timer: + process_timeout_execute(); + break; + + case schedual: + process_scheduled_execute_inline(); + break; + + default: + GLZ_THROW_OR_ABORT(std::runtime_error{"Unhandled event id!"}); + } #endif +#if defined(__linux__) || defined(__APPLE__) + if (event_count == -1) { - net::event_close(event_fd); + net::close_event(event_fd); GLZ_THROW_OR_ABORT(std::runtime_error{"wait for event failed"}); } @@ -541,21 +577,19 @@ namespace glz GLZ_THROW_OR_ABORT(std::runtime_error{"event error"}); } #endif + if (not handle_ptr) { GLZ_THROW_OR_ABORT(std::runtime_error{"handle_ptr is null"}); } if (handle_ptr == m_timer_ptr) { - // Process all events that have timed out. process_timeout_execute(); } else if (handle_ptr == m_schedule_ptr) { - // Process scheduled coroutines. process_scheduled_execute_inline(); } else if (handle_ptr == m_shutdown_ptr) [[unlikely]] { - // Nothing to do , just needed to wake-up and smell the flowers - std::cout << "Waking up and smelling flowers...\n"; + } else { // Individual poll task wake-up. @@ -567,7 +601,7 @@ namespace glz } } } - +#endif // Its important to not resume any handles until the full set is accounted for. If a timeout // and an event for the same handle happen in the same epoll_wait() call then inline processing // will destruct the poll_info object before the second event is handled. This is also possible @@ -660,7 +694,7 @@ namespace glz poll_info->m_processed = true; // Given a valid fd always remove it from epoll so the next poll can blindly EPOLL_CTL_ADD. - if (poll_info->m_fd != net::invalid_file_handle) { + if (poll_info->m_fd != net::invalid_event_handle) { #if defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, poll_info->m_fd, nullptr); #endif @@ -709,7 +743,7 @@ namespace glz pi->m_processed = true; // Since this timed out, remove its corresponding event if it has one. - if (pi->m_fd != net::invalid_file_handle) { + if (pi->m_fd != net::invalid_event_handle) { #if defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); #elif defined(__APPLE__) @@ -794,6 +828,21 @@ namespace glz if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "Error: kevent (update timer).\n"; } +#elif defined(_WIN32) + size_t seconds{}; + size_t nanoseconds{1}; + if (tp > now) { + const auto time_left = tp - now; + const auto s = std::chrono::duration_cast(time_left); + seconds = s.count(); + nanoseconds = std::chrono::duration_cast(time_left - s).count(); + } + LARGE_INTEGER signal_time{}; + signal_time.QuadPart = -std::chrono::duration_cast(tp - now).count() / 100; + + if (!SetWaitableTimer(timer_fd, &signal_time, 0, nullptr, nullptr, FALSE)) { + std::cerr << "SetWaitableTimer failed (" << GetLastError() << ")\n"; + } #endif } else { @@ -811,6 +860,11 @@ namespace glz if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "Error: kevent (update timer).\n"; } +#elif defined(_WIN32) + LARGE_INTEGER signal_time{}; + if (!SetWaitableTimer(timer_fd, nullptr, 0, nullptr, nullptr, FALSE)) { + std::cerr << "Error: SetWaitableTimer (disable timer) failed (" << GetLastError() << ")\n"; + } #endif } } diff --git a/include/glaze/coroutine/poll_info.hpp b/include/glaze/coroutine/poll_info.hpp index cc017d41a8..59587f4250 100644 --- a/include/glaze/coroutine/poll_info.hpp +++ b/include/glaze/coroutine/poll_info.hpp @@ -60,7 +60,7 @@ namespace glz /// The file descriptor being polled on. This is needed so that if the timeout occurs first then /// the event loop can immediately disable the event within epoll. - net::file_handle_t m_fd{net::invalid_file_handle}; + net::event_handle_t m_fd{net::invalid_event_handle}; /// The timeout's position in the timeout map. A poll() with no timeout or yield() this is empty. /// This is needed so that if the event occurs first then the event loop can immediately disable /// the timeout within epoll. diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 35ebf56ce9..d5c26afaad 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -43,24 +43,27 @@ namespace glz::net { #ifdef _WIN32 - using file_handle_t = unsigned int; + using event_handle_t = HANDLE; using ssize_t = int64_t; #else - using file_handle_t = int; + using event_handle_t = int; #endif #if defined(__APPLE__) using poll_event_t = struct kevent; - constexpr int invalid_file_handle = -1; + constexpr int invalid_event_handle = -1; using ident_t = uintptr_t; constexpr uintptr_t invalid_ident = ~uintptr_t(0); // set all bits #elif defined(__linux__) using poll_event_t = struct epoll_event; - constexpr int invalid_file_handle = -1; + constexpr int invalid_event_handle = -1; using ident_t = int; constexpr int invalid_ident = -1; #elif defined(_WIN32) - constexpr unsigned int invalid_file_handle = 0; + using ident_t = HANDLE; + using poll_event_t = HANDLE; + inline const HANDLE invalid_event_handle = INVALID_HANDLE_VALUE; + inline const HANDLE invalid_ident = INVALID_HANDLE_VALUE; #endif #if defined(__APPLE__) @@ -70,64 +73,67 @@ namespace glz::net constexpr auto poll_in = EPOLLIN; constexpr auto poll_out = EPOLLOUT; #elif defined(_WIN32) + constexpr auto poll_in = 0; + constexpr auto poll_out = 1; #endif - - inline auto close_file_handle(file_handle_t& fd) { - if (fd != invalid_file_handle) { + + + inline auto close_socket(auto& fd) { + if (fd != invalid_event_handle) { #ifdef _WIN32 ::closesocket(fd); #else ::close(fd); #endif } - fd = invalid_file_handle; + fd = invalid_event_handle; } - inline auto event_close(file_handle_t fd) { + inline auto close_event(auto fd) { #ifdef _WIN32 - WSACloseEvent(fd); + ::CloseHandle(fd); #else ::close(fd); #endif } - inline file_handle_t create_event_poll() { + inline event_handle_t create_event_poll() { #if defined(__APPLE__) return ::kqueue(); #elif defined(__linux__) return ::epoll_create1(EPOLL_CLOEXEC); #elif defined(_WIN32) - return WSACreateEvent(); + return INVALID_HANDLE_VALUE; #endif } - inline file_handle_t create_shutdown_handle() { + inline event_handle_t create_shutdown_handle() { #if defined(__APPLE__) - return invalid_file_handle; + return invalid_event_handle; #elif defined(__linux__) return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) - return 0; + return CreateEventA(nullptr, TRUE, FALSE, "create_shutdown_handle"); #endif } - inline file_handle_t create_timer_handle() { + inline event_handle_t create_timer_handle() { #if defined(__APPLE__) - return invalid_file_handle; + return invalid_event_handle; #elif defined(__linux__) return ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); #elif defined(_WIN32) - return 0; + return CreateWaitableTimerA(nullptr, TRUE, "create_timer_handle"); #endif } - inline file_handle_t create_schedule_handle() { + inline event_handle_t create_schedule_handle() { #if defined(__APPLE__) - return invalid_file_handle; + return invalid_event_handle; #elif defined(__linux__) return ::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); #elif defined(_WIN32) - return 0; + return CreateEventA(nullptr, TRUE, FALSE, "create_schedule_handle"); #endif } From ceb6e8382b4c6e321a2d18f812a04616a4ec93fb Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Fri, 28 Jun 2024 09:10:42 -0500 Subject: [PATCH 223/309] Completed coro Window's implementation. --- include/glaze/coroutine/io_scheduler.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 28d61355cb..291dcd5e8d 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -554,7 +554,8 @@ namespace glz break; default: - GLZ_THROW_OR_ABORT(std::runtime_error{"Unhandled event id!"}); + break; + // GLZ_THROW_OR_ABORT(std::runtime_error{"Unhandled event id!"}); } #endif @@ -862,7 +863,7 @@ namespace glz } #elif defined(_WIN32) LARGE_INTEGER signal_time{}; - if (!SetWaitableTimer(timer_fd, nullptr, 0, nullptr, nullptr, FALSE)) { + if (!SetWaitableTimer(timer_fd, &signal_time, 0, nullptr, nullptr, FALSE)) { std::cerr << "Error: SetWaitableTimer (disable timer) failed (" << GetLastError() << ")\n"; } #endif From b710e4c6f1f575b1743fe65514646dee2b5a9e0e Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Fri, 28 Jun 2024 09:19:51 -0500 Subject: [PATCH 224/309] Added missing namespace glz::net to close_socket routine. --- include/glaze/coroutine/io_scheduler.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 291dcd5e8d..a123d5ae07 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -97,9 +97,9 @@ namespace glz } #if defined(__linux__) - close_socket(event_fd); - close_socket(timer_fd); - close_socket(schedule_fd); + glz::net::close_socket(event_fd); + glz::net::close_socket(timer_fd); + glz::net::close_socket(schedule_fd); #endif } From 9dd6838c6bca0950ced69f91673b3ee2688857dc Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Fri, 28 Jun 2024 09:24:52 -0500 Subject: [PATCH 225/309] Added close_events for Window's platform implementation. --- include/glaze/coroutine/io_scheduler.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index a123d5ae07..93ccfb2345 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -100,6 +100,11 @@ namespace glz glz::net::close_socket(event_fd); glz::net::close_socket(timer_fd); glz::net::close_socket(schedule_fd); + +#elif defined(_WIN32) + glz::net::close_event(event_fd); + glz::net::close_event(timer_fd); + glz::net::close_event(schedule_fd); #endif } From 16e1d429a18870dabaa4fe6fd03351eebc95d100 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 1 Jul 2024 10:50:35 -0500 Subject: [PATCH 226/309] fixed elif --- include/glaze/coroutine/io_scheduler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index 93ccfb2345..b293f6f4b4 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -298,7 +298,7 @@ namespace glz if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "kqueue failed to register for fd: " << fd << "\n"; } -#elf defined(_WIN32) +#elif defined(_WIN32) #endif From 52b0ae7f508b2ec59aabc700177da311c6287b37 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 1 Jul 2024 10:55:29 -0500 Subject: [PATCH 227/309] Update semaphore.hpp --- include/glaze/coroutine/semaphore.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/coroutine/semaphore.hpp b/include/glaze/coroutine/semaphore.hpp index e0556ce9dc..de6cbf7ed0 100644 --- a/include/glaze/coroutine/semaphore.hpp +++ b/include/glaze/coroutine/semaphore.hpp @@ -161,7 +161,7 @@ namespace glz /** * @return The maximum number of resources the semaphore can contain. */ - std::ptrdiff_t max() const noexcept { return m_least_max_value; } + std::ptrdiff_t max_resources() const noexcept { return m_least_max_value; } /** * The current number of resources available in this semaphore. From c88691e1bf969e7bc18d45ae00ad1caa9d8fc047 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 1 Jul 2024 11:00:22 -0500 Subject: [PATCH 228/309] Update ip.hpp --- include/glaze/network/ip.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/glaze/network/ip.hpp b/include/glaze/network/ip.hpp index d182d600b5..e4d8109ec5 100644 --- a/include/glaze/network/ip.hpp +++ b/include/glaze/network/ip.hpp @@ -5,7 +5,9 @@ #pragma once +#if defined(__linux__) || defined(__APPLE__) #include +#endif #include #include From 400772d234ac16f0aeaeae9955fd428afd2e8111 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 1 Jul 2024 11:08:56 -0500 Subject: [PATCH 229/309] Update ip.hpp --- include/glaze/network/ip.hpp | 109 ++++++++++------------------------- 1 file changed, 29 insertions(+), 80 deletions(-) diff --git a/include/glaze/network/ip.hpp b/include/glaze/network/ip.hpp index e4d8109ec5..95616cae6b 100644 --- a/include/glaze/network/ip.hpp +++ b/include/glaze/network/ip.hpp @@ -7,17 +7,22 @@ #if defined(__linux__) || defined(__APPLE__) #include +#elif defined(_WIN32) +#include +#include #endif #include #include #include +#include "glaze/reflection/enum_macro.hpp" + namespace glz { enum struct ip_version : int { ipv4 = AF_INET, ipv6 = AF_INET6 }; - std::optional binary_to_ip_string(const std::string_view binary_address, ip_version ipv = ip_version::ipv6) { + inline std::optional binary_to_ip_string(const std::string_view binary_address, ip_version ipv = ip_version::ipv6) { std::string output{}; output.resize(16); @@ -29,85 +34,29 @@ namespace glz return {}; } - enum struct connect_status - { - connected, - invalid_ip_address, - timeout, - error - }; - - inline std::string to_string(const connect_status& status) noexcept - { - using enum connect_status; - switch (status) - { - case connected: - return "connected"; - case invalid_ip_address: - return "invalid_ip_address"; - case timeout: - return "timeout"; - case error: - return "error"; - default: - return "unknown"; - } - } + GLZ_ENUM(connect_status, connected, invalid_ip_address, timeout, error); - enum struct recv_status : int64_t - { - ok = 0, - /// The peer closed the socket. - closed = -1, - /// The udp socket has not been bind()'ed to a local port. - udp_not_bound = -2, - try_again = EAGAIN, - // Note: that only the tcp::client will return this, a tls::client returns the specific ssl_would_block_* status'. - would_block = EWOULDBLOCK, - bad_file_descriptor = EBADF, - connection_refused = ECONNREFUSED, - memory_fault = EFAULT, - interrupted = EINTR, - invalid_argument = EINVAL, - no_memory = ENOMEM, - not_connected = ENOTCONN, - not_a_socket = ENOTSOCK, - }; + GLZ_ENUM(recv_status, ok, closed, udp_not_bound, try_again, // + would_block, bad_file_descriptor, connection_refused, // + memory_fault, interrupted, invalid_argument, no_memory, // + not_connected, not_a_socket); - std::string to_string(recv_status status) - { - using enum recv_status; - switch (status) - { - case ok: - return "ok"; - case closed: - return "closed"; - case udp_not_bound: - return "udp_not_bound"; - //case try_again: - // return "try_again"; - case would_block: - return "would_block"; - case bad_file_descriptor: - return "bad_file_descriptor"; - case connection_refused: - return "connection_refused"; - case memory_fault: - return "memory_fault"; - case interrupted: - return "interrupted"; - case invalid_argument: - return "invalid_argument"; - case no_memory: - return "no_memory"; - case not_connected: - return "not_connected"; - case not_a_socket: - return "not_a_socket"; - default: - return "unknown"; - } - } + /* + ok = 0, + /// The peer closed the socket. + closed = -1, + /// The udp socket has not been bind()'ed to a local port. + udp_not_bound = -2, + try_again = EAGAIN, + // Note: that only the tcp::client will return this, a tls::client returns the specific ssl_would_block_* status'. + would_block = EWOULDBLOCK, + bad_file_descriptor = EBADF, + connection_refused = ECONNREFUSED, + memory_fault = EFAULT, + interrupted = EINTR, + invalid_argument = EINVAL, + no_memory = ENOMEM, + not_connected = ENOTCONN, + not_a_socket = ENOTSOCK, + */ } From 87ec2da5bd48d5cdbf062e4c76678f52758aa55c Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 1 Jul 2024 11:33:00 -0500 Subject: [PATCH 230/309] moved socket from socket branch --- include/glaze/coroutine/socket.hpp | 539 ++++++++++++++++++++--------- include/glaze/network/core.hpp | 2 + 2 files changed, 368 insertions(+), 173 deletions(-) diff --git a/include/glaze/coroutine/socket.hpp b/include/glaze/coroutine/socket.hpp index 2483e2683e..d13cdcdad9 100644 --- a/include/glaze/coroutine/socket.hpp +++ b/include/glaze/coroutine/socket.hpp @@ -1,215 +1,408 @@ // Glaze Library // For the license information refer to glaze.hpp -// Modified from the awesome: https://github.com/jbaldwin/libcoro +// Glaze Library +// For the license information refer to glaze.hpp #pragma once -#include "glaze/coroutine/poll.hpp" +#include "glaze/network/core.hpp" #include "glaze/network/ip.hpp" +#ifdef _WIN32 +#pragma comment(lib, "Ws2_32.lib") +#define GLZ_SOCKET_ERROR_CODE WSAGetLastError() +#else +#define GLZ_SOCKET_ERROR_CODE errno +#include +#include +#if __has_include() +#include +#endif +#include +#include +#include +#include +#endif + namespace glz { - struct socket +#ifdef _WIN32 + constexpr auto e_would_block = WSAEWOULDBLOCK; + constexpr auto invalid_socket = INVALID_SOCKET; + using socket_t = SOCKET; + constexpr auto socket_error = SOCKET_ERROR; +#else + constexpr auto e_would_block = EWOULDBLOCK; + constexpr auto invalid_socket = -1; + using socket_t = int; + constexpr auto socket_error = -1; +#endif +} + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace glz +{ + inline std::string get_ip_port(const sockaddr_in& server_addr) + { + char ip_str[INET_ADDRSTRLEN]{}; + +#ifdef _WIN32 + inet_ntop(AF_INET, &(server_addr.sin_addr), ip_str, INET_ADDRSTRLEN); +#else + inet_ntop(AF_INET, &(server_addr.sin_addr), ip_str, sizeof(ip_str)); +#endif + + return {std::format("{}:{}", ip_str, ntohs(server_addr.sin_port))}; + } + + inline std::string get_socket_error_message(int err) { - enum struct type { - udp, - tcp - }; +#ifdef _WIN32 + + char* msg = nullptr; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, + err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL); + std::string message(msg); + LocalFree(msg); + return {message}; - enum struct blocking { - yes, - no - }; +#else + return strerror(err); +#endif + } - struct options + struct socket_api_error_category_t final : public std::error_category + { + std::string what{}; + const char* name() const noexcept override { return "socket error"; } + std::string message(int ev) const override { - /// The domain for the socket. - ip_version domain{}; - /// The type of socket. - socket::type type{}; - /// If the socket should be blocking or non-blocking. - socket::blocking blocking{}; - }; - - static int type_to_os(socket::type type) + if (what.empty()) { + return {get_socket_error_message(ev)}; + } + else { + return {std::format("{}\nDetails: {}", what, get_socket_error_message(ev))}; + } + } + void operator()(int ev, const std::string_view w) { - switch (type) - { - case type::udp: - return SOCK_DGRAM; - case type::tcp: - return SOCK_STREAM; - default: - GLZ_THROW_OR_ABORT(std::runtime_error{"Unknown socket::type_t."}); - } + what = w; + this->message(ev); } + }; - socket() = default; - explicit socket(int fd) : m_fd(fd) {} + inline const socket_api_error_category_t& socket_api_error_category(const std::string_view what) + { + static socket_api_error_category_t singleton; + singleton.what = what; + return singleton; + } + + inline std::error_code get_socket_error(const std::string_view what = "") + { +#ifdef _WIN32 + int err = WSAGetLastError(); +#else + int err = errno; +#endif + + return {std::error_code(err, socket_api_error_category(what))}; + } + + inline std::error_code check_status(int ec, const std::string_view what = "") + { + if (ec >= 0) { + return {}; + } + + return {get_socket_error(what)}; + } + + // Example: + // + // std::error_code ec = check_status(result, "connect failed"); + // + // if (ec) { + // std::cerr << get_socket_error(std::format("Failed to connect to socket at address: {}.\nIs the server + // running?", ip_port)).message(); + // } + // else { + // std::cout << "Connected successfully!"; + // } + + // For Windows WSASocket Compatability + + inline constexpr uint16_t make_version(uint8_t low_byte, uint8_t high_byte) noexcept + { + return uint16_t(low_byte) | (uint16_t(high_byte) << 8); + } + + inline constexpr uint8_t major_version(uint16_t version) noexcept + { + return uint8_t(version & 0xFF); // Extract the low byte + } + + inline constexpr uint8_t minor_version(uint16_t version) noexcept + { + return uint8_t((version >> 8) & 0xFF); // Shift right by 8 bits and extract the low byte + } + + // Function to get Winsock version string on Windows, return "na" otherwise + inline std::string get_winsock_version_string(uint32_t version = make_version(2, 2)) + { +#if _WIN32 + BYTE major = major_version(uint16_t(version)); + BYTE minor = minor_version(uint16_t(version)); + return std::format("{}.{}", static_cast(major), static_cast(minor)); +#else + (void)version; + return ""; // Default behavior for non-Windows platforms +#endif + } + + // The 'wsa_startup_t' calls the windows WSAStartup function. This must be the first Windows + // Sockets function called by an application or DLL. It allows an application or DLL to + // specify the version of Windows Sockets required and retrieve details of the specific + // Windows Sockets implementation.The application or DLL can only issue further Windows Sockets + // functions after successfully calling WSAStartup. + // + // Important: WSAStartup and its corresponding WSACleanup must be called on the same thread. + // + template + struct windows_socket_startup_t final + { +#ifdef _WIN64 + WSADATA wsa_data{}; - socket(const socket& other) : m_fd(dup(other.m_fd)) {} - socket(socket&& other) : m_fd(std::exchange(other.m_fd, -1)) {} - - socket& operator=(const socket& other) noexcept + std::error_code error_code{}; + + std::error_code start(const WORD win_sock_version = make_version(2, 2)) // Request latest Winsock version 2.2 + { + static std::once_flag flag{}; + std::error_code startup_error{}; + std::call_once(flag, [this, win_sock_version, &startup_error]() { + int result = WSAStartup(win_sock_version, &wsa_data); + if (result != 0) { + error_code = get_socket_error( + std::format("Unable to initialize Winsock library version {}.", get_winsock_version_string())); + } + }); + return {error_code}; + } + + windows_socket_startup_t() { - this->close(); - this->m_fd = dup(other.m_fd); - return *this; + if constexpr (run_wsa_startup) { + error_code = start(); + } + } + + ~windows_socket_startup_t() { WSACleanup(); } + +#else + std::error_code start() { return std::error_code{}; } +#endif + }; + + GLZ_ENUM(ip_error, none, + queue_create_failed, + event_ctl_failed, + event_wait_failed, + event_enum_failed, + socket_connect_failed, + socket_bind_failed, + send_failed, + receive_failed, + client_disconnected); + + template + concept ip_header = std::same_as || requires { T::body_size; }; + + struct ip_error_category : std::error_category + { + static const ip_error_category& instance() { + static ip_error_category instance{}; + return instance; } - - socket& operator=(socket&& other) noexcept + + const char* name() const noexcept override { return "ip_error_category"; } + + std::string message(int ec) const override { - if (std::addressof(other) != this) - { - m_fd = std::exchange(other.m_fd, -1); - } + return std::string{nameof(static_cast(ec))}; + } + }; + + struct socket + { + socket_t socket_fd{invalid_socket}; - return *this; + void set_non_blocking() + { +#ifdef _WIN32 + u_long mode = 1; + ioctlsocket(socket_fd, FIONBIO, &mode); +#else + int flags = fcntl(socket_fd, F_GETFL, 0); + fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); +#endif + } + + socket() = default; + + socket(socket_t fd) : socket_fd(fd) { set_non_blocking(); } + + void close() + { + if (socket_fd != invalid_socket) { + net::close_socket(socket_fd); + } } ~socket() { close(); } - /** - * This function returns true if the socket's file descriptor is a valid number, however it does - * not imply if the socket is still usable. - * @return True if the socket file descriptor is > 0. - */ - bool valid() const { return m_fd != -1; } - - /** - * @param block Sets the socket to the given blocking mode. - */ - bool blocking(socket::blocking block) + [[nodiscard]] std::error_code connect(const std::string& address, const int port) { - if (m_fd < 0) - { - return false; - } + socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd == -1) { + return {int(ip_error::socket_connect_failed), ip_error_category::instance()}; + } + + sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(uint16_t(port)); + inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); - int flags = fcntl(m_fd, F_GETFL, 0); - if (flags == -1) - { - return false; - } + if (::connect(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { + return {int(ip_error::socket_connect_failed), ip_error_category::instance()}; + } - // Add or subtract non-blocking flag. - flags = (block == blocking::yes) ? flags & ~O_NONBLOCK : (flags | O_NONBLOCK); + set_non_blocking(); - return (fcntl(m_fd, F_SETFL, flags) == 0); + return {}; } - /** - * @param how Shuts the socket down with the given operations. - * @param Returns true if the sockets given operations were shutdown. - */ - bool shutdown(poll_op how = poll_op::read_write) + [[nodiscard]] bool no_delay() { - if (m_fd != -1) - { - int h{0}; - switch (how) - { - case poll_op::read: - h = SHUT_RD; - break; - case poll_op::write: - h = SHUT_WR; - break; - case poll_op::read_write: - h = SHUT_RDWR; - break; - } - - return (::shutdown(m_fd, h) == 0); - } - return false; + int flag = 1; + int result = setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int)); + return result == 0; } - /** - * Closes the socket and sets this socket to an invalid state. - */ - void close() + [[nodiscard]] std::error_code bind_and_listen(int port) { - if (m_fd != -1) - { - ::close(m_fd); - m_fd = -1; - } + socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd == invalid_socket) { + return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; + } + + sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(uint16_t(port)); + + if (::bind(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { + return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; + } + + if (::listen(socket_fd, SOMAXCONN) == -1) { + return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; + } + + set_non_blocking(); + if (not no_delay()) { + return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; + } + + return {}; } - /** - * @return The native handle (file descriptor) for this socket. - */ - int native_handle() const { return m_fd; } + template + [[nodiscard]] std::error_code receive(Header& header, std::string& buffer) + { + // first receive the header + size_t total_bytes{}; + while (total_bytes < sizeof(Header)) { + auto bytes = ::recv(socket_fd, reinterpret_cast(&header) + total_bytes, + size_t(sizeof(Header) - total_bytes), 0); + if (bytes == -1) { + if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + else { + // error + buffer.clear(); + return {int(ip_error::receive_failed), ip_error_category::instance()}; + } + } + else if (bytes == 0) { + return {int(ip_error::client_disconnected), ip_error_category::instance()}; + } - private: - int m_fd{-1}; - }; + total_bytes += bytes; + } - /** - * Creates a socket with the given socket options, this typically is used for creating sockets to - * use within client objects, e.g. tcp::client and udp::client. - * @param opts See socket::options for more details. - */ - socket make_socket(const socket::options& opts) - { - socket s{::socket(int(opts.domain), socket::type_to_os(opts.type), 0)}; - if (not s.valid()) - { - GLZ_THROW_OR_ABORT(std::runtime_error{"Failed to create socket."}); - } - - if (opts.blocking == socket::blocking::no) - { - if (not s.blocking(socket::blocking::no)) - { - GLZ_THROW_OR_ABORT(std::runtime_error{"Failed to set socket to non-blocking mode."}); - } - } - - return s; - } + size_t size{}; + if constexpr (std::same_as) { + size = header; + } + else { + size = size_t(header.body_size); + } - /** - * Creates a socket that can accept connections or packets with the given socket options, address, - * port and backlog. This is used for creating sockets to use within server objects, e.g. - * tcp::server and udp::server. - * @param opts See socket::options for more details - * @param address The ip address to bind to. If the type of socket is tcp then it will also listen. - * @param port The port to bind to. - * @param backlog If the type of socket is tcp then the backlog of connections to allow. Does nothing - * for udp types. - */ - socket make_accept_socket(const socket::options& opts, const std::string& address, uint16_t port, - int32_t backlog = 128) - { - socket s = make_socket(opts); - - int sock_opt{1}; - if (setsockopt(s.native_handle(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &sock_opt, sizeof(sock_opt)) < 0) - { - GLZ_THROW_OR_ABORT(std::runtime_error{"Failed to setsockopt(SO_REUSEADDR | SO_REUSEPORT)"}); - } - - sockaddr_in server{}; - server.sin_family = int(opts.domain); - server.sin_port = htons(port); - server.sin_addr = *reinterpret_cast(address.data()); - - if (bind(s.native_handle(), (struct sockaddr*)&server, sizeof(server)) < 0) - { - GLZ_THROW_OR_ABORT(std::runtime_error{"Failed to bind."}); - } - - if (opts.type == socket::type::tcp) - { - if (listen(s.native_handle(), backlog) < 0) - { - GLZ_THROW_OR_ABORT(std::runtime_error{"Failed to listen."}); - } - } - - return s; - } + buffer.resize(size); + + total_bytes = 0; + while (total_bytes < size) { + auto bytes = ::recv(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); + if (bytes == -1) { + if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + else { + buffer.clear(); + return {int(ip_error::receive_failed), ip_error_category::instance()}; + } + } + else if (bytes == 0) { + return {int(ip_error::client_disconnected), ip_error_category::instance()}; + } + + total_bytes += bytes; + } + return {}; + } + + [[nodiscard]] std::error_code send(const std::string_view buffer) + { + const size_t size = buffer.size(); + size_t total_bytes{}; + while (total_bytes < size) { + auto bytes = ::send(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); + if (bytes == -1) { + if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + std::this_thread::yield(); + continue; + } + else { + return {int(ip_error::send_failed), ip_error_category::instance()}; + } + } + + total_bytes += bytes; + } + return {}; + } + }; } diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index d5c26afaad..387ae3114a 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -3,6 +3,8 @@ #pragma once +#include + #ifndef GLZ_THROW_OR_ABORT #if __cpp_exceptions #define GLZ_THROW_OR_ABORT(EXC) (throw(EXC)) From c27e0dd76c56311b7b915418a47b92c7e593bd37 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 1 Jul 2024 11:49:56 -0500 Subject: [PATCH 231/309] Update socket.hpp --- include/glaze/coroutine/socket.hpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/include/glaze/coroutine/socket.hpp b/include/glaze/coroutine/socket.hpp index d13cdcdad9..645a423431 100644 --- a/include/glaze/coroutine/socket.hpp +++ b/include/glaze/coroutine/socket.hpp @@ -51,17 +51,20 @@ namespace glz namespace glz { - inline std::string get_ip_port(const sockaddr_in& server_addr) + namespace detail { - char ip_str[INET_ADDRSTRLEN]{}; + inline std::string format_ip_port(const sockaddr_in& server_addr) + { + char ip_str[INET_ADDRSTRLEN]{}; -#ifdef _WIN32 - inet_ntop(AF_INET, &(server_addr.sin_addr), ip_str, INET_ADDRSTRLEN); -#else - inet_ntop(AF_INET, &(server_addr.sin_addr), ip_str, sizeof(ip_str)); -#endif + #ifdef _WIN32 + inet_ntop(AF_INET, &(server_addr.sin_addr), ip_str, INET_ADDRSTRLEN); + #else + inet_ntop(AF_INET, &(server_addr.sin_addr), ip_str, sizeof(ip_str)); + #endif - return {std::format("{}:{}", ip_str, ntohs(server_addr.sin_port))}; + return {std::format("{}:{}", ip_str, ntohs(server_addr.sin_port))}; + } } inline std::string get_socket_error_message(int err) @@ -80,7 +83,7 @@ namespace glz #endif } - struct socket_api_error_category_t final : public std::error_category + struct socket_api_error_category_t final : std::error_category { std::string what{}; const char* name() const noexcept override { return "socket error"; } @@ -93,6 +96,7 @@ namespace glz return {std::format("{}\nDetails: {}", what, get_socket_error_message(ev))}; } } + void operator()(int ev, const std::string_view w) { what = w; @@ -115,7 +119,7 @@ namespace glz int err = errno; #endif - return {std::error_code(err, socket_api_error_category(what))}; + return {err, socket_api_error_category(what)}; } inline std::error_code check_status(int ec, const std::string_view what = "") @@ -162,7 +166,7 @@ namespace glz #if _WIN32 BYTE major = major_version(uint16_t(version)); BYTE minor = minor_version(uint16_t(version)); - return std::format("{}.{}", static_cast(major), static_cast(minor)); + return std::format("{}.{}", int(major), int(minor)); #else (void)version; return ""; // Default behavior for non-Windows platforms From c805046d9942f7d72aa5f3a8dd31c69f238fba38 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 1 Jul 2024 11:56:25 -0500 Subject: [PATCH 232/309] fixing socket.hpp --- include/glaze/coroutine.hpp | 1 - include/glaze/network/socket.hpp | 200 ++++++++++++++++++ .../socket.hpp => network/socket_core.hpp} | 185 ---------------- tests/coroutine_test/coroutine_test.cpp | 1 + 4 files changed, 201 insertions(+), 186 deletions(-) create mode 100644 include/glaze/network/socket.hpp rename include/glaze/{coroutine/socket.hpp => network/socket_core.hpp} (52%) diff --git a/include/glaze/coroutine.hpp b/include/glaze/coroutine.hpp index 85b74e190c..e4f803c0a5 100644 --- a/include/glaze/coroutine.hpp +++ b/include/glaze/coroutine.hpp @@ -11,7 +11,6 @@ #include "glaze/coroutine/ring_buffer.hpp" #include "glaze/coroutine/semaphore.hpp" #include "glaze/coroutine/shared_mutex.hpp" -#include "glaze/coroutine/socket.hpp" #include "glaze/coroutine/sync_wait.hpp" #include "glaze/coroutine/task.hpp" #include "glaze/coroutine/thread_pool.hpp" diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp new file mode 100644 index 0000000000..15295cd1da --- /dev/null +++ b/include/glaze/network/socket.hpp @@ -0,0 +1,200 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +#include "glaze/network/core.hpp" +#include "glaze/network/ip.hpp" +#include "glaze/network/socket_core.hpp" + +namespace glz +{ +#ifdef _WIN32 + constexpr auto e_would_block = WSAEWOULDBLOCK; + constexpr auto invalid_socket = INVALID_SOCKET; + using socket_t = SOCKET; + constexpr auto socket_error = SOCKET_ERROR; +#else + constexpr auto e_would_block = EWOULDBLOCK; + constexpr auto invalid_socket = -1; + using socket_t = int; + constexpr auto socket_error = -1; +#endif +} + +#include +#include +#include +#include +#include +#include + +namespace glz +{ + struct socket + { + socket_t socket_fd{invalid_socket}; + + void set_non_blocking() + { +#ifdef _WIN32 + u_long mode = 1; + ioctlsocket(socket_fd, FIONBIO, &mode); +#else + int flags = fcntl(socket_fd, F_GETFL, 0); + fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); +#endif + } + + socket() = default; + + socket(socket_t fd) : socket_fd(fd) { set_non_blocking(); } + + void close() + { + if (socket_fd != invalid_socket) { + net::close_socket(socket_fd); + } + } + + ~socket() { close(); } + + [[nodiscard]] bool no_delay() + { + int flag = 1; + int result = setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int)); + return result == 0; + } + }; + + [[nodiscard]] inline std::error_code connect(socket& sckt, const std::string& address, const int port) + { + sckt.socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (sckt.socket_fd == -1) { + return {int(ip_error::socket_connect_failed), ip_error_category::instance()}; + } + + sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(uint16_t(port)); + inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); + + if (::connect(sckt.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { + return {int(ip_error::socket_connect_failed), ip_error_category::instance()}; + } + + sckt.set_non_blocking(); + + return {}; + } + + [[nodiscard]] inline std::error_code bind_and_listen(socket& sckt, int port) + { + sckt.socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (sckt.socket_fd == invalid_socket) { + return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; + } + + sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(uint16_t(port)); + + if (::bind(sckt.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { + return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; + } + + if (::listen(sckt.socket_fd, SOMAXCONN) == -1) { + return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; + } + + sckt.set_non_blocking(); + if (not sckt.no_delay()) { + return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; + } + + return {}; + } + + template + [[nodiscard]] std::error_code receive(socket& sckt, Header& header, std::string& buffer) + { + // first receive the header + size_t total_bytes{}; + while (total_bytes < sizeof(Header)) { + auto bytes = ::recv(sckt.socket_fd, reinterpret_cast(&header) + total_bytes, + size_t(sizeof(Header) - total_bytes), 0); + if (bytes == -1) { + if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + else { + // error + buffer.clear(); + return {int(ip_error::receive_failed), ip_error_category::instance()}; + } + } + else if (bytes == 0) { + return {int(ip_error::client_disconnected), ip_error_category::instance()}; + } + + total_bytes += bytes; + } + + size_t size{}; + if constexpr (std::same_as) { + size = header; + } + else { + size = size_t(header.body_size); + } + + buffer.resize(size); + + total_bytes = 0; + while (total_bytes < size) { + auto bytes = ::recv(sckt.socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); + if (bytes == -1) { + if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + else { + buffer.clear(); + return {int(ip_error::receive_failed), ip_error_category::instance()}; + } + } + else if (bytes == 0) { + return {int(ip_error::client_disconnected), ip_error_category::instance()}; + } + + total_bytes += bytes; + } + return {}; + } + + [[nodiscard]] inline std::error_code send(socket& sckt, const std::string_view buffer) + { + const size_t size = buffer.size(); + size_t total_bytes{}; + while (total_bytes < size) { + auto bytes = ::send(sckt.socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); + if (bytes == -1) { + if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + std::this_thread::yield(); + continue; + } + else { + return {int(ip_error::send_failed), ip_error_category::instance()}; + } + } + + total_bytes += bytes; + } + return {}; + } +} diff --git a/include/glaze/coroutine/socket.hpp b/include/glaze/network/socket_core.hpp similarity index 52% rename from include/glaze/coroutine/socket.hpp rename to include/glaze/network/socket_core.hpp index 645a423431..5e07be44a3 100644 --- a/include/glaze/coroutine/socket.hpp +++ b/include/glaze/network/socket_core.hpp @@ -25,29 +25,8 @@ #include #endif -namespace glz -{ -#ifdef _WIN32 - constexpr auto e_would_block = WSAEWOULDBLOCK; - constexpr auto invalid_socket = INVALID_SOCKET; - using socket_t = SOCKET; - constexpr auto socket_error = SOCKET_ERROR; -#else - constexpr auto e_would_block = EWOULDBLOCK; - constexpr auto invalid_socket = -1; - using socket_t = int; - constexpr auto socket_error = -1; -#endif -} - -#include #include -#include -#include -#include #include -#include -#include namespace glz { @@ -245,168 +224,4 @@ namespace glz return std::string{nameof(static_cast(ec))}; } }; - - struct socket - { - socket_t socket_fd{invalid_socket}; - - void set_non_blocking() - { -#ifdef _WIN32 - u_long mode = 1; - ioctlsocket(socket_fd, FIONBIO, &mode); -#else - int flags = fcntl(socket_fd, F_GETFL, 0); - fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); -#endif - } - - socket() = default; - - socket(socket_t fd) : socket_fd(fd) { set_non_blocking(); } - - void close() - { - if (socket_fd != invalid_socket) { - net::close_socket(socket_fd); - } - } - - ~socket() { close(); } - - [[nodiscard]] std::error_code connect(const std::string& address, const int port) - { - socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd == -1) { - return {int(ip_error::socket_connect_failed), ip_error_category::instance()}; - } - - sockaddr_in server_addr; - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(uint16_t(port)); - inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); - - if (::connect(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { - return {int(ip_error::socket_connect_failed), ip_error_category::instance()}; - } - - set_non_blocking(); - - return {}; - } - - [[nodiscard]] bool no_delay() - { - int flag = 1; - int result = setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int)); - return result == 0; - } - - [[nodiscard]] std::error_code bind_and_listen(int port) - { - socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd == invalid_socket) { - return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; - } - - sockaddr_in server_addr; - server_addr.sin_family = AF_INET; - server_addr.sin_addr.s_addr = INADDR_ANY; - server_addr.sin_port = htons(uint16_t(port)); - - if (::bind(socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { - return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; - } - - if (::listen(socket_fd, SOMAXCONN) == -1) { - return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; - } - - set_non_blocking(); - if (not no_delay()) { - return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; - } - - return {}; - } - - template - [[nodiscard]] std::error_code receive(Header& header, std::string& buffer) - { - // first receive the header - size_t total_bytes{}; - while (total_bytes < sizeof(Header)) { - auto bytes = ::recv(socket_fd, reinterpret_cast(&header) + total_bytes, - size_t(sizeof(Header) - total_bytes), 0); - if (bytes == -1) { - if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; - } - else { - // error - buffer.clear(); - return {int(ip_error::receive_failed), ip_error_category::instance()}; - } - } - else if (bytes == 0) { - return {int(ip_error::client_disconnected), ip_error_category::instance()}; - } - - total_bytes += bytes; - } - - size_t size{}; - if constexpr (std::same_as) { - size = header; - } - else { - size = size_t(header.body_size); - } - - buffer.resize(size); - - total_bytes = 0; - while (total_bytes < size) { - auto bytes = ::recv(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); - if (bytes == -1) { - if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; - } - else { - buffer.clear(); - return {int(ip_error::receive_failed), ip_error_category::instance()}; - } - } - else if (bytes == 0) { - return {int(ip_error::client_disconnected), ip_error_category::instance()}; - } - - total_bytes += bytes; - } - return {}; - } - - [[nodiscard]] std::error_code send(const std::string_view buffer) - { - const size_t size = buffer.size(); - size_t total_bytes{}; - while (total_bytes < size) { - auto bytes = ::send(socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); - if (bytes == -1) { - if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { - std::this_thread::yield(); - continue; - } - else { - return {int(ip_error::send_failed), ip_error_category::instance()}; - } - } - - total_bytes += bytes; - } - return {}; - } - }; } diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index f8662eb05a..1d8c473ecb 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -4,6 +4,7 @@ #define UT_RUN_TIME_ONLY #include "glaze/coroutine.hpp" +#include "glaze/network/socket.hpp" #include "ut/ut.hpp" From 74ac5969a056903654e54432fcf529f4e6c8d31a Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 1 Jul 2024 12:02:10 -0500 Subject: [PATCH 233/309] invalid_socket into net/core --- include/glaze/network/core.hpp | 7 +++++-- include/glaze/network/socket.hpp | 8 +++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 387ae3114a..e011178ec9 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -52,16 +52,19 @@ namespace glz::net #endif #if defined(__APPLE__) + constexpr auto invalid_socket = -1; using poll_event_t = struct kevent; constexpr int invalid_event_handle = -1; using ident_t = uintptr_t; constexpr uintptr_t invalid_ident = ~uintptr_t(0); // set all bits #elif defined(__linux__) + constexpr auto invalid_socket = -1; using poll_event_t = struct epoll_event; constexpr int invalid_event_handle = -1; using ident_t = int; constexpr int invalid_ident = -1; #elif defined(_WIN32) + constexpr auto invalid_socket = INVALID_SOCKET; using ident_t = HANDLE; using poll_event_t = HANDLE; inline const HANDLE invalid_event_handle = INVALID_HANDLE_VALUE; @@ -81,14 +84,14 @@ namespace glz::net inline auto close_socket(auto& fd) { - if (fd != invalid_event_handle) { + if (fd != invalid_socket) { #ifdef _WIN32 ::closesocket(fd); #else ::close(fd); #endif } - fd = invalid_event_handle; + fd = invalid_socket; } inline auto close_event(auto fd) { diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 15295cd1da..392cc9777d 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -14,12 +14,10 @@ namespace glz { #ifdef _WIN32 constexpr auto e_would_block = WSAEWOULDBLOCK; - constexpr auto invalid_socket = INVALID_SOCKET; using socket_t = SOCKET; constexpr auto socket_error = SOCKET_ERROR; #else constexpr auto e_would_block = EWOULDBLOCK; - constexpr auto invalid_socket = -1; using socket_t = int; constexpr auto socket_error = -1; #endif @@ -36,7 +34,7 @@ namespace glz { struct socket { - socket_t socket_fd{invalid_socket}; + socket_t socket_fd{net::invalid_socket}; void set_non_blocking() { @@ -55,7 +53,7 @@ namespace glz void close() { - if (socket_fd != invalid_socket) { + if (socket_fd != net::invalid_socket) { net::close_socket(socket_fd); } } @@ -94,7 +92,7 @@ namespace glz [[nodiscard]] inline std::error_code bind_and_listen(socket& sckt, int port) { sckt.socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); - if (sckt.socket_fd == invalid_socket) { + if (sckt.socket_fd == net::invalid_socket) { return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; } From dd4245fe45be266bb74378a7b2bec06d0010731d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 1 Jul 2024 12:08:07 -0500 Subject: [PATCH 234/309] fixes --- include/glaze/json/schema.hpp | 4 ++-- include/glaze/network/server.hpp | 10 +++++----- include/glaze/network/socket_io.hpp | 12 ++++++------ tests/socket_test/socket_test.cpp | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/glaze/json/schema.hpp b/include/glaze/json/schema.hpp index 05cc458f7c..5583877a0e 100644 --- a/include/glaze/json/schema.hpp +++ b/include/glaze/json/schema.hpp @@ -284,12 +284,12 @@ namespace glz if constexpr (std::integral) { s.type = {"integer"}; s.attributes.minimum = static_cast(std::numeric_limits::lowest()); - s.attributes.maximum = static_cast(std::numeric_limits::max()); + s.attributes.maximum = static_cast((std::numeric_limits::max)()); } else { s.type = {"number"}; s.attributes.minimum = std::numeric_limits::lowest(); - s.attributes.maximum = std::numeric_limits::max(); + s.attributes.maximum = (std::numeric_limits::max)(); } } }; diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index 4a0574960e..caa0410ee7 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -86,9 +86,9 @@ namespace glz { glz::socket accept_socket{}; - const auto ec = accept_socket.bind_and_listen(port); + const auto ec = bind_and_listen(accept_socket, port); if (ec) { - return {ip_error::socket_bind_failed, ip_error_category::instance()}; + return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; } #if defined(__APPLE__) @@ -100,7 +100,7 @@ namespace glz #endif if (event_fd == GLZ_INVALID_EVENT) { - return {ip_error::queue_create_failed, ip_error_category::instance()}; + return {int(ip_error::queue_create_failed), ip_error_category::instance()}; } bool event_setup_failed = false; @@ -119,7 +119,7 @@ namespace glz if (event_setup_failed) { GLZ_EVENT_CLOSE(event_fd); - return {ip_error::event_ctl_failed, ip_error_category::instance()}; + return {int(ip_error::event_ctl_failed), ip_error_category::instance()}; } #if defined(__APPLE__) @@ -150,7 +150,7 @@ namespace glz if (n == WSA_WAIT_TIMEOUT) continue; #endif GLZ_EVENT_CLOSE(event_fd); - return {ip_error::event_wait_failed, ip_error_category::instance()}; + return {int(ip_error::event_wait_failed), ip_error_category::instance()}; } auto spawn_socket = [&] { diff --git a/include/glaze/network/socket_io.hpp b/include/glaze/network/socket_io.hpp index f168475bc7..453697a073 100644 --- a/include/glaze/network/socket_io.hpp +++ b/include/glaze/network/socket_io.hpp @@ -13,7 +13,7 @@ namespace glz [[nodiscard]] std::error_code receive(socket& sckt, Buffer& buffer) { uint64_t header{}; - if (auto ec = sckt.receive(header, buffer)) { + if (auto ec = receive(sckt, header, buffer)) { return ec; } return {}; @@ -24,11 +24,11 @@ namespace glz { uint64_t header = uint64_t(buffer.size()); - if (auto ec = sckt.send(sv{reinterpret_cast(&header), sizeof(header)})) { + if (auto ec = send(sckt, sv{reinterpret_cast(&header), sizeof(header)})) { return ec; } - if (auto ec = sckt.send(buffer)) { + if (auto ec = send(sckt, buffer)) { return ec; } @@ -41,7 +41,7 @@ namespace glz static thread_local std::string buffer{}; uint64_t header{}; - if (auto ec = sckt.receive(header, buffer)) { + if (auto ec = receive(sckt, header, buffer)) { return ec; } @@ -63,11 +63,11 @@ namespace glz uint64_t header = uint64_t(buffer.size()); - if (auto ec = sckt.send(sv{reinterpret_cast(&header), sizeof(header)})) { + if (auto ec = send(sckt, sv{reinterpret_cast(&header), sizeof(header)})) { return ec; } - if (auto ec = sckt.send(buffer)) { + if (auto ec = send(sckt, buffer)) { return ec; } diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 11e7ae405c..d42d20a0db 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -63,7 +63,7 @@ suite socket_test = [] { threads.emplace_back(std::async([id, &sockets] { glz::socket& socket = sockets[id]; - if (socket.connect(service_0_ip, service_0_port)) { + if (connect(socket, service_0_ip, service_0_port)) { std::cerr << std::format("Failed to connect to server.\nDetails: {}\n", glz::get_socket_error().message()); } else { From 2b64967df04aeae847af12bf8ee6eeeb42b08238 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Mon, 1 Jul 2024 13:02:44 -0500 Subject: [PATCH 235/309] Corrected Windows compatibility errors. --- include/glaze/network/core.hpp | 2 +- include/glaze/network/server.hpp | 8 +++++++- include/glaze/network/socket.hpp | 7 ++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index e011178ec9..6166d4bc5e 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -46,7 +46,7 @@ namespace glz::net { #ifdef _WIN32 using event_handle_t = HANDLE; - using ssize_t = int64_t; + using ssize_t = int32_t; #else using event_handle_t = int; #endif diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index caa0410ee7..85afae85e6 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -177,8 +177,14 @@ namespace glz #else // Windows WSANETWORKEVENTS events; if (WSAEnumNetworkEvents(accept_socket.socket_fd, event_fd, &events) == GLZ_SOCKET_ERROR) { + WSACloseEvent(event_fd); - return {ip_error::event_enum_failed, ip_error_category::instance()}; + + // requires explicit 'std::error_code'...otherwise the following error with msvc... + // + // error C2440: 'return': cannot convert from 'initializer list' to 'std::error_code' + // + return {int(ip_error::event_enum_failed), ip_error_category::instance()}; } if (events.lNetworkEvents & FD_ACCEPT) { diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 392cc9777d..2aaaebe60e 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -124,7 +124,7 @@ namespace glz size_t total_bytes{}; while (total_bytes < sizeof(Header)) { auto bytes = ::recv(sckt.socket_fd, reinterpret_cast(&header) + total_bytes, - size_t(sizeof(Header) - total_bytes), 0); + glz::net::ssize_t(sizeof(Header) - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -155,7 +155,8 @@ namespace glz total_bytes = 0; while (total_bytes < size) { - auto bytes = ::recv(sckt.socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); + auto bytes = + ::recv(sckt.socket_fd, buffer.data() + total_bytes, glz::net::ssize_t(buffer.size() - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -180,7 +181,7 @@ namespace glz const size_t size = buffer.size(); size_t total_bytes{}; while (total_bytes < size) { - auto bytes = ::send(sckt.socket_fd, buffer.data() + total_bytes, size_t(buffer.size() - total_bytes), 0); + auto bytes = ::send(sckt.socket_fd, buffer.data() + total_bytes, glz::net::ssize_t(buffer.size() - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::yield(); From 3bcd924fd7291d30b79bab28466bf99386c212a3 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Mon, 1 Jul 2024 13:17:11 -0500 Subject: [PATCH 236/309] Clean-up on socket_io.hpp --- include/glaze/network/socket_io.hpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/include/glaze/network/socket_io.hpp b/include/glaze/network/socket_io.hpp index 453697a073..4f78186d31 100644 --- a/include/glaze/network/socket_io.hpp +++ b/include/glaze/network/socket_io.hpp @@ -23,16 +23,12 @@ namespace glz [[nodiscard]] std::error_code send(socket& sckt, Buffer& buffer) { uint64_t header = uint64_t(buffer.size()); - + if (auto ec = send(sckt, sv{reinterpret_cast(&header), sizeof(header)})) { return ec; } - if (auto ec = send(sckt, buffer)) { - return ec; - } - - return {}; + return send(sckt, buffer); } template @@ -67,10 +63,6 @@ namespace glz return ec; } - if (auto ec = send(sckt, buffer)) { - return ec; - } - - return {}; + return send(sckt, buffer); } } From 458f93ec1089fcee4d659fc9e976112d5203eb12 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Mon, 1 Jul 2024 14:28:11 -0500 Subject: [PATCH 237/309] Corrected recursive call error on 'send' --- include/glaze/network/socket.hpp | 4 +-- include/glaze/network/socket_io.hpp | 52 ++++++++++++++--------------- tests/socket_test/socket_test.cpp | 2 +- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 2aaaebe60e..f962a011ac 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -118,7 +118,7 @@ namespace glz } template - [[nodiscard]] std::error_code receive(socket& sckt, Header& header, std::string& buffer) + [[nodiscard]] std::error_code raw_receive(socket& sckt, Header& header, std::string& buffer) { // first receive the header size_t total_bytes{}; @@ -176,7 +176,7 @@ namespace glz return {}; } - [[nodiscard]] inline std::error_code send(socket& sckt, const std::string_view buffer) + [[nodiscard]] inline std::error_code raw_send(socket& sckt, const std::string_view buffer) { const size_t size = buffer.size(); size_t total_bytes{}; diff --git a/include/glaze/network/socket_io.hpp b/include/glaze/network/socket_io.hpp index 4f78186d31..c540fff2b7 100644 --- a/include/glaze/network/socket_io.hpp +++ b/include/glaze/network/socket_io.hpp @@ -9,60 +9,60 @@ namespace glz { - template - [[nodiscard]] std::error_code receive(socket& sckt, Buffer& buffer) - { - uint64_t header{}; - if (auto ec = receive(sckt, header, buffer)) { - return ec; - } - return {}; - } - template [[nodiscard]] std::error_code send(socket& sckt, Buffer& buffer) { uint64_t header = uint64_t(buffer.size()); - if (auto ec = send(sckt, sv{reinterpret_cast(&header), sizeof(header)})) { + if (auto ec = raw_send(sckt, sv{reinterpret_cast(&header), sizeof(header)})) { return ec; } - return send(sckt, buffer); + return raw_send(sckt, buffer); } template - [[nodiscard]] std::error_code receive_value(socket& sckt, T&& value) + [[nodiscard]] std::error_code send_value(socket& sckt, T&& value) { static thread_local std::string buffer{}; - uint64_t header{}; - if (auto ec = receive(sckt, header, buffer)) { - return ec; + if (auto ec = glz::write(std::forward(value), buffer)) { + return {int(ec.ec), error_category::instance()}; } - if (auto ec = glz::read(std::forward(value), buffer)) { - return {int(ec.ec), error_category::instance()}; + uint64_t header = uint64_t(buffer.size()); + + if (auto ec = raw_send(sckt, sv{reinterpret_cast(&header), sizeof(header)})) { + return ec; } + return raw_send(sckt, buffer); + } + + template + [[nodiscard]] std::error_code receive(socket& sckt, Buffer& buffer) + { + uint64_t header{}; + if (auto ec = receive(sckt, header, buffer)) { + return ec; + } return {}; } template - [[nodiscard]] std::error_code send_value(socket& sckt, T&& value) + [[nodiscard]] std::error_code receive_value(socket& sckt, T&& value) { static thread_local std::string buffer{}; - if (auto ec = glz::write(std::forward(value), buffer)) { - return {int(ec.ec), error_category::instance()}; + uint64_t header{}; + if (auto ec = raw_receive(sckt, header, buffer)) { + return ec; } - uint64_t header = uint64_t(buffer.size()); - - if (auto ec = send(sckt, sv{reinterpret_cast(&header), sizeof(header)})) { - return ec; + if (auto ec = glz::read(std::forward(value), buffer)) { + return {int(ec.ec), error_category::instance()}; } - return send(sckt, buffer); + return {}; } } diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index d42d20a0db..4b54b52132 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -17,7 +17,7 @@ using namespace ut; constexpr bool user_input = false; -constexpr auto n_clients = 10; +constexpr auto n_clients = 1; constexpr auto service_0_port{8080}; constexpr auto service_0_ip{"127.0.0.1"}; From 88ab52a51cb0f2fb21f1c466dc9da731fdaca68f Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Mon, 1 Jul 2024 14:43:58 -0500 Subject: [PATCH 238/309] Identified additional logic errors. --- tests/socket_test/socket_test.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 4b54b52132..1fb64437ce 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -17,7 +17,7 @@ using namespace ut; constexpr bool user_input = false; -constexpr auto n_clients = 1; +constexpr auto n_clients = 10; constexpr auto service_0_port{8080}; constexpr auto service_0_ip{"127.0.0.1"}; @@ -46,8 +46,8 @@ suite make_server = [] { std::cerr << ec.message() << '\n'; return; } - std::cout << std::format("Server: {}\n", received); + glz::send_value(client, std::format("Hello to {} from server.\n", received)); } }); @@ -75,11 +75,22 @@ suite socket_test = [] { std::cout << std::format("Received from server: {}\n", received); size_t tick{}; + std::string result; while (tick < 3) { if (auto ec = glz::send_value(socket, std::format("Client {}, {}", id, tick))) { + std::cerr << ec.message() << '\n'; return; } + if (auto ec = glz::receive_value(socket, result)) { + continue; + } + else { + expect(result.size() > 0); + std::cout << result; + } + + std::this_thread::sleep_for(std::chrono::seconds(2)); ++tick; } From 0669808681a0fe96e87beae18aa015ff1e4a1af5 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 1 Jul 2024 15:29:48 -0500 Subject: [PATCH 239/309] fix mac/linux --- include/glaze/network/core.hpp | 1 + tests/socket_test/socket_test.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index 6166d4bc5e..eacbddaf06 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -49,6 +49,7 @@ namespace glz::net using ssize_t = int32_t; #else using event_handle_t = int; + using ssize_t = ::ssize_t; #endif #if defined(__APPLE__) diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 1fb64437ce..096ada53ba 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -47,7 +47,7 @@ suite make_server = [] { return; } std::cout << std::format("Server: {}\n", received); - glz::send_value(client, std::format("Hello to {} from server.\n", received)); + std::ignore = glz::send_value(client, std::format("Hello to {} from server.\n", received)); } }); From 579bf7cf23ef1fedccc8005b17bc0a0b73bbfc2e Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 09:05:32 -0500 Subject: [PATCH 240/309] cleaning up socket_io --- include/glaze/network/socket_io.hpp | 34 ++++------------------------- tests/socket_test/socket_test.cpp | 12 +++++----- 2 files changed, 10 insertions(+), 36 deletions(-) diff --git a/include/glaze/network/socket_io.hpp b/include/glaze/network/socket_io.hpp index c540fff2b7..5dafd697e6 100644 --- a/include/glaze/network/socket_io.hpp +++ b/include/glaze/network/socket_io.hpp @@ -9,23 +9,9 @@ namespace glz { - template - [[nodiscard]] std::error_code send(socket& sckt, Buffer& buffer) + template + [[nodiscard]] std::error_code send(socket& sckt, T&& value, Buffer&& buffer) { - uint64_t header = uint64_t(buffer.size()); - - if (auto ec = raw_send(sckt, sv{reinterpret_cast(&header), sizeof(header)})) { - return ec; - } - - return raw_send(sckt, buffer); - } - - template - [[nodiscard]] std::error_code send_value(socket& sckt, T&& value) - { - static thread_local std::string buffer{}; - if (auto ec = glz::write(std::forward(value), buffer)) { return {int(ec.ec), error_category::instance()}; } @@ -39,21 +25,9 @@ namespace glz return raw_send(sckt, buffer); } - template - [[nodiscard]] std::error_code receive(socket& sckt, Buffer& buffer) + template + [[nodiscard]] std::error_code receive(socket& sckt, T&& value, Buffer&& buffer) { - uint64_t header{}; - if (auto ec = receive(sckt, header, buffer)) { - return ec; - } - return {}; - } - - template - [[nodiscard]] std::error_code receive_value(socket& sckt, T&& value) - { - static thread_local std::string buffer{}; - uint64_t header{}; if (auto ec = raw_receive(sckt, header, buffer)) { return ec; diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 096ada53ba..89934fddea 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -35,19 +35,19 @@ suite make_server = [] { const auto future = server.async_accept([](glz::socket&& client, auto& active) { std::cout << "New client connected!\n"; - if (auto ec = glz::send_value(client, "Welcome!")) { + if (auto ec = glz::send(client, "Welcome!", std::string{})) { std::cerr << ec.message() << '\n'; return; } while (active) { std::string received{}; - if (auto ec = glz::receive_value(client, received)) { + if (auto ec = glz::receive(client, received, std::string{})) { std::cerr << ec.message() << '\n'; return; } std::cout << std::format("Server: {}\n", received); - std::ignore = glz::send_value(client, std::format("Hello to {} from server.\n", received)); + std::ignore = glz::send(client, std::format("Hello to {} from server.\n", received), std::string{}); } }); @@ -68,7 +68,7 @@ suite socket_test = [] { } else { std::string received{}; - if (auto ec = glz::receive_value(socket, received)) { + if (auto ec = glz::receive(socket, received, std::string{})) { std::cerr << ec.message() << '\n'; return; } @@ -77,12 +77,12 @@ suite socket_test = [] { size_t tick{}; std::string result; while (tick < 3) { - if (auto ec = glz::send_value(socket, std::format("Client {}, {}", id, tick))) { + if (auto ec = glz::send(socket, std::format("Client {}, {}", id, tick), std::string{})) { std::cerr << ec.message() << '\n'; return; } - if (auto ec = glz::receive_value(socket, result)) { + if (auto ec = glz::receive(socket, result, std::string{})) { continue; } else { From 7d8173589290a9c604fa38862a8086e13134dc3d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 09:34:06 -0500 Subject: [PATCH 241/309] async_recv --- include/glaze/network/socket.hpp | 52 +++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index f962a011ac..b2a7176df4 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -117,30 +117,60 @@ namespace glz return {}; } + GLZ_ENUM(socket_event, bytes_read, wait, client_disconnected, receive_failed); + + struct socket_state + { + size_t bytes_read{}; + socket_event event{}; + }; + + [[nodiscard]] inline socket_state async_recv(socket& sckt, char* buffer, size_t size) + { + auto bytes = ::recv(sckt.socket_fd, buffer, net::ssize_t(size), 0); + if (bytes == -1) { + if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + return {0, socket_event::wait}; + } + else { + return {0, socket_event::receive_failed}; + } + } + else if (bytes == 0) { + return {0, socket_event::client_disconnected}; + } + return {size_t(bytes), socket_event::bytes_read}; + } + template [[nodiscard]] std::error_code raw_receive(socket& sckt, Header& header, std::string& buffer) { // first receive the header size_t total_bytes{}; while (total_bytes < sizeof(Header)) { - auto bytes = ::recv(sckt.socket_fd, reinterpret_cast(&header) + total_bytes, - glz::net::ssize_t(sizeof(Header) - total_bytes), 0); - if (bytes == -1) { - if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + auto[bytes, event] = async_recv(sckt, reinterpret_cast(&header) + total_bytes, sizeof(Header) - total_bytes); + using enum socket_event; + switch (event) + { + case bytes_read: { + total_bytes += bytes; + break; + } + case wait: { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } - else { - // error + case client_disconnected: { + return {int(ip_error::client_disconnected), ip_error_category::instance()}; + } + case receive_failed: { + [[fallthrough]]; + } + default: { buffer.clear(); return {int(ip_error::receive_failed), ip_error_category::instance()}; } } - else if (bytes == 0) { - return {int(ip_error::client_disconnected), ip_error_category::instance()}; - } - - total_bytes += bytes; } size_t size{}; From 5fe1e029a8ecb69965183ad29dab5540fe8f5edb Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 09:42:03 -0500 Subject: [PATCH 242/309] updates --- include/glaze/network/socket.hpp | 28 +++++++++++++++++----------- include/glaze/network/socket_io.hpp | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index b2a7176df4..16ff6feddd 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -143,7 +143,7 @@ namespace glz } template - [[nodiscard]] std::error_code raw_receive(socket& sckt, Header& header, std::string& buffer) + [[nodiscard]] std::error_code blocking_header_receive(socket& sckt, Header& header, std::string& buffer) { // first receive the header size_t total_bytes{}; @@ -185,23 +185,29 @@ namespace glz total_bytes = 0; while (total_bytes < size) { - auto bytes = - ::recv(sckt.socket_fd, buffer.data() + total_bytes, glz::net::ssize_t(buffer.size() - total_bytes), 0); - if (bytes == -1) { - if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { + auto[bytes, event] = async_recv(sckt, buffer.data() + total_bytes, buffer.size() - total_bytes); + using enum socket_event; + switch (event) + { + case bytes_read: { + total_bytes += bytes; + break; + } + case wait: { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } - else { + case client_disconnected: { + return {int(ip_error::client_disconnected), ip_error_category::instance()}; + } + case receive_failed: { + [[fallthrough]]; + } + default: { buffer.clear(); return {int(ip_error::receive_failed), ip_error_category::instance()}; } } - else if (bytes == 0) { - return {int(ip_error::client_disconnected), ip_error_category::instance()}; - } - - total_bytes += bytes; } return {}; } diff --git a/include/glaze/network/socket_io.hpp b/include/glaze/network/socket_io.hpp index 5dafd697e6..eafd73c74c 100644 --- a/include/glaze/network/socket_io.hpp +++ b/include/glaze/network/socket_io.hpp @@ -29,7 +29,7 @@ namespace glz [[nodiscard]] std::error_code receive(socket& sckt, T&& value, Buffer&& buffer) { uint64_t header{}; - if (auto ec = raw_receive(sckt, header, buffer)) { + if (auto ec = blocking_header_receive(sckt, header, buffer)) { return ec; } From fbc8a23faac0832a2001162c753fc115b6ccb9a3 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 09:43:43 -0500 Subject: [PATCH 243/309] updates --- include/glaze/network/socket.hpp | 2 +- include/glaze/network/socket_io.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 16ff6feddd..a58c018ff3 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -212,7 +212,7 @@ namespace glz return {}; } - [[nodiscard]] inline std::error_code raw_send(socket& sckt, const std::string_view buffer) + [[nodiscard]] inline std::error_code blocking_send(socket& sckt, const std::string_view buffer) { const size_t size = buffer.size(); size_t total_bytes{}; diff --git a/include/glaze/network/socket_io.hpp b/include/glaze/network/socket_io.hpp index eafd73c74c..9dc064cf33 100644 --- a/include/glaze/network/socket_io.hpp +++ b/include/glaze/network/socket_io.hpp @@ -18,11 +18,11 @@ namespace glz uint64_t header = uint64_t(buffer.size()); - if (auto ec = raw_send(sckt, sv{reinterpret_cast(&header), sizeof(header)})) { + if (auto ec = blocking_send(sckt, sv{reinterpret_cast(&header), sizeof(header)})) { return ec; } - return raw_send(sckt, buffer); + return blocking_send(sckt, buffer); } template From b69c419a60d718e5115f09055b05bdbf45ab3ccf Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 10:04:56 -0500 Subject: [PATCH 244/309] timeout_ms --- include/glaze/network/socket.hpp | 16 +++++++++++++++- include/glaze/network/socket_core.hpp | 1 + include/glaze/network/socket_io.hpp | 4 ++-- tests/socket_test/socket_test.cpp | 6 +++--- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index a58c018ff3..7562068b45 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -10,6 +10,8 @@ #include "glaze/network/ip.hpp" #include "glaze/network/socket_core.hpp" +#include + namespace glz { #ifdef _WIN32 @@ -143,11 +145,17 @@ namespace glz } template - [[nodiscard]] std::error_code blocking_header_receive(socket& sckt, Header& header, std::string& buffer) + [[nodiscard]] std::error_code blocking_header_receive(socket& sckt, Header& header, std::string& buffer, size_t timeout_ms) { // first receive the header + auto t0 = std::chrono::steady_clock::now(); size_t total_bytes{}; while (total_bytes < sizeof(Header)) { + auto t1 = std::chrono::steady_clock::now(); + if (size_t(std::chrono::duration_cast(t1 - t0).count()) >= timeout_ms) { + std::cout << std::chrono::duration_cast(t1 - t0).count() << '\n'; + return {int(ip_error::receive_timeout), ip_error_category::instance()}; + } auto[bytes, event] = async_recv(sckt, reinterpret_cast(&header) + total_bytes, sizeof(Header) - total_bytes); using enum socket_event; switch (event) @@ -183,8 +191,14 @@ namespace glz buffer.resize(size); + t0 = std::chrono::steady_clock::now(); total_bytes = 0; while (total_bytes < size) { + auto t1 = std::chrono::steady_clock::now(); + if (size_t(std::chrono::duration_cast(t1 - t0).count()) >= timeout_ms) { + std::cout << std::chrono::duration_cast(t1 - t0).count() << '\n'; + return {int(ip_error::receive_timeout), ip_error_category::instance()}; + } auto[bytes, event] = async_recv(sckt, buffer.data() + total_bytes, buffer.size() - total_bytes); using enum socket_event; switch (event) diff --git a/include/glaze/network/socket_core.hpp b/include/glaze/network/socket_core.hpp index 5e07be44a3..403c036ea0 100644 --- a/include/glaze/network/socket_core.hpp +++ b/include/glaze/network/socket_core.hpp @@ -205,6 +205,7 @@ namespace glz socket_bind_failed, send_failed, receive_failed, + receive_timeout, client_disconnected); template diff --git a/include/glaze/network/socket_io.hpp b/include/glaze/network/socket_io.hpp index 9dc064cf33..81c33af1d1 100644 --- a/include/glaze/network/socket_io.hpp +++ b/include/glaze/network/socket_io.hpp @@ -26,10 +26,10 @@ namespace glz } template - [[nodiscard]] std::error_code receive(socket& sckt, T&& value, Buffer&& buffer) + [[nodiscard]] std::error_code receive(socket& sckt, T&& value, Buffer&& buffer, size_t timeout_ms) { uint64_t header{}; - if (auto ec = blocking_header_receive(sckt, header, buffer)) { + if (auto ec = blocking_header_receive(sckt, header, buffer, timeout_ms)) { return ec; } diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 89934fddea..96c7bda597 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -42,7 +42,7 @@ suite make_server = [] { while (active) { std::string received{}; - if (auto ec = glz::receive(client, received, std::string{})) { + if (auto ec = glz::receive(client, received, std::string{}, 5000)) { std::cerr << ec.message() << '\n'; return; } @@ -68,7 +68,7 @@ suite socket_test = [] { } else { std::string received{}; - if (auto ec = glz::receive(socket, received, std::string{})) { + if (auto ec = glz::receive(socket, received, std::string{}, 100)) { std::cerr << ec.message() << '\n'; return; } @@ -82,7 +82,7 @@ suite socket_test = [] { std::cerr << ec.message() << '\n'; return; } - if (auto ec = glz::receive(socket, result, std::string{})) { + if (auto ec = glz::receive(socket, result, std::string{}, 100)) { continue; } else { From 77b19089b900414745b9858606aa8420ed069cfd Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Tue, 2 Jul 2024 11:31:25 -0500 Subject: [PATCH 245/309] Added client.hpp --- include/glaze/network/client.hpp | 140 +++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 include/glaze/network/client.hpp diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp new file mode 100644 index 0000000000..6ab0d0c095 --- /dev/null +++ b/include/glaze/network/client.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include "server.hpp" + +namespace glz +{ + + class client + { + public: + struct options + { + /// The ip address to connect to. Use a dns_resolver to turn hostnames into ip addresses. + glz::net::ip_address address{net::ip_address::from_string("127.0.0.1")}; + /// The port to connect to. + uint16_t port{8080}; + }; + + /** + * Creates a new tcp client that can connect to an ip address + port. By default the socket + * created will be in non-blocking mode, meaning that any sending or receiving of data should + * poll for event readiness prior. + * @param scheduler The io scheduler to drive the tcp client. + * @param opts See client::options for more information. + */ + explicit client(std::shared_ptr scheduler, + options opts = options{ + .address = {net::ip_address::from_string("127.0.0.1")}, + .port = 8080, + }); + client(const client& other); + client(client&& other); + auto operator=(const client& other) noexcept -> client&; + auto operator=(client&& other) noexcept -> client&; + ~client(); + + /** + * @return The tcp socket this client is using. + * @{ + **/ + auto socket() -> net::socket& { return m_socket; } + auto socket() const -> const net::socket& { return m_socket; } + /** @} */ + + /** + * Connects to the address+port with the given timeout. Once connected calling this function + * only returns the connected status, it will not reconnect. + * @param timeout How long to wait for the connection to establish? Timeout of zero is indefinite. + * @return The result status of trying to connect. + */ + auto connect(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) -> coro::task; + + /** + * Polls for the given operation on this client's tcp socket. This should be done prior to + * calling recv and after a send that doesn't send the entire buffer. + * @param op The poll operation to perform, use read for incoming data and write for outgoing. + * @param timeout The amount of time to wait for the poll event to be ready. Use zero for infinte timeout. + * @return The status result of th poll operation. When poll_status::event is returned then the + * event operation is ready. + */ + auto poll(coro::poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) + -> coro::task + { + return m_io_scheduler->poll(m_socket, op, timeout); + } + + /** + * Receives incoming data into the given buffer. By default since all tcp client sockets are set + * to non-blocking use co_await poll() to determine when data is ready to be received. + * @param buffer Received bytes are written into this buffer up to the buffers size. + * @return The status of the recv call and a span of the bytes recevied (if any). The span of + * bytes will be a subspan or full span of the given input buffer. + */ + template + auto recv(buffer_type&& buffer) -> std::pair> + { + // If the user requested zero bytes, just return. + if (buffer.empty()) { + return {recv_status::ok, std::span{}}; + } + + auto bytes_recv = ::recv(m_socket.native_handle(), buffer.data(), buffer.size(), 0); + if (bytes_recv > 0) { + // Ok, we've recieved some data. + return {recv_status::ok, std::span{buffer.data(), static_cast(bytes_recv)}}; + } + else if (bytes_recv == 0) { + // On TCP stream sockets 0 indicates the connection has been closed by the peer. + return {recv_status::closed, std::span{}}; + } + else { + // Report the error to the user. + return {static_cast(errno), std::span{}}; + } + } + + /** + * Sends outgoing data from the given buffer. If a partial write occurs then use co_await poll() + * to determine when the tcp client socket is ready to be written to again. On partial writes + * the status will be 'ok' and the span returned will be non-empty, it will contain the buffer + * span data that was not written to the client's socket. + * @param buffer The data to write on the tcp socket. + * @return The status of the send call and a span of any remaining bytes not sent. If all bytes + * were successfully sent the status will be 'ok' and the remaining span will be empty. + */ + template + auto send(const buffer_type& buffer) -> std::pair> + { + // If the user requested zero bytes, just return. + if (buffer.empty()) { + return {send_status::ok, std::span{buffer.data(), buffer.size()}}; + } + + auto bytes_sent = ::send(m_socket.native_handle(), buffer.data(), buffer.size(), 0); + if (bytes_sent >= 0) { + // Some or all of the bytes were written. + return {send_status::ok, std::span{buffer.data() + bytes_sent, buffer.size() - bytes_sent}}; + } + else { + // Due to the error none of the bytes were written. + return {static_cast(errno), std::span{buffer.data(), buffer.size()}}; + } + } + + private: + /// The tcp::server creates already connected clients and provides a tcp socket pre-built. + friend server; + client(std::shared_ptr scheduler, net::socket socket, options opts); + + /// The scheduler that will drive this tcp client. + std::shared_ptr m_io_scheduler{nullptr}; + /// Options for what server to connect to. + options m_options{}; + /// The tcp socket. + net::socket m_socket{-1}; + /// Cache the status of the connect in the event the user calls connect() again. + std::optional m_connect_status{std::nullopt}; + }; + +} // namespace glz From 0d132c672ffac8694816f32345bfe00b4ab52579 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 12:04:17 -0500 Subject: [PATCH 246/309] updates --- include/glaze/network/client.hpp | 120 +++++++++++++++++-------------- include/glaze/network/socket.hpp | 6 +- 2 files changed, 69 insertions(+), 57 deletions(-) diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 6ab0d0c095..d41ed53985 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -1,46 +1,27 @@ +// Glaze Library +// For the license information refer to glaze.hpp + #pragma once -#include "server.hpp" +#include "glaze/coroutine/io_scheduler.hpp" +#include "glaze/network/socket.hpp" namespace glz { - - class client + /** + * By default the socket + * created will be in non-blocking mode, meaning that any sending or receiving of data should + * poll for event readiness prior. + */ + struct client { - public: - struct options - { - /// The ip address to connect to. Use a dns_resolver to turn hostnames into ip addresses. - glz::net::ip_address address{net::ip_address::from_string("127.0.0.1")}; - /// The port to connect to. - uint16_t port{8080}; - }; - - /** - * Creates a new tcp client that can connect to an ip address + port. By default the socket - * created will be in non-blocking mode, meaning that any sending or receiving of data should - * poll for event readiness prior. - * @param scheduler The io scheduler to drive the tcp client. - * @param opts See client::options for more information. - */ - explicit client(std::shared_ptr scheduler, - options opts = options{ - .address = {net::ip_address::from_string("127.0.0.1")}, - .port = 8080, - }); - client(const client& other); - client(client&& other); - auto operator=(const client& other) noexcept -> client&; - auto operator=(client&& other) noexcept -> client&; - ~client(); - - /** - * @return The tcp socket this client is using. - * @{ - **/ - auto socket() -> net::socket& { return m_socket; } - auto socket() const -> const net::socket& { return m_socket; } - /** @} */ + std::string address{"127.0.0.1"}; + uint16_t port{8080}; + std::shared_ptr scheduler{}; + ip_version ipv{}; + glz::socket socket{}; + /// Cache the status of the connect in the event the user calls connect() again. + std::optional m_connect_status{}; /** * Connects to the address+port with the given timeout. Once connected calling this function @@ -48,7 +29,53 @@ namespace glz * @param timeout How long to wait for the connection to establish? Timeout of zero is indefinite. * @return The result status of trying to connect. */ - auto connect(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) -> coro::task; + coro::task connect(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) + { + // Only allow the user to connect per tcp client once, if they need to re-connect they should + // make a new tcp::client. + if (m_connect_status.has_value()) { + co_return m_connect_status.value(); + } + + // This enforces the connection status is aways set on the client object upon returning. + auto return_value = [this](connect_status s) -> connect_status { + m_connect_status = s; + return s; + }; + + sockaddr_in server_addr; + server_addr.sin_family = int(ipv); + server_addr.sin_port = htons(port); + ::inet_pton(int(ipv), address.c_str(), &server_addr.sin_addr); + + auto result = ::connect(socket.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)); + if (result == 0) { + co_return return_value(connect_status::connected); + } + else if (result == -1) { + // If the connect is happening in the background poll for write on the socket to trigger + // when the connection is established. + if (errno == EAGAIN || errno == EINPROGRESS) { + auto pstatus = co_await io_scheduler->poll(m_socket, poll_op::write, timeout); + if (pstatus == poll_status::event) { + int result{0}; + socklen_t result_length{sizeof(result)}; + if (getsockopt(m_socket.native_handle(), SOL_SOCKET, SO_ERROR, &result, &result_length) < 0) { + std::cerr << "connect failed to getsockopt after write poll event\n"; + } + + if (result == 0) { + co_return return_value(connect_status::connected); + } + } + else if (pstatus == poll_status::timeout) { + co_return return_value(connect_status::timeout); + } + } + } + + co_return return_value(connect_status::error); + } /** * Polls for the given operation on this client's tcp socket. This should be done prior to @@ -121,20 +148,5 @@ namespace glz return {static_cast(errno), std::span{buffer.data(), buffer.size()}}; } } - - private: - /// The tcp::server creates already connected clients and provides a tcp socket pre-built. - friend server; - client(std::shared_ptr scheduler, net::socket socket, options opts); - - /// The scheduler that will drive this tcp client. - std::shared_ptr m_io_scheduler{nullptr}; - /// Options for what server to connect to. - options m_options{}; - /// The tcp socket. - net::socket m_socket{-1}; - /// Cache the status of the connect in the event the user calls connect() again. - std::optional m_connect_status{std::nullopt}; }; - -} // namespace glz +} diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 7562068b45..90a9ebf114 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -73,14 +73,14 @@ namespace glz [[nodiscard]] inline std::error_code connect(socket& sckt, const std::string& address, const int port) { sckt.socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); - if (sckt.socket_fd == -1) { + if (sckt.socket_fd == net::invalid_socket) { return {int(ip_error::socket_connect_failed), ip_error_category::instance()}; } sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(uint16_t(port)); - inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); + ::inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); if (::connect(sckt.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { return {int(ip_error::socket_connect_failed), ip_error_category::instance()}; @@ -100,7 +100,7 @@ namespace glz sockaddr_in server_addr; server_addr.sin_family = AF_INET; - server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_addr.s_addr = INADDR_ANY; // TODO: Make this support a specific address server_addr.sin_port = htons(uint16_t(port)); if (::bind(sckt.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { From f034e26cb9c16f220923cb28bccffce1e2c47173 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 12:31:32 -0500 Subject: [PATCH 247/309] client.hpp updates --- include/glaze/coroutine/concepts.hpp | 16 ++++++++++ include/glaze/network.hpp | 7 +++++ include/glaze/network/client.hpp | 35 +++++++++++---------- include/glaze/network/ip.hpp | 41 ++++++++++++++++++++++--- tests/coroutine_test/coroutine_test.cpp | 2 +- 5 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 include/glaze/network.hpp diff --git a/include/glaze/coroutine/concepts.hpp b/include/glaze/coroutine/concepts.hpp index 58ff0c10ad..12721a83d8 100644 --- a/include/glaze/coroutine/concepts.hpp +++ b/include/glaze/coroutine/concepts.hpp @@ -44,4 +44,20 @@ namespace glz t.poll(fd, op, timeout) } -> std::same_as>; };*/ + + template + concept const_buffer = requires(const T t) + { + { t.empty() } -> std::same_as; + { t.data() } -> std::same_as; + { t.size() } -> std::same_as; + }; + + template + concept mutable_buffer = requires(T t) + { + { t.empty() } -> std::same_as; + { t.data() } -> std::same_as; + { t.size() } -> std::same_as; + }; } diff --git a/include/glaze/network.hpp b/include/glaze/network.hpp new file mode 100644 index 0000000000..f41bea6605 --- /dev/null +++ b/include/glaze/network.hpp @@ -0,0 +1,7 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +#include "glaze/network/client.hpp" +#include "glaze/network/socket.hpp" diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index d41ed53985..a1f3d8a8ba 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -4,6 +4,7 @@ #pragma once #include "glaze/coroutine/io_scheduler.hpp" +#include "glaze/network/ip.hpp" #include "glaze/network/socket.hpp" namespace glz @@ -21,7 +22,7 @@ namespace glz ip_version ipv{}; glz::socket socket{}; /// Cache the status of the connect in the event the user calls connect() again. - std::optional m_connect_status{}; + std::optional connect_status{}; /** * Connects to the address+port with the given timeout. Once connected calling this function @@ -29,17 +30,17 @@ namespace glz * @param timeout How long to wait for the connection to establish? Timeout of zero is indefinite. * @return The result status of trying to connect. */ - coro::task connect(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) + task connect(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { // Only allow the user to connect per tcp client once, if they need to re-connect they should // make a new tcp::client. - if (m_connect_status.has_value()) { - co_return m_connect_status.value(); + if (connect_status.has_value()) { + co_return connect_status.value(); } // This enforces the connection status is aways set on the client object upon returning. - auto return_value = [this](connect_status s) -> connect_status { - m_connect_status = s; + auto return_value = [this](glz::connect_status s) -> glz::connect_status { + connect_status = s; return s; }; @@ -56,11 +57,11 @@ namespace glz // If the connect is happening in the background poll for write on the socket to trigger // when the connection is established. if (errno == EAGAIN || errno == EINPROGRESS) { - auto pstatus = co_await io_scheduler->poll(m_socket, poll_op::write, timeout); + auto pstatus = co_await scheduler->poll(socket.socket_fd, poll_op::write, timeout); if (pstatus == poll_status::event) { int result{0}; socklen_t result_length{sizeof(result)}; - if (getsockopt(m_socket.native_handle(), SOL_SOCKET, SO_ERROR, &result, &result_length) < 0) { + if (getsockopt(socket.socket_fd, SOL_SOCKET, SO_ERROR, &result, &result_length) < 0) { std::cerr << "connect failed to getsockopt after write poll event\n"; } @@ -85,10 +86,9 @@ namespace glz * @return The status result of th poll operation. When poll_status::event is returned then the * event operation is ready. */ - auto poll(coro::poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) - -> coro::task + task poll(glz::poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { - return m_io_scheduler->poll(m_socket, op, timeout); + return scheduler->poll(socket.socket_fd, op, timeout); } /** @@ -98,15 +98,15 @@ namespace glz * @return The status of the recv call and a span of the bytes recevied (if any). The span of * bytes will be a subspan or full span of the given input buffer. */ - template - auto recv(buffer_type&& buffer) -> std::pair> + template + auto recv(Buffer&& buffer) -> std::pair> { // If the user requested zero bytes, just return. if (buffer.empty()) { return {recv_status::ok, std::span{}}; } - auto bytes_recv = ::recv(m_socket.native_handle(), buffer.data(), buffer.size(), 0); + auto bytes_recv = ::recv(socket.socket_fd, buffer.data(), buffer.size(), 0); if (bytes_recv > 0) { // Ok, we've recieved some data. return {recv_status::ok, std::span{buffer.data(), static_cast(bytes_recv)}}; @@ -130,21 +130,22 @@ namespace glz * @return The status of the send call and a span of any remaining bytes not sent. If all bytes * were successfully sent the status will be 'ok' and the remaining span will be empty. */ - template - auto send(const buffer_type& buffer) -> std::pair> + template + auto send(const Buffer& buffer) -> std::pair> { // If the user requested zero bytes, just return. if (buffer.empty()) { return {send_status::ok, std::span{buffer.data(), buffer.size()}}; } - auto bytes_sent = ::send(m_socket.native_handle(), buffer.data(), buffer.size(), 0); + auto bytes_sent = ::send(socket.socket_fd, buffer.data(), buffer.size(), 0); if (bytes_sent >= 0) { // Some or all of the bytes were written. return {send_status::ok, std::span{buffer.data() + bytes_sent, buffer.size() - bytes_sent}}; } else { // Due to the error none of the bytes were written. + // TODO: add errno conversion return {static_cast(errno), std::span{buffer.data(), buffer.size()}}; } } diff --git a/include/glaze/network/ip.hpp b/include/glaze/network/ip.hpp index 95616cae6b..53f63f95da 100644 --- a/include/glaze/network/ip.hpp +++ b/include/glaze/network/ip.hpp @@ -21,8 +21,10 @@ namespace glz { enum struct ip_version : int { ipv4 = AF_INET, ipv6 = AF_INET6 }; - - inline std::optional binary_to_ip_string(const std::string_view binary_address, ip_version ipv = ip_version::ipv6) { + + inline std::optional binary_to_ip_string(const std::string_view binary_address, + ip_version ipv = ip_version::ipv6) + { std::string output{}; output.resize(16); @@ -33,15 +35,21 @@ namespace glz } return {}; } - + GLZ_ENUM(connect_status, connected, invalid_ip_address, timeout, error); - + GLZ_ENUM(recv_status, ok, closed, udp_not_bound, try_again, // would_block, bad_file_descriptor, connection_refused, // memory_fault, interrupted, invalid_argument, no_memory, // not_connected, not_a_socket); - + + GLZ_ENUM(send_status, ok, closed, permission_denied, try_again, would_block, already_in_progress, + bad_file_descriptor, connection_reset, no_peer_address, memory_fault, interrupted, is_connection, + message_size, output_queue_full, no_memory, not_connected, not_a_socket, operationg_not_supported, + pipe_closed); + /* + // recv_status ok = 0, /// The peer closed the socket. closed = -1, @@ -59,4 +67,27 @@ namespace glz not_connected = ENOTCONN, not_a_socket = ENOTSOCK, */ + + /* + // send_status + ok = 0, + closed = -1, + permission_denied = EACCES, + try_again = EAGAIN, + would_block = EWOULDBLOCK, + already_in_progress = EALREADY, + bad_file_descriptor = EBADF, + connection_reset = ECONNRESET, + no_peer_address = EDESTADDRREQ, + memory_fault = EFAULT, + interrupted = EINTR, + is_connection = EISCONN, + message_size = EMSGSIZE, + output_queue_full = ENOBUFS, + no_memory = ENOMEM, + not_connected = ENOTCONN, + not_a_socket = ENOTSOCK, + operationg_not_supported = EOPNOTSUPP, + pipe_closed = EPIPE, + */ } diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 1d8c473ecb..a0b1c0800d 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -4,7 +4,7 @@ #define UT_RUN_TIME_ONLY #include "glaze/coroutine.hpp" -#include "glaze/network/socket.hpp" +#include "glaze/network.hpp" #include "ut/ut.hpp" From 9d194f52863d2107bfd3b47cb2b770b48e4a0e69 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 12:48:59 -0500 Subject: [PATCH 248/309] server.hpp updates --- include/glaze/coroutine/delete.hpp | 5 - include/glaze/network.hpp | 1 + include/glaze/network/client.hpp | 2 +- include/glaze/network/server.hpp | 234 +++++---------------------- include/glaze/network/server_old.hpp | 215 ++++++++++++++++++++++++ 5 files changed, 253 insertions(+), 204 deletions(-) create mode 100644 include/glaze/network/server_old.hpp diff --git a/include/glaze/coroutine/delete.hpp b/include/glaze/coroutine/delete.hpp index c319309bf6..9f1e331307 100644 --- a/include/glaze/coroutine/delete.hpp +++ b/include/glaze/coroutine/delete.hpp @@ -1,9 +1,4 @@ // Glaze Library // For the license information refer to glaze.hpp -// Modified from the awesome: https://github.com/jbaldwin/libcoro - #pragma once - -#include "glaze/csv/read.hpp" -#include "glaze/csv/write.hpp" diff --git a/include/glaze/network.hpp b/include/glaze/network.hpp index f41bea6605..b44a975b11 100644 --- a/include/glaze/network.hpp +++ b/include/glaze/network.hpp @@ -4,4 +4,5 @@ #pragma once #include "glaze/network/client.hpp" +#include "glaze/network/server.hpp" #include "glaze/network/socket.hpp" diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index a1f3d8a8ba..d5b9d80945 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -18,8 +18,8 @@ namespace glz { std::string address{"127.0.0.1"}; uint16_t port{8080}; - std::shared_ptr scheduler{}; ip_version ipv{}; + std::shared_ptr scheduler{}; glz::socket socket{}; /// Cache the status of the connect in the event the user calls connect() again. std::optional connect_status{}; diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index 85afae85e6..75bb6b4eb3 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -3,213 +3,51 @@ #pragma once +#include "glaze/coroutine/task.hpp" +#include "glaze/network/client.hpp" +#include "glaze/network/ip.hpp" #include "glaze/network/socket.hpp" -#ifdef _WIN32 -#define GLZ_CLOSE_SOCKET closesocket -#define GLZ_EVENT_CLOSE WSACloseEvent -#define GLZ_EWOULDBLOCK WSAEWOULDBLOCK -#define GLZ_INVALID_EVENT WSA_INVALID_EVENT -#define GLZ_INVALID_SOCKET INVALID_SOCKET -#define GLZ_SOCKET SOCKET -#define GLZ_SOCKET_ERROR SOCKET_ERROR -#define GLZ_SOCKET_ERROR_CODE WSAGetLastError() -#define GLZ_WAIT_FAILED WSA_WAIT_FAILED -#define GLZ_WAIT_RESULT_TYPE DWORD -#else -#include -#include -#if __has_include() -#include -#endif -#include -#include -#include -#include - -#include -#define GLZ_CLOSE_SOCKET ::close -#define GLZ_EVENT_CLOSE ::close -#define GLZ_EWOULDBLOCK EWOULDBLOCK -#define GLZ_INVALID_EVENT (-1) -#define GLZ_INVALID_SOCKET (-1) -#define GLZ_SOCKET int -#define GLZ_SOCKET_ERROR (-1) -#define GLZ_SOCKET_ERROR_CODE errno -#define GLZ_WAIT_FAILED (-1) -#define GLZ_WAIT_RESULT_TYPE int -#endif - -#if defined(__APPLE__) -#include // for kqueue on macOS -#elif defined(__linux__) -#include // for epoll on Linux -#endif - namespace glz { - namespace detail + struct server { - inline void server_thread_cleanup(auto& threads) + std::string address{"0.0.0.0"}; + uint16_t port{8080}; + int32_t backlog{128}; // The kernel backlog of connections to buffer. + std::shared_ptr scheduler{}; + /// The socket for accepting new tcp connections on. + socket accept_socket{}; + + /** + * Polls for new incoming tcp connections. + * @param timeout How long to wait for a new connection before timing out, zero waits indefinitely. + * @return The result of the poll, 'event' means the poll was successful and there is at least 1 + * connection ready to be accepted. + */ + task poll(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { - threads.erase(std::partition(threads.begin(), threads.end(), - [](auto& future) { - if (auto status = future.wait_for(std::chrono::milliseconds(0)); - status == std::future_status::ready) { - return false; - } - return true; - }), - threads.end()); + return scheduler->poll(accept_socket.socket_fd, poll_op::read, timeout); } - } - struct server final - { - int port{}; - std::atomic active = true; - std::shared_future async_accept_thread{}; - std::vector> threads{}; - - ~server() { active = false; } - - template - std::shared_future async_accept(AcceptCallback&& callback) + /** + * Accepts an incoming tcp client connection. On failure the tls clients socket will be set to + * and invalid state, use the socket.is_value() to verify the client was correctly accepted. + * @return The newly connected tcp client connection. + */ + client accept() { - async_accept_thread = { - std::async([this, callback = std::forward(callback)] { return accept(callback); })}; - return async_accept_thread; - } - - template - [[nodiscard]] std::error_code accept(AcceptCallback&& callback) - { - glz::socket accept_socket{}; - - const auto ec = bind_and_listen(accept_socket, port); - if (ec) { - return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; - } - -#if defined(__APPLE__) - int event_fd = ::kqueue(); -#elif defined(__linux__) - int event_fd = ::epoll_create1(0); -#elif defined(_WIN32) - HANDLE event_fd = WSACreateEvent(); -#endif - - if (event_fd == GLZ_INVALID_EVENT) { - return {int(ip_error::queue_create_failed), ip_error_category::instance()}; - } - - bool event_setup_failed = false; -#if defined(__APPLE__) - struct kevent change; - EV_SET(&change, accept_socket.socket_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, nullptr); - event_setup_failed = ::kevent(event_fd, &change, 1, nullptr, 0, nullptr) == -1; -#elif defined(__linux__) - struct epoll_event ev; - ev.events = EPOLLIN; - ev.data.fd = accept_socket.socket_fd; - event_setup_failed = epoll_ctl(event_fd, EPOLL_CTL_ADD, accept_socket.socket_fd, &ev) == -1; -#elif defined(_WIN32) - event_setup_failed = WSAEventSelect(accept_socket.socket_fd, event_fd, FD_ACCEPT) == GLZ_SOCKET_ERROR; -#endif - - if (event_setup_failed) { - GLZ_EVENT_CLOSE(event_fd); - return {int(ip_error::event_ctl_failed), ip_error_category::instance()}; - } - -#if defined(__APPLE__) - std::vector events(16); -#elif defined(__linux__) - std::vector epoll_events(16); -#endif - - while (active) { - GLZ_WAIT_RESULT_TYPE n{}; - -#if defined(__APPLE__) - struct timespec timeout - { - 0, 10000000 - }; // 10ms - n = ::kevent(event_fd, nullptr, 0, events.data(), static_cast(events.size()), &timeout); -#elif defined(__linux__) - n = ::epoll_wait(event_fd, epoll_events.data(), static_cast(epoll_events.size()), 10); -#elif defined(_WIN32) - n = WSAWaitForMultipleEvents(1, &event_fd, FALSE, 10, FALSE); -#endif - - if (n == GLZ_WAIT_FAILED) { -#if defined(__APPLE__) || defined(__linux__) - if (errno == EINTR) continue; -#else - if (n == WSA_WAIT_TIMEOUT) continue; -#endif - GLZ_EVENT_CLOSE(event_fd); - return {int(ip_error::event_wait_failed), ip_error_category::instance()}; - } - - auto spawn_socket = [&] { - sockaddr_in client_addr; - socklen_t client_len = sizeof(client_addr); - auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); - if (client_fd != GLZ_INVALID_SOCKET) { - threads.emplace_back( - std::async([this, callback, client_fd] { callback(socket{client_fd}, active); })); - } - }; - -#if defined(__APPLE__) || defined(__linux__) - for (int i = 0; i < n; ++i) { -#if defined(__APPLE__) - if (events[i].ident == uintptr_t(accept_socket.socket_fd) && events[i].filter == EVFILT_READ) { -#elif defined(__linux__) - if (epoll_events[i].data.fd == accept_socket.socket_fd && epoll_events[i].events & EPOLLIN) { -#endif - spawn_socket(); - } - } - -#else // Windows - WSANETWORKEVENTS events; - if (WSAEnumNetworkEvents(accept_socket.socket_fd, event_fd, &events) == GLZ_SOCKET_ERROR) { - - WSACloseEvent(event_fd); - - // requires explicit 'std::error_code'...otherwise the following error with msvc... - // - // error C2440: 'return': cannot convert from 'initializer list' to 'std::error_code' - // - return {int(ip_error::event_enum_failed), ip_error_category::instance()}; - } - - if (events.lNetworkEvents & FD_ACCEPT) { - if (events.iErrorCode[FD_ACCEPT_BIT] == 0) { - spawn_socket(); - } - } -#endif - - detail::server_thread_cleanup(threads); - } - - GLZ_EVENT_CLOSE(event_fd); - return {}; + sockaddr_in client{}; + constexpr int len = sizeof(struct sockaddr_in); + socket s{::accept(accept_socket.socket_fd, (struct sockaddr*)(&client), + const_cast((const socklen_t*)(&len)))}; + + std::string_view ip_addr_view{ + (char*)(&client.sin_addr.s_addr), + sizeof(client.sin_addr.s_addr), + }; + + return {binary_to_ip_string(ip_addr_view).value(), ntohs(client.sin_port), ip_version(client.sin_family), scheduler}; } }; } - -#undef GLZ_CLOSE_SOCKET -#undef GLZ_EVENT_CLOSE -#undef GLZ_EWOULDBLOCK -#undef GLZ_INVALID_EVENT -#undef GLZ_INVALID_SOCKET -#undef GLZ_SOCKET -#undef GLZ_SOCKET_ERROR -#undef GLZ_SOCKET_ERROR_CODE -#undef GLZ_WAIT_FAILED -#undef GLZ_WAIT_RESULT_TYPE diff --git a/include/glaze/network/server_old.hpp b/include/glaze/network/server_old.hpp new file mode 100644 index 0000000000..85afae85e6 --- /dev/null +++ b/include/glaze/network/server_old.hpp @@ -0,0 +1,215 @@ +// Glaze Library +// For the license information refer to glaze.hpp + +#pragma once + +#include "glaze/network/socket.hpp" + +#ifdef _WIN32 +#define GLZ_CLOSE_SOCKET closesocket +#define GLZ_EVENT_CLOSE WSACloseEvent +#define GLZ_EWOULDBLOCK WSAEWOULDBLOCK +#define GLZ_INVALID_EVENT WSA_INVALID_EVENT +#define GLZ_INVALID_SOCKET INVALID_SOCKET +#define GLZ_SOCKET SOCKET +#define GLZ_SOCKET_ERROR SOCKET_ERROR +#define GLZ_SOCKET_ERROR_CODE WSAGetLastError() +#define GLZ_WAIT_FAILED WSA_WAIT_FAILED +#define GLZ_WAIT_RESULT_TYPE DWORD +#else +#include +#include +#if __has_include() +#include +#endif +#include +#include +#include +#include + +#include +#define GLZ_CLOSE_SOCKET ::close +#define GLZ_EVENT_CLOSE ::close +#define GLZ_EWOULDBLOCK EWOULDBLOCK +#define GLZ_INVALID_EVENT (-1) +#define GLZ_INVALID_SOCKET (-1) +#define GLZ_SOCKET int +#define GLZ_SOCKET_ERROR (-1) +#define GLZ_SOCKET_ERROR_CODE errno +#define GLZ_WAIT_FAILED (-1) +#define GLZ_WAIT_RESULT_TYPE int +#endif + +#if defined(__APPLE__) +#include // for kqueue on macOS +#elif defined(__linux__) +#include // for epoll on Linux +#endif + +namespace glz +{ + namespace detail + { + inline void server_thread_cleanup(auto& threads) + { + threads.erase(std::partition(threads.begin(), threads.end(), + [](auto& future) { + if (auto status = future.wait_for(std::chrono::milliseconds(0)); + status == std::future_status::ready) { + return false; + } + return true; + }), + threads.end()); + } + } + + struct server final + { + int port{}; + std::atomic active = true; + std::shared_future async_accept_thread{}; + std::vector> threads{}; + + ~server() { active = false; } + + template + std::shared_future async_accept(AcceptCallback&& callback) + { + async_accept_thread = { + std::async([this, callback = std::forward(callback)] { return accept(callback); })}; + return async_accept_thread; + } + + template + [[nodiscard]] std::error_code accept(AcceptCallback&& callback) + { + glz::socket accept_socket{}; + + const auto ec = bind_and_listen(accept_socket, port); + if (ec) { + return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; + } + +#if defined(__APPLE__) + int event_fd = ::kqueue(); +#elif defined(__linux__) + int event_fd = ::epoll_create1(0); +#elif defined(_WIN32) + HANDLE event_fd = WSACreateEvent(); +#endif + + if (event_fd == GLZ_INVALID_EVENT) { + return {int(ip_error::queue_create_failed), ip_error_category::instance()}; + } + + bool event_setup_failed = false; +#if defined(__APPLE__) + struct kevent change; + EV_SET(&change, accept_socket.socket_fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, nullptr); + event_setup_failed = ::kevent(event_fd, &change, 1, nullptr, 0, nullptr) == -1; +#elif defined(__linux__) + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = accept_socket.socket_fd; + event_setup_failed = epoll_ctl(event_fd, EPOLL_CTL_ADD, accept_socket.socket_fd, &ev) == -1; +#elif defined(_WIN32) + event_setup_failed = WSAEventSelect(accept_socket.socket_fd, event_fd, FD_ACCEPT) == GLZ_SOCKET_ERROR; +#endif + + if (event_setup_failed) { + GLZ_EVENT_CLOSE(event_fd); + return {int(ip_error::event_ctl_failed), ip_error_category::instance()}; + } + +#if defined(__APPLE__) + std::vector events(16); +#elif defined(__linux__) + std::vector epoll_events(16); +#endif + + while (active) { + GLZ_WAIT_RESULT_TYPE n{}; + +#if defined(__APPLE__) + struct timespec timeout + { + 0, 10000000 + }; // 10ms + n = ::kevent(event_fd, nullptr, 0, events.data(), static_cast(events.size()), &timeout); +#elif defined(__linux__) + n = ::epoll_wait(event_fd, epoll_events.data(), static_cast(epoll_events.size()), 10); +#elif defined(_WIN32) + n = WSAWaitForMultipleEvents(1, &event_fd, FALSE, 10, FALSE); +#endif + + if (n == GLZ_WAIT_FAILED) { +#if defined(__APPLE__) || defined(__linux__) + if (errno == EINTR) continue; +#else + if (n == WSA_WAIT_TIMEOUT) continue; +#endif + GLZ_EVENT_CLOSE(event_fd); + return {int(ip_error::event_wait_failed), ip_error_category::instance()}; + } + + auto spawn_socket = [&] { + sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + auto client_fd = ::accept(accept_socket.socket_fd, (sockaddr*)&client_addr, &client_len); + if (client_fd != GLZ_INVALID_SOCKET) { + threads.emplace_back( + std::async([this, callback, client_fd] { callback(socket{client_fd}, active); })); + } + }; + +#if defined(__APPLE__) || defined(__linux__) + for (int i = 0; i < n; ++i) { +#if defined(__APPLE__) + if (events[i].ident == uintptr_t(accept_socket.socket_fd) && events[i].filter == EVFILT_READ) { +#elif defined(__linux__) + if (epoll_events[i].data.fd == accept_socket.socket_fd && epoll_events[i].events & EPOLLIN) { +#endif + spawn_socket(); + } + } + +#else // Windows + WSANETWORKEVENTS events; + if (WSAEnumNetworkEvents(accept_socket.socket_fd, event_fd, &events) == GLZ_SOCKET_ERROR) { + + WSACloseEvent(event_fd); + + // requires explicit 'std::error_code'...otherwise the following error with msvc... + // + // error C2440: 'return': cannot convert from 'initializer list' to 'std::error_code' + // + return {int(ip_error::event_enum_failed), ip_error_category::instance()}; + } + + if (events.lNetworkEvents & FD_ACCEPT) { + if (events.iErrorCode[FD_ACCEPT_BIT] == 0) { + spawn_socket(); + } + } +#endif + + detail::server_thread_cleanup(threads); + } + + GLZ_EVENT_CLOSE(event_fd); + return {}; + } + }; +} + +#undef GLZ_CLOSE_SOCKET +#undef GLZ_EVENT_CLOSE +#undef GLZ_EWOULDBLOCK +#undef GLZ_INVALID_EVENT +#undef GLZ_INVALID_SOCKET +#undef GLZ_SOCKET +#undef GLZ_SOCKET_ERROR +#undef GLZ_SOCKET_ERROR_CODE +#undef GLZ_WAIT_FAILED +#undef GLZ_WAIT_RESULT_TYPE From 1320acfb737e8d4d4faa9864c501f1dce5ce6a8a Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 12:52:57 -0500 Subject: [PATCH 249/309] coroutine network updates --- include/glaze/network/client.hpp | 2 +- include/glaze/network/server.hpp | 4 ++-- include/glaze/network/socket.hpp | 4 ++++ tests/coroutine_test/coroutine_test.cpp | 14 +++++++------- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index d5b9d80945..9202d75c96 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -16,10 +16,10 @@ namespace glz */ struct client { + std::shared_ptr scheduler{}; std::string address{"127.0.0.1"}; uint16_t port{8080}; ip_version ipv{}; - std::shared_ptr scheduler{}; glz::socket socket{}; /// Cache the status of the connect in the event the user calls connect() again. std::optional connect_status{}; diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index 75bb6b4eb3..ba715e98d6 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -12,10 +12,10 @@ namespace glz { struct server { + std::shared_ptr scheduler{}; std::string address{"0.0.0.0"}; uint16_t port{8080}; int32_t backlog{128}; // The kernel backlog of connections to buffer. - std::shared_ptr scheduler{}; /// The socket for accepting new tcp connections on. socket accept_socket{}; @@ -47,7 +47,7 @@ namespace glz sizeof(client.sin_addr.s_addr), }; - return {binary_to_ip_string(ip_addr_view).value(), ntohs(client.sin_port), ip_version(client.sin_family), scheduler}; + return {scheduler, binary_to_ip_string(ip_addr_view).value(), ntohs(client.sin_port), ip_version(client.sin_family)}; } }; } diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 90a9ebf114..77b558e383 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -59,6 +59,10 @@ namespace glz net::close_socket(socket_fd); } } + + bool valid() const { + return socket_fd != net::invalid_socket; + } ~socket() { close(); } diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index a0b1c0800d..4a319cf874 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -367,7 +367,7 @@ suite ring_buffer_test = [] { glz::sync_wait(glz::when_all(std::move(tasks))); }; -/*suite io_scheduler_test = [] { +suite io_scheduler_test = [] { auto scheduler = std::make_shared(glz::io_scheduler::options{ // The scheduler will spawn a dedicated event processing thread. This is the default, but // it is possible to use 'manual' and call 'process_events()' to drive the scheduler yourself. @@ -396,7 +396,7 @@ suite ring_buffer_test = [] { // it is immediately available for the client to connect since this will create a socket, // bind the socket and start listening on that socket. See tcp::server for more details on // how to specify the local address and port to bind to as well as enabling SSL/TLS. - glz::net::tcp::server server{scheduler}; + glz::server server{scheduler}; // Now scheduler this task onto the scheduler. co_await scheduler->schedule(); @@ -411,7 +411,7 @@ suite ring_buffer_test = [] { auto client = server.accept(); // Verify the incoming connection was accepted correctly. - if (!client.socket().is_valid()) { + if (!client.socket.valid()) { co_return; // Handle error. } @@ -427,7 +427,7 @@ suite ring_buffer_test = [] { // can be used to resize the buffer or work with the bytes without modifying the buffer at all. std::string request(256, '\0'); auto [recv_status, recv_bytes] = client.recv(request); - if (recv_status != glz::net::recv_status::ok) { + if (recv_status != glz::recv_status::ok) { co_return; // Handle error, see net::recv_status for detailed error states. } @@ -449,7 +449,7 @@ suite ring_buffer_test = [] { do { // Optimistically send() prior to polling. auto [send_status, r] = client.send(remaining); - if (send_status != glz::net::send_status::ok) { + if (send_status != glz::send_status::ok) { co_return; // Handle error, see net::send_status for detailed error states. } @@ -475,7 +475,7 @@ suite ring_buffer_test = [] { // Create the tcp::client with the default settings, see tcp::client for how to set the // ip address, port, and optionally enabling SSL/TLS. - glz::net::tcp::client client{scheduler}; + glz::client client{scheduler}; // Ommitting error checking code for the client, each step should check the status and // verify the number of bytes sent or received. @@ -501,7 +501,7 @@ suite ring_buffer_test = [] { // Create and wait for the server and client tasks to complete. glz::sync_wait(glz::when_all(make_server_task(), make_client_task())); -};*/ +}; int main() { From b2f74832df678f4438e5b7c09c452a83258c177d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 12:56:45 -0500 Subject: [PATCH 250/309] updates --- include/glaze/network/client.hpp | 11 ++++++----- include/glaze/network/ip.hpp | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 9202d75c96..6981ef6313 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -22,7 +22,7 @@ namespace glz ip_version ipv{}; glz::socket socket{}; /// Cache the status of the connect in the event the user calls connect() again. - std::optional connect_status{}; + glz::connect_status connect_status{}; /** * Connects to the address+port with the given timeout. Once connected calling this function @@ -34,8 +34,8 @@ namespace glz { // Only allow the user to connect per tcp client once, if they need to re-connect they should // make a new tcp::client. - if (connect_status.has_value()) { - co_return connect_status.value(); + if (connect_status != glz::connect_status::unset) { + co_return connect_status; } // This enforces the connection status is aways set on the client object upon returning. @@ -99,7 +99,7 @@ namespace glz * bytes will be a subspan or full span of the given input buffer. */ template - auto recv(Buffer&& buffer) -> std::pair> + std::pair> recv(Buffer&& buffer) { // If the user requested zero bytes, just return. if (buffer.empty()) { @@ -117,6 +117,7 @@ namespace glz } else { // Report the error to the user. + // TODO: add errno conversion return {static_cast(errno), std::span{}}; } } @@ -131,7 +132,7 @@ namespace glz * were successfully sent the status will be 'ok' and the remaining span will be empty. */ template - auto send(const Buffer& buffer) -> std::pair> + std::pair> send(const Buffer& buffer) { // If the user requested zero bytes, just return. if (buffer.empty()) { diff --git a/include/glaze/network/ip.hpp b/include/glaze/network/ip.hpp index 53f63f95da..3400f36589 100644 --- a/include/glaze/network/ip.hpp +++ b/include/glaze/network/ip.hpp @@ -36,7 +36,7 @@ namespace glz return {}; } - GLZ_ENUM(connect_status, connected, invalid_ip_address, timeout, error); + GLZ_ENUM(connect_status, unset, connected, invalid_ip_address, timeout, error); GLZ_ENUM(recv_status, ok, closed, udp_not_bound, try_again, // would_block, bad_file_descriptor, connection_refused, // From e29dd86da13fbedc2b2172842ed99ca45ed338a8 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:02:46 -0500 Subject: [PATCH 251/309] ip_status --- include/glaze/network/client.hpp | 34 ++++++++++++------------- include/glaze/network/ip.hpp | 30 ++++++++++++---------- tests/coroutine_test/coroutine_test.cpp | 14 +++++----- 3 files changed, 40 insertions(+), 38 deletions(-) diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 6981ef6313..929afcbf0f 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -22,7 +22,7 @@ namespace glz ip_version ipv{}; glz::socket socket{}; /// Cache the status of the connect in the event the user calls connect() again. - glz::connect_status connect_status{}; + glz::ip_status connect_status{}; /** * Connects to the address+port with the given timeout. Once connected calling this function @@ -30,16 +30,16 @@ namespace glz * @param timeout How long to wait for the connection to establish? Timeout of zero is indefinite. * @return The result status of trying to connect. */ - task connect(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) + task connect(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { // Only allow the user to connect per tcp client once, if they need to re-connect they should // make a new tcp::client. - if (connect_status != glz::connect_status::unset) { + if (connect_status != glz::ip_status::unset) { co_return connect_status; } // This enforces the connection status is aways set on the client object upon returning. - auto return_value = [this](glz::connect_status s) -> glz::connect_status { + auto return_value = [this](glz::ip_status s) -> glz::ip_status { connect_status = s; return s; }; @@ -51,7 +51,7 @@ namespace glz auto result = ::connect(socket.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)); if (result == 0) { - co_return return_value(connect_status::connected); + co_return return_value(ip_status::connected); } else if (result == -1) { // If the connect is happening in the background poll for write on the socket to trigger @@ -66,16 +66,16 @@ namespace glz } if (result == 0) { - co_return return_value(connect_status::connected); + co_return return_value(ip_status::connected); } } else if (pstatus == poll_status::timeout) { - co_return return_value(connect_status::timeout); + co_return return_value(ip_status::timeout); } } } - co_return return_value(connect_status::error); + co_return return_value(ip_status::error); } /** @@ -99,26 +99,26 @@ namespace glz * bytes will be a subspan or full span of the given input buffer. */ template - std::pair> recv(Buffer&& buffer) + std::pair> recv(Buffer&& buffer) { // If the user requested zero bytes, just return. if (buffer.empty()) { - return {recv_status::ok, std::span{}}; + return {ip_status::ok, std::span{}}; } auto bytes_recv = ::recv(socket.socket_fd, buffer.data(), buffer.size(), 0); if (bytes_recv > 0) { // Ok, we've recieved some data. - return {recv_status::ok, std::span{buffer.data(), static_cast(bytes_recv)}}; + return {ip_status::ok, std::span{buffer.data(), static_cast(bytes_recv)}}; } else if (bytes_recv == 0) { // On TCP stream sockets 0 indicates the connection has been closed by the peer. - return {recv_status::closed, std::span{}}; + return {ip_status::closed, std::span{}}; } else { // Report the error to the user. // TODO: add errno conversion - return {static_cast(errno), std::span{}}; + return {static_cast(errno), std::span{}}; } } @@ -132,22 +132,22 @@ namespace glz * were successfully sent the status will be 'ok' and the remaining span will be empty. */ template - std::pair> send(const Buffer& buffer) + std::pair> send(const Buffer& buffer) { // If the user requested zero bytes, just return. if (buffer.empty()) { - return {send_status::ok, std::span{buffer.data(), buffer.size()}}; + return {ip_status::ok, std::span{buffer.data(), buffer.size()}}; } auto bytes_sent = ::send(socket.socket_fd, buffer.data(), buffer.size(), 0); if (bytes_sent >= 0) { // Some or all of the bytes were written. - return {send_status::ok, std::span{buffer.data() + bytes_sent, buffer.size() - bytes_sent}}; + return {ip_status::ok, std::span{buffer.data() + bytes_sent, buffer.size() - bytes_sent}}; } else { // Due to the error none of the bytes were written. // TODO: add errno conversion - return {static_cast(errno), std::span{buffer.data(), buffer.size()}}; + return {static_cast(errno), std::span{buffer.data(), buffer.size()}}; } } }; diff --git a/include/glaze/network/ip.hpp b/include/glaze/network/ip.hpp index 3400f36589..385fcfd5aa 100644 --- a/include/glaze/network/ip.hpp +++ b/include/glaze/network/ip.hpp @@ -35,21 +35,23 @@ namespace glz } return {}; } - - GLZ_ENUM(connect_status, unset, connected, invalid_ip_address, timeout, error); - - GLZ_ENUM(recv_status, ok, closed, udp_not_bound, try_again, // - would_block, bad_file_descriptor, connection_refused, // - memory_fault, interrupted, invalid_argument, no_memory, // - not_connected, not_a_socket); - - GLZ_ENUM(send_status, ok, closed, permission_denied, try_again, would_block, already_in_progress, - bad_file_descriptor, connection_reset, no_peer_address, memory_fault, interrupted, is_connection, - message_size, output_queue_full, no_memory, not_connected, not_a_socket, operationg_not_supported, - pipe_closed); + + GLZ_ENUM(ip_status, // + unset, // + ok, // + closed, // + connected, // + invalid_ip_address, // + timeout, // + error, // + try_again, // + would_block, // + bad_file_descriptor, // + connection_refused, // + ); /* - // recv_status + // ip_status ok = 0, /// The peer closed the socket. closed = -1, @@ -69,7 +71,7 @@ namespace glz */ /* - // send_status + // ip_status ok = 0, closed = -1, permission_denied = EACCES, diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 4a319cf874..196773a92f 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -426,9 +426,9 @@ suite io_scheduler_test = [] { // as well as a span that overlaps the given buffer for the bytes that were read. This // can be used to resize the buffer or work with the bytes without modifying the buffer at all. std::string request(256, '\0'); - auto [recv_status, recv_bytes] = client.recv(request); - if (recv_status != glz::recv_status::ok) { - co_return; // Handle error, see net::recv_status for detailed error states. + auto [ip_status, recv_bytes] = client.recv(request); + if (ip_status != glz::ip_status::ok) { + co_return; // Handle error, see net::ip_status for detailed error states. } request.resize(recv_bytes.size()); @@ -448,9 +448,9 @@ suite io_scheduler_test = [] { std::span remaining = response; do { // Optimistically send() prior to polling. - auto [send_status, r] = client.send(remaining); - if (send_status != glz::send_status::ok) { - co_return; // Handle error, see net::send_status for detailed error states. + auto [ip_status, r] = client.send(remaining); + if (ip_status != glz::ip_status::ok) { + co_return; // Handle error, see net::ip_status for detailed error states. } if (r.empty()) { @@ -492,7 +492,7 @@ suite io_scheduler_test = [] { // Wait for the response and receive it. co_await client.poll(glz::poll_op::read); std::string response(256, '\0'); - auto [recv_status, recv_bytes] = client.recv(response); + auto [ip_status, recv_bytes] = client.recv(response); response.resize(recv_bytes.size()); std::cout << "client: " << response << "\n"; From 53a032fb8591341f7bed3b7b657812137f5290dc Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:11:19 -0500 Subject: [PATCH 252/309] updates --- include/glaze/network/client.hpp | 10 +++++----- include/glaze/network/ip.hpp | 29 +++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 929afcbf0f..8b01db1e74 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -118,7 +118,7 @@ namespace glz else { // Report the error to the user. // TODO: add errno conversion - return {static_cast(errno), std::span{}}; + return {errno_to_ip_status(), std::span{}}; } } @@ -132,22 +132,22 @@ namespace glz * were successfully sent the status will be 'ok' and the remaining span will be empty. */ template - std::pair> send(const Buffer& buffer) + std::pair send(const Buffer& buffer) { // If the user requested zero bytes, just return. if (buffer.empty()) { - return {ip_status::ok, std::span{buffer.data(), buffer.size()}}; + return {ip_status::ok, std::string_view{buffer.data(), buffer.size()}}; } auto bytes_sent = ::send(socket.socket_fd, buffer.data(), buffer.size(), 0); if (bytes_sent >= 0) { // Some or all of the bytes were written. - return {ip_status::ok, std::span{buffer.data() + bytes_sent, buffer.size() - bytes_sent}}; + return {ip_status::ok, std::string_view{buffer.data() + bytes_sent, buffer.size() - bytes_sent}}; } else { // Due to the error none of the bytes were written. // TODO: add errno conversion - return {static_cast(errno), std::span{buffer.data(), buffer.size()}}; + return {errno_to_ip_status(), std::string_view{buffer.data(), buffer.size()}}; } } }; diff --git a/include/glaze/network/ip.hpp b/include/glaze/network/ip.hpp index 385fcfd5aa..ad42f5f16e 100644 --- a/include/glaze/network/ip.hpp +++ b/include/glaze/network/ip.hpp @@ -41,14 +41,39 @@ namespace glz ok, // closed, // connected, // + connection_refused, // invalid_ip_address, // timeout, // error, // try_again, // would_block, // - bad_file_descriptor, // - connection_refused, // + bad_file_descriptor // ); + + inline ip_status errno_to_ip_status() noexcept + { +#if defined(__linux__) || defined(__APPLE__) + const auto err = errno; + using enum ip_status; + switch (err) { + case 0: { + return ok; + } + case -1: { + return closed; + } + case EWOULDBLOCK: { + return would_block; + } + case ECONNREFUSED: { + return connection_refused; + } + default: { + return error; + } + } +#endif + } /* // ip_status From bac308965a1d2de91ab3ab3dd402394c0a27fc89 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:15:32 -0500 Subject: [PATCH 253/309] exposing networking code in io_scheduler --- include/glaze/coroutine/io_scheduler.hpp | 14 ++++---------- include/glaze/network/client.hpp | 4 +--- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index b293f6f4b4..d33b97a089 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -19,10 +19,7 @@ #include "glaze/coroutine/task_container.hpp" #include "glaze/coroutine/thread_pool.hpp" #include "glaze/network/core.hpp" - -#ifdef GLZ_FEATURE_NETWORKING -#include "coro/net/socket.hpp" -#endif +#include "glaze/network/socket.hpp" #include #include @@ -309,8 +306,7 @@ namespace glz n_active_tasks.fetch_sub(1, std::memory_order::release); co_return result; } - -#ifdef GLZ_FEATURE_NETWORKING + /** * Polls the given coro::net::socket for the given operations. * @param sock The socket to poll for events on. @@ -319,13 +315,11 @@ namespace glz * block indefinitely until the event triggers. * @return THe result of the poll operation. */ - [[nodiscard]] auto poll(const net::socket& sock, glz::poll_op op, + [[nodiscard]] task poll(const socket& sock, poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) - -> glz::task { - return poll(sock.native_handle(), op, timeout); + return poll(sock.socket_fd, op, timeout); } -#endif /** * Resumes execution of a direct coroutine handle on this io scheduler. diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 8b01db1e74..342830c7da 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -109,7 +109,7 @@ namespace glz auto bytes_recv = ::recv(socket.socket_fd, buffer.data(), buffer.size(), 0); if (bytes_recv > 0) { // Ok, we've recieved some data. - return {ip_status::ok, std::span{buffer.data(), static_cast(bytes_recv)}}; + return {ip_status::ok, std::span{buffer.data(), size_t(bytes_recv)}}; } else if (bytes_recv == 0) { // On TCP stream sockets 0 indicates the connection has been closed by the peer. @@ -117,7 +117,6 @@ namespace glz } else { // Report the error to the user. - // TODO: add errno conversion return {errno_to_ip_status(), std::span{}}; } } @@ -146,7 +145,6 @@ namespace glz } else { // Due to the error none of the bytes were written. - // TODO: add errno conversion return {errno_to_ip_status(), std::string_view{buffer.data(), buffer.size()}}; } } From 7d971202b659c52bcc62b2918e899bab59c61c64 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:17:28 -0500 Subject: [PATCH 254/309] Update io_scheduler.hpp --- include/glaze/coroutine/io_scheduler.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index d33b97a089..e454780e23 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -262,7 +262,7 @@ namespace glz * block indefinitely until the event triggers. * @return The result of the poll operation. */ - [[nodiscard]] glz::task poll(net::event_handle_t fd, glz::poll_op op, + [[nodiscard]] task poll(net::event_handle_t fd, glz::poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { // Because the size will drop when this coroutine suspends every poll needs to undo the subtraction @@ -293,7 +293,7 @@ namespace glz net::poll_event_t e{ .ident = uintptr_t(fd), .filter = EVFILT_READ, .flags = EV_ADD | EV_EOF, .udata = &poll_info}; if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { - std::cerr << "kqueue failed to register for fd: " << fd << "\n"; + std::cerr << "kqueue failed to register for file_descriptor: " << fd << "\n"; } #elif defined(_WIN32) From c1dce1164371ae77426c699164912399b3c1ce11 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:21:23 -0500 Subject: [PATCH 255/309] cleaning --- include/glaze/coroutine/io_scheduler.hpp | 18 +++++++++--------- tests/coroutine_test/coroutine_test.cpp | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index e454780e23..fcb625423d 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -32,19 +32,19 @@ namespace glz { + enum struct thread_strategy { + /// Spawns a dedicated background thread for the scheduler to run on. + spawn, + /// Requires the user to call process_events() to drive the scheduler. + manual + }; + struct io_scheduler final { using clock = std::chrono::steady_clock; using time_point = clock::time_point; using timed_events = poll_info::timed_events; - enum struct thread_strategy_t { - /// Spawns a dedicated background thread for the scheduler to run on. - spawn, - /// Requires the user to call process_events() to drive the scheduler. - manual - }; - enum struct execution_strategy_t { /// Tasks will be FIFO queued to be executed on a thread pool. This is better for tasks that /// are long lived and will use lots of CPU because long lived tasks will block other i/o @@ -60,7 +60,7 @@ namespace glz struct options { /// Should the io scheduler spawn a dedicated event processor? - thread_strategy_t thread_strategy{thread_strategy_t::spawn}; + glz::thread_strategy thread_strategy{glz::thread_strategy::spawn}; /// If spawning a dedicated event processor a functor to call upon that thread starting. std::function on_io_thread_start_functor{}; /// If spawning a dedicated event processor a functor to call upon that thread stopping. @@ -487,7 +487,7 @@ namespace glz ::kevent(event_fd, &e_timer, 1, nullptr, 0, nullptr); #endif - if (m_opts.thread_strategy == thread_strategy_t::spawn) { + if (m_opts.thread_strategy == glz::thread_strategy::spawn) { m_io_thread = std::thread([this]() { process_events_dedicated_thread(); }); } // else manual mode, the user must call process_events. diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 196773a92f..698bccec4a 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -371,7 +371,7 @@ suite io_scheduler_test = [] { auto scheduler = std::make_shared(glz::io_scheduler::options{ // The scheduler will spawn a dedicated event processing thread. This is the default, but // it is possible to use 'manual' and call 'process_events()' to drive the scheduler yourself. - .thread_strategy = glz::io_scheduler::thread_strategy_t::spawn, + .thread_strategy = glz::thread_strategy::spawn, // If the scheduler is in spawn mode this functor is called upon starting the dedicated // event processor thread. .on_io_thread_start_functor = [] { std::cout << "io_scheduler::process event thread start\n"; }, From 44db454dd16dcd815be7b56f2cc958a811bc8bcb Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:25:18 -0500 Subject: [PATCH 256/309] cleaning --- include/glaze/coroutine/io_scheduler.hpp | 14 +++++++------- tests/coroutine_test/coroutine_test.cpp | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/io_scheduler.hpp index fcb625423d..37d1e7ea44 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/io_scheduler.hpp @@ -45,7 +45,7 @@ namespace glz using time_point = clock::time_point; using timed_events = poll_info::timed_events; - enum struct execution_strategy_t { + enum struct execution_strategy { /// Tasks will be FIFO queued to be executed on a thread pool. This is better for tasks that /// are long lived and will use lots of CPU because long lived tasks will block other i/o /// operations while they complete. This strategy is generally better for lower latency @@ -73,7 +73,7 @@ namespace glz /// If inline task processing is enabled then the io worker will resume tasks on its thread /// rather than scheduling them to be picked up by the thread pool. - const execution_strategy_t execution_strategy{execution_strategy_t::process_tasks_on_thread_pool}; + const execution_strategy execution_strategy{execution_strategy::process_tasks_on_thread_pool}; }; io_scheduler() { init(); } @@ -137,7 +137,7 @@ namespace glz */ auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> void { - if (m_scheduler.m_opts.execution_strategy == execution_strategy_t::process_tasks_inline) { + if (m_scheduler.m_opts.execution_strategy == execution_strategy::process_tasks_inline) { m_scheduler.n_active_tasks.fetch_add(1, std::memory_order::release); { std::scoped_lock lk{m_scheduler.m_scheduled_tasks_mutex}; @@ -335,7 +335,7 @@ namespace glz return false; } - if (m_opts.execution_strategy == execution_strategy_t::process_tasks_inline) { + if (m_opts.execution_strategy == execution_strategy::process_tasks_inline) { { std::scoped_lock lk{m_scheduled_tasks_mutex}; m_scheduled_tasks.emplace_back(handle); @@ -368,7 +368,7 @@ namespace glz */ size_t size() const noexcept { - if (m_opts.execution_strategy == execution_strategy_t::process_tasks_inline) { + if (m_opts.execution_strategy == execution_strategy::process_tasks_inline) { return n_active_tasks.load(std::memory_order::acquire); } else { @@ -457,7 +457,7 @@ namespace glz void init() { - if (m_opts.execution_strategy == execution_strategy_t::process_tasks_on_thread_pool) { + if (m_opts.execution_strategy == execution_strategy::process_tasks_on_thread_pool) { m_thread_pool = std::make_unique(std::move(m_opts.pool)); } @@ -609,7 +609,7 @@ namespace glz // the thread switch required. If max_events == 1 this would be unnecessary. if (!m_handles_to_resume.empty()) { - if (m_opts.execution_strategy == execution_strategy_t::process_tasks_inline) { + if (m_opts.execution_strategy == execution_strategy::process_tasks_inline) { for (auto& handle : m_handles_to_resume) { handle.resume(); } diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 698bccec4a..1df6d8d202 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -389,7 +389,7 @@ suite io_scheduler_test = [] { .on_thread_stop_functor = [](size_t i) { std::cout << "io_scheduler::thread_pool worker " << i << " stopping\n"; }, }, - .execution_strategy = glz::io_scheduler::execution_strategy_t::process_tasks_on_thread_pool}); + .execution_strategy = glz::io_scheduler::execution_strategy::process_tasks_on_thread_pool}); auto make_server_task = [&]() -> glz::task { // Start by creating a tcp server, we'll do this before putting it into the scheduler so From 543dd311fa8a492c0caa8e5f25132d6648e7378c Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:26:43 -0500 Subject: [PATCH 257/309] io_scheduler -> scheduler --- include/glaze/coroutine.hpp | 2 +- .../{io_scheduler.hpp => scheduler.hpp} | 22 +++++++++---------- include/glaze/coroutine/task_container.hpp | 8 +++---- include/glaze/network/client.hpp | 4 ++-- include/glaze/network/server.hpp | 2 +- tests/coroutine_test/coroutine_test.cpp | 20 ++++++++--------- 6 files changed, 29 insertions(+), 29 deletions(-) rename include/glaze/coroutine/{io_scheduler.hpp => scheduler.hpp} (98%) diff --git a/include/glaze/coroutine.hpp b/include/glaze/coroutine.hpp index e4f803c0a5..e29396ab24 100644 --- a/include/glaze/coroutine.hpp +++ b/include/glaze/coroutine.hpp @@ -5,7 +5,7 @@ #include "glaze/coroutine/awaitable.hpp" #include "glaze/coroutine/generator.hpp" -#include "glaze/coroutine/io_scheduler.hpp" +#include "glaze/coroutine/scheduler.hpp" #include "glaze/coroutine/latch.hpp" #include "glaze/coroutine/mutex.hpp" #include "glaze/coroutine/ring_buffer.hpp" diff --git a/include/glaze/coroutine/io_scheduler.hpp b/include/glaze/coroutine/scheduler.hpp similarity index 98% rename from include/glaze/coroutine/io_scheduler.hpp rename to include/glaze/coroutine/scheduler.hpp index 37d1e7ea44..770c0e3a64 100644 --- a/include/glaze/coroutine/io_scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -39,7 +39,7 @@ namespace glz manual }; - struct io_scheduler final + struct scheduler final { using clock = std::chrono::steady_clock; using time_point = clock::time_point; @@ -76,16 +76,16 @@ namespace glz const execution_strategy execution_strategy{execution_strategy::process_tasks_on_thread_pool}; }; - io_scheduler() { init(); } + scheduler() { init(); } - io_scheduler(options opts) : m_opts(std::move(opts)) { init(); } + scheduler(options opts) : m_opts(std::move(opts)) { init(); } - io_scheduler(const io_scheduler&) = delete; - io_scheduler(io_scheduler&&) = delete; - io_scheduler& operator=(const io_scheduler&) = delete; - io_scheduler& operator=(io_scheduler&&) = delete; + scheduler(const scheduler&) = delete; + scheduler(scheduler&&) = delete; + scheduler& operator=(const scheduler&) = delete; + scheduler& operator=(scheduler&&) = delete; - ~io_scheduler() + ~scheduler() { shutdown(); @@ -124,7 +124,7 @@ namespace glz struct schedule_operation { /// The thread pool that this operation will execute on. - io_scheduler& m_scheduler; + scheduler& m_scheduler; /** * Operations always pause so the executing thread can be switched. @@ -172,7 +172,7 @@ namespace glz }; /** - * Schedules the current task onto this io_scheduler for execution. + * Schedules the current task onto this scheduler for execution. */ auto schedule() -> schedule_operation { return schedule_operation{*this}; } @@ -441,7 +441,7 @@ namespace glz /// or for tasks that are polling with timeouts. timed_events m_timed_events{}; - /// Has the io_scheduler been requested to shut down? + /// Has the scheduler been requested to shut down? std::atomic m_shutdown_requested{false}; std::atomic m_io_processing{false}; diff --git a/include/glaze/coroutine/task_container.hpp b/include/glaze/coroutine/task_container.hpp index 8089d07a5c..64459bd60d 100644 --- a/include/glaze/coroutine/task_container.hpp +++ b/include/glaze/coroutine/task_container.hpp @@ -27,7 +27,7 @@ namespace glz { - struct io_scheduler; + struct scheduler; template struct task_container @@ -42,7 +42,7 @@ namespace glz /** * @param e Tasks started in the container are scheduled onto this executor. For tasks created - * from a coro::io_scheduler, this would usually be that coro::io_scheduler instance. + * from a coro::scheduler, this would usually be that coro::scheduler instance. * @param opts Task container options. */ task_container(std::shared_ptr e, @@ -258,14 +258,14 @@ namespace glz double m_growth_factor{}; /// The executor to schedule tasks that have just started. std::shared_ptr m_executor{nullptr}; - /// This is used internally since io_scheduler cannot pass itself in as a shared_ptr. + /// This is used internally since scheduler cannot pass itself in as a shared_ptr. executor_type* m_executor_ptr{nullptr}; /** * Special constructor for internal types to create their embeded task containers. */ - friend io_scheduler; + friend scheduler; task_container(executor_type& e, const options opts = options{.reserve_size = 8, .growth_factor = 2}) : m_growth_factor(opts.growth_factor), m_executor_ptr(&e) { diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 342830c7da..62487dd989 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -3,7 +3,7 @@ #pragma once -#include "glaze/coroutine/io_scheduler.hpp" +#include "glaze/coroutine/scheduler.hpp" #include "glaze/network/ip.hpp" #include "glaze/network/socket.hpp" @@ -16,7 +16,7 @@ namespace glz */ struct client { - std::shared_ptr scheduler{}; + std::shared_ptr scheduler{}; std::string address{"127.0.0.1"}; uint16_t port{8080}; ip_version ipv{}; diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index ba715e98d6..2ce848d7ec 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -12,7 +12,7 @@ namespace glz { struct server { - std::shared_ptr scheduler{}; + std::shared_ptr scheduler{}; std::string address{"0.0.0.0"}; uint16_t port{8080}; int32_t backlog{128}; // The kernel backlog of connections to buffer. diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 1df6d8d202..3d1f51c1dd 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -46,7 +46,7 @@ suite thread_pool = [] { auto make_task_inline = [](uint64_t x) -> glz::task { co_return x + x; }; // This will block the calling thread until the created task completes. - // Since this task isn't scheduled on any glz::thread_pool or glz::io_scheduler + // Since this task isn't scheduled on any glz::thread_pool or glz::scheduler // it will execute directly on the calling thread. auto result = glz::sync_wait(make_task_inline(5)); expect(result == 10); @@ -135,10 +135,10 @@ suite event = [] { suite latch = [] { std::cout << "\nLatch test:\n"; - // Complete worker tasks faster on a thread pool, using the io_scheduler version so the worker + // Complete worker tasks faster on a thread pool, using the scheduler version so the worker // tasks can yield for a specific amount of time to mimic difficult work. The pool is only // setup with a single thread to showcase yield_for(). - glz::io_scheduler tp{glz::io_scheduler::options{.pool = glz::thread_pool::options{.thread_count = 1}}}; + glz::scheduler tp{glz::scheduler::options{.pool = glz::thread_pool::options{.thread_count = 1}}}; // This task will wait until the given latch setters have completed. auto make_latch_task = [](glz::latch& l) -> glz::task { @@ -157,7 +157,7 @@ suite latch = [] { // This task does 'work' and counts down on the latch when completed. The final child task to // complete will end up resuming the latch task when the latch's count reaches zero. - auto make_worker_task = [](glz::io_scheduler& tp, glz::latch& l, int64_t i) -> glz::task { + auto make_worker_task = [](glz::scheduler& tp, glz::latch& l, int64_t i) -> glz::task { // Schedule the worker task onto the thread pool. co_await tp.schedule(); std::cout << "worker task " << i << " is working...\n"; @@ -368,16 +368,16 @@ suite ring_buffer_test = [] { }; suite io_scheduler_test = [] { - auto scheduler = std::make_shared(glz::io_scheduler::options{ + auto scheduler = std::make_shared(glz::scheduler::options{ // The scheduler will spawn a dedicated event processing thread. This is the default, but // it is possible to use 'manual' and call 'process_events()' to drive the scheduler yourself. .thread_strategy = glz::thread_strategy::spawn, // If the scheduler is in spawn mode this functor is called upon starting the dedicated // event processor thread. - .on_io_thread_start_functor = [] { std::cout << "io_scheduler::process event thread start\n"; }, + .on_io_thread_start_functor = [] { std::cout << "scheduler::process event thread start\n"; }, // If the scheduler is in spawn mode this functor is called upon stopping the dedicated // event process thread. - .on_io_thread_stop_functor = [] { std::cout << "io_scheduler::process event thread stop\n"; }, + .on_io_thread_stop_functor = [] { std::cout << "scheduler::process event thread stop\n"; }, // The io scheduler can use a coro::thread_pool to process the events or tasks it is given. // You can use an execution strategy of `process_tasks_inline` to have the event loop thread // directly process the tasks, this might be desirable for small tasks vs a thread pool for large tasks. @@ -385,11 +385,11 @@ suite io_scheduler_test = [] { glz::thread_pool::options{ .thread_count = 2, .on_thread_start_functor = - [](size_t i) { std::cout << "io_scheduler::thread_pool worker " << i << " starting\n"; }, + [](size_t i) { std::cout << "scheduler::thread_pool worker " << i << " starting\n"; }, .on_thread_stop_functor = - [](size_t i) { std::cout << "io_scheduler::thread_pool worker " << i << " stopping\n"; }, + [](size_t i) { std::cout << "scheduler::thread_pool worker " << i << " stopping\n"; }, }, - .execution_strategy = glz::io_scheduler::execution_strategy::process_tasks_on_thread_pool}); + .execution_strategy = glz::scheduler::execution_strategy::process_tasks_on_thread_pool}); auto make_server_task = [&]() -> glz::task { // Start by creating a tcp server, we'll do this before putting it into the scheduler so From b9f5c15b5c3779ce70f0e4f76d2b77706d9b6e74 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:47:36 -0500 Subject: [PATCH 258/309] cleaning --- include/glaze/coroutine/when_all.hpp | 56 ++++++++++++------------- tests/coroutine_test/coroutine_test.cpp | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/include/glaze/coroutine/when_all.hpp b/include/glaze/coroutine/when_all.hpp index 4b734d72aa..14cd35e9dd 100644 --- a/include/glaze/coroutine/when_all.hpp +++ b/include/glaze/coroutine/when_all.hpp @@ -39,18 +39,18 @@ namespace glz return *this; } - auto is_ready() const noexcept -> bool + bool is_ready() const noexcept { return m_awaiting_coroutine && m_awaiting_coroutine.done(); } - auto try_await(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool + bool try_await(std::coroutine_handle<> awaiting_coroutine) noexcept { m_awaiting_coroutine = awaiting_coroutine; return m_count.fetch_sub(1, std::memory_order::acq_rel) > 1; } - auto notify_awaitable_completed() noexcept -> void + void notify_awaitable_completed() noexcept { if (m_count.fetch_sub(1, std::memory_order::acq_rel) == 1) { m_awaiting_coroutine.resume(); @@ -61,13 +61,13 @@ namespace glz /// The number of tasks that are being waited on. std::atomic m_count; /// The when_all_task awaiting to be resumed upon all task completions. - std::coroutine_handle<> m_awaiting_coroutine{nullptr}; + std::coroutine_handle<> m_awaiting_coroutine{}; }; - template + template struct when_all_ready_awaitable; - template + template struct when_all_task; /// Empty tuple<> implementation. @@ -82,17 +82,17 @@ namespace glz auto await_resume() const noexcept -> std::tuple<> { return {}; } }; - template - struct when_all_ready_awaitable> + template + struct when_all_ready_awaitable> { - explicit when_all_ready_awaitable(task_types&&... tasks) noexcept( - std::conjunction...>::value) - : m_latch(sizeof...(task_types)), m_tasks(std::move(tasks)...) + explicit when_all_ready_awaitable(Tasks&&... tasks) noexcept( + std::conjunction...>::value) + : m_latch(sizeof...(Tasks)), m_tasks(std::move(tasks)...) {} - explicit when_all_ready_awaitable(std::tuple&& tasks) noexcept( - std::is_nothrow_move_constructible_v>) - : m_latch(sizeof...(task_types)), m_tasks(std::move(tasks)) + explicit when_all_ready_awaitable(std::tuple&& tasks) noexcept( + std::is_nothrow_move_constructible_v>) + : m_latch(sizeof...(Tasks)), m_tasks(std::move(tasks)) {} when_all_ready_awaitable(const when_all_ready_awaitable&) = delete; @@ -116,7 +116,7 @@ namespace glz return m_awaitable.try_await(awaiting_coroutine); } - auto await_resume() noexcept -> std::tuple& { return m_awaitable.m_tasks; } + auto await_resume() noexcept -> std::tuple& { return m_awaitable.m_tasks; } private: when_all_ready_awaitable& m_awaitable; @@ -131,14 +131,14 @@ namespace glz { explicit awaiter(when_all_ready_awaitable& awaitable) noexcept : m_awaitable(awaitable) {} - auto await_ready() const noexcept -> bool { return m_awaitable.is_ready(); } + bool await_ready() const noexcept { return m_awaitable.is_ready(); } auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool { return m_awaitable.try_await(awaiting_coroutine); } - auto await_resume() noexcept -> std::tuple&& { return std::move(m_awaitable.m_tasks); } + auto await_resume() noexcept -> std::tuple&& { return std::move(m_awaitable.m_tasks); } private: when_all_ready_awaitable& m_awaitable; @@ -148,28 +148,28 @@ namespace glz } private: - auto is_ready() const noexcept -> bool { return m_latch.is_ready(); } + bool is_ready() const noexcept { return m_latch.is_ready(); } - auto try_await(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool + bool try_await(std::coroutine_handle<> awaiting_coroutine) noexcept { std::apply([this](auto&&... tasks) { ((tasks.start(m_latch)), ...); }, m_tasks); return m_latch.try_await(awaiting_coroutine); } when_all_latch m_latch; - std::tuple m_tasks; + std::tuple m_tasks; }; - template + template struct when_all_ready_awaitable { - explicit when_all_ready_awaitable(task_container_type&& tasks) noexcept - : m_latch(std::size(tasks)), m_tasks(std::forward(tasks)) + explicit when_all_ready_awaitable(TaskContainer&& tasks) noexcept + : m_latch(std::size(tasks)), m_tasks(std::forward(tasks)) {} when_all_ready_awaitable(const when_all_ready_awaitable&) = delete; when_all_ready_awaitable(when_all_ready_awaitable&& other) noexcept( - std::is_nothrow_move_constructible_v) + std::is_nothrow_move_constructible_v) : m_latch(std::move(other.m_latch)), m_tasks(std::move(m_tasks)) {} @@ -189,7 +189,7 @@ namespace glz return m_awaitable.try_await(awaiting_coroutine); } - auto await_resume() noexcept -> task_container_type& { return m_awaitable.m_tasks; } + auto await_resume() noexcept -> TaskContainer& { return m_awaitable.m_tasks; } private: when_all_ready_awaitable& m_awaitable; @@ -211,7 +211,7 @@ namespace glz return m_awaitable.try_await(awaiting_coroutine); } - auto await_resume() noexcept -> task_container_type&& { return std::move(m_awaitable.m_tasks); } + auto await_resume() noexcept -> TaskContainer&& { return std::move(m_awaitable.m_tasks); } private: when_all_ready_awaitable& m_awaitable; @@ -233,7 +233,7 @@ namespace glz } when_all_latch m_latch; - task_container_type m_tasks; + TaskContainer m_tasks; }; template @@ -358,7 +358,7 @@ namespace glz struct when_all_task { // To be able to call start(). - template + template friend struct when_all_ready_awaitable; using promise_type = when_all_task_promise; diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 3d1f51c1dd..e67cd2ebff 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -383,7 +383,7 @@ suite io_scheduler_test = [] { // directly process the tasks, this might be desirable for small tasks vs a thread pool for large tasks. .pool = glz::thread_pool::options{ - .thread_count = 2, + .thread_count = 1, .on_thread_start_functor = [](size_t i) { std::cout << "scheduler::thread_pool worker " << i << " starting\n"; }, .on_thread_stop_functor = From 27fef5a724017bada44ac934346cb349b5d5ccc4 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:49:47 -0500 Subject: [PATCH 259/309] Update when_all.hpp --- include/glaze/coroutine/when_all.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/glaze/coroutine/when_all.hpp b/include/glaze/coroutine/when_all.hpp index 14cd35e9dd..54fe1ac40c 100644 --- a/include/glaze/coroutine/when_all.hpp +++ b/include/glaze/coroutine/when_all.hpp @@ -438,23 +438,23 @@ namespace glz } // namespace detail - template - [[nodiscard]] auto when_all(awaitables_type... awaitables) + template + [[nodiscard]] auto when_all(Awaitables... awaitables) { return detail::when_all_ready_awaitable::awaiter_return_type>...>>( + detail::when_all_task::awaiter_return_type>...>>( std::make_tuple(detail::make_when_all_task(std::move(awaitables))...)); } - template , + template , class return_type = typename awaitable_traits::awaiter_return_type> - [[nodiscard]] auto when_all(range_type awaitables) + [[nodiscard]] auto when_all(Range awaitables) -> detail::when_all_ready_awaitable>> { std::vector> output_tasks; // If the size is known in constant time reserve the output tasks size. - if constexpr (std::ranges::sized_range) { + if constexpr (std::ranges::sized_range) { output_tasks.reserve(std::size(awaitables)); } From e0e4017a84e66cddf1a7fd08efd08af74a9416b6 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:52:29 -0500 Subject: [PATCH 260/309] simplifying --- include/glaze/coroutine/awaitable.hpp | 4 ++-- include/glaze/coroutine/sync_wait.hpp | 4 ++-- include/glaze/coroutine/when_all.hpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/glaze/coroutine/awaitable.hpp b/include/glaze/coroutine/awaitable.hpp index e65c0c31e4..83fbe10b6e 100644 --- a/include/glaze/coroutine/awaitable.hpp +++ b/include/glaze/coroutine/awaitable.hpp @@ -100,7 +100,7 @@ namespace glz template struct awaitable_traits { - using awaiter_type = decltype(get_awaiter(std::declval())); - using awaiter_return_type = decltype(std::declval().await_resume()); + using type = decltype(get_awaiter(std::declval())); + using return_type = decltype(std::declval().await_resume()); }; } diff --git a/include/glaze/coroutine/sync_wait.hpp b/include/glaze/coroutine/sync_wait.hpp index a3d6dad3f0..eba3813a55 100644 --- a/include/glaze/coroutine/sync_wait.hpp +++ b/include/glaze/coroutine/sync_wait.hpp @@ -268,7 +268,7 @@ namespace glz }; template ::awaiter_return_type> + class return_type = awaitable_traits::return_type> static auto make_sync_wait_task(awaitable_type&& a) -> sync_wait_task; template @@ -285,7 +285,7 @@ namespace glz } template ::awaiter_return_type> + class return_type = typename awaitable_traits::return_type> auto sync_wait(awaitable_type&& a) -> decltype(auto) { detail::sync_wait_event e{}; diff --git a/include/glaze/coroutine/when_all.hpp b/include/glaze/coroutine/when_all.hpp index 54fe1ac40c..51df2c8883 100644 --- a/include/glaze/coroutine/when_all.hpp +++ b/include/glaze/coroutine/when_all.hpp @@ -421,7 +421,7 @@ namespace glz }; template ::awaiter_return_type> + class return_type = typename awaitable_traits::return_type> static auto make_when_all_task(Awaitable a) -> when_all_task; template @@ -442,12 +442,12 @@ namespace glz [[nodiscard]] auto when_all(Awaitables... awaitables) { return detail::when_all_ready_awaitable::awaiter_return_type>...>>( + detail::when_all_task::return_type>...>>( std::make_tuple(detail::make_when_all_task(std::move(awaitables))...)); } template , - class return_type = typename awaitable_traits::awaiter_return_type> + class return_type = typename awaitable_traits::return_type> [[nodiscard]] auto when_all(Range awaitables) -> detail::when_all_ready_awaitable>> { From b9d46e53ccc6e0c0ad65d4a616f187e0cf11fbfb Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:53:27 -0500 Subject: [PATCH 261/309] Update when_all.hpp --- include/glaze/coroutine/when_all.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/glaze/coroutine/when_all.hpp b/include/glaze/coroutine/when_all.hpp index 51df2c8883..6555cc4fc6 100644 --- a/include/glaze/coroutine/when_all.hpp +++ b/include/glaze/coroutine/when_all.hpp @@ -446,12 +446,12 @@ namespace glz std::make_tuple(detail::make_when_all_task(std::move(awaitables))...)); } - template , - class return_type = typename awaitable_traits::return_type> + template , + class Return = typename awaitable_traits::return_type> [[nodiscard]] auto when_all(Range awaitables) - -> detail::when_all_ready_awaitable>> + -> detail::when_all_ready_awaitable>> { - std::vector> output_tasks; + std::vector> output_tasks; // If the size is known in constant time reserve the output tasks size. if constexpr (std::ranges::sized_range) { From 6941dfe65a1d472fcfe37996cb368cb64c21e7a0 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:58:34 -0500 Subject: [PATCH 262/309] Update when_all.hpp --- include/glaze/coroutine/when_all.hpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/include/glaze/coroutine/when_all.hpp b/include/glaze/coroutine/when_all.hpp index 6555cc4fc6..ac9d4351a6 100644 --- a/include/glaze/coroutine/when_all.hpp +++ b/include/glaze/coroutine/when_all.hpp @@ -109,7 +109,7 @@ namespace glz { explicit awaiter(when_all_ready_awaitable& awaitable) noexcept : m_awaitable(awaitable) {} - auto await_ready() const noexcept -> bool { return m_awaitable.is_ready(); } + bool await_ready() const noexcept { return m_awaitable.is_ready(); } auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> bool { @@ -354,14 +354,10 @@ namespace glz std::exception_ptr m_exception_ptr; }; - template + template struct when_all_task { - // To be able to call start(). - template - friend struct when_all_ready_awaitable; - - using promise_type = when_all_task_promise; + using promise_type = when_all_task_promise; using coroutine_handle_type = typename promise_type::coroutine_handle_type; when_all_task(coroutine_handle_type coroutine) noexcept : m_coroutine(coroutine) {} @@ -381,9 +377,9 @@ namespace glz } } - auto return_value() & -> decltype(auto) + decltype(auto) return_value() & { - if constexpr (std::is_void_v) { + if constexpr (std::is_void_v) { m_coroutine.promise().result(); return std::nullptr_t{}; } @@ -392,9 +388,9 @@ namespace glz } } - auto return_value() const& -> decltype(auto) + decltype(auto) return_value() const& { - if constexpr (std::is_void_v) { + if constexpr (std::is_void_v) { m_coroutine.promise().result(); return std::nullptr_t{}; } @@ -403,9 +399,9 @@ namespace glz } } - auto return_value() && -> decltype(auto) + decltype(auto) return_value() && { - if constexpr (std::is_void_v) { + if constexpr (std::is_void_v) { m_coroutine.promise().result(); return std::nullptr_t{}; } @@ -413,10 +409,10 @@ namespace glz return m_coroutine.promise().result(); } } + + void start(when_all_latch& latch) noexcept { m_coroutine.promise().start(latch); } private: - auto start(when_all_latch& latch) noexcept -> void { m_coroutine.promise().start(latch); } - coroutine_handle_type m_coroutine; }; From 920aadbf076587f1a78e800e21ce71847ed61871 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 2 Jul 2024 13:59:28 -0500 Subject: [PATCH 263/309] Update when_all.hpp --- include/glaze/coroutine/when_all.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/glaze/coroutine/when_all.hpp b/include/glaze/coroutine/when_all.hpp index ac9d4351a6..fd3e14405e 100644 --- a/include/glaze/coroutine/when_all.hpp +++ b/include/glaze/coroutine/when_all.hpp @@ -420,15 +420,15 @@ namespace glz class return_type = typename awaitable_traits::return_type> static auto make_when_all_task(Awaitable a) -> when_all_task; - template - static auto make_when_all_task(awaitable a) -> when_all_task + template + static auto make_when_all_task(Awaitable a) -> when_all_task { - if constexpr (std::is_void_v) { - co_await static_cast(a); + if constexpr (std::is_void_v) { + co_await static_cast(a); co_return; } else { - co_yield co_await static_cast(a); + co_yield co_await static_cast(a); } } From 5e9ecb6eec4bb34bf168e9802fc4ac03fe6ba41f Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Tue, 2 Jul 2024 14:57:50 -0500 Subject: [PATCH 264/309] Corrected build errors on Windows. --- include/glaze/coroutine/scheduler.hpp | 1 + include/glaze/coroutine/task.hpp | 11 ++++++----- include/glaze/network/client.hpp | 19 +++++++++++++------ include/glaze/network/core.hpp | 2 ++ tests/coroutine_test/coroutine_test.cpp | 8 ++++---- tests/socket_test/socket_test.cpp | 2 +- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index 770c0e3a64..75f36b8e7e 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -318,6 +318,7 @@ namespace glz [[nodiscard]] task poll(const socket& sock, poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { + static_assert(false, "Fix recursive poll call"); return poll(sock.socket_fd, op, timeout); } diff --git a/include/glaze/coroutine/task.hpp b/include/glaze/coroutine/task.hpp index 58d9b41520..726fa4719c 100644 --- a/include/glaze/coroutine/task.hpp +++ b/include/glaze/coroutine/task.hpp @@ -5,19 +5,20 @@ #pragma once +#include +#include +#include +#include +#include + #ifndef GLZ_THROW_OR_ABORT #if __cpp_exceptions #define GLZ_THROW_OR_ABORT(EXC) (throw(EXC)) -#include #else #define GLZ_THROW_OR_ABORT(EXC) (std::abort()) #endif #endif -#include -#include -#include - namespace glz { template diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 62487dd989..9f601258f3 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -45,9 +45,9 @@ namespace glz }; sockaddr_in server_addr; - server_addr.sin_family = int(ipv); + server_addr.sin_family = glz::net::asize_t(ipv); server_addr.sin_port = htons(port); - ::inet_pton(int(ipv), address.c_str(), &server_addr.sin_addr); + ::inet_pton(glz::net::asize_t(ipv), address.c_str(), &server_addr.sin_addr); auto result = ::connect(socket.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)); if (result == 0) { @@ -59,9 +59,16 @@ namespace glz if (errno == EAGAIN || errno == EINPROGRESS) { auto pstatus = co_await scheduler->poll(socket.socket_fd, poll_op::write, timeout); if (pstatus == poll_status::event) { - int result{0}; + result = 0; socklen_t result_length{sizeof(result)}; - if (getsockopt(socket.socket_fd, SOL_SOCKET, SO_ERROR, &result, &result_length) < 0) { + + #if defined(_WIN32) + if (getsockopt(socket.socket_fd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&result), + #else + if (getsockopt(socket.socket_fd, SOL_SOCKET, SO_ERROR, &result, + #endif + + &result_length) < 0) { std::cerr << "connect failed to getsockopt after write poll event\n"; } @@ -106,7 +113,7 @@ namespace glz return {ip_status::ok, std::span{}}; } - auto bytes_recv = ::recv(socket.socket_fd, buffer.data(), buffer.size(), 0); + auto bytes_recv = ::recv(socket.socket_fd, buffer.data(), glz::net::ssize_t(buffer.size()), 0); if (bytes_recv > 0) { // Ok, we've recieved some data. return {ip_status::ok, std::span{buffer.data(), size_t(bytes_recv)}}; @@ -138,7 +145,7 @@ namespace glz return {ip_status::ok, std::string_view{buffer.data(), buffer.size()}}; } - auto bytes_sent = ::send(socket.socket_fd, buffer.data(), buffer.size(), 0); + auto bytes_sent = ::send(socket.socket_fd, buffer.data(), glz::net::ssize_t(buffer.size()), 0); if (bytes_sent >= 0) { // Some or all of the bytes were written. return {ip_status::ok, std::string_view{buffer.data() + bytes_sent, buffer.size() - bytes_sent}}; diff --git a/include/glaze/network/core.hpp b/include/glaze/network/core.hpp index eacbddaf06..62d4eb6cfa 100644 --- a/include/glaze/network/core.hpp +++ b/include/glaze/network/core.hpp @@ -47,9 +47,11 @@ namespace glz::net #ifdef _WIN32 using event_handle_t = HANDLE; using ssize_t = int32_t; + using asize_t = uint8_t; #else using event_handle_t = int; using ssize_t = ::ssize_t; + using asize_t = int; #endif #if defined(__APPLE__) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index e67cd2ebff..30b1b7f8bd 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -448,8 +448,8 @@ suite io_scheduler_test = [] { std::span remaining = response; do { // Optimistically send() prior to polling. - auto [ip_status, r] = client.send(remaining); - if (ip_status != glz::ip_status::ok) { + auto [ips, r] = client.send(remaining); + if (ips != glz::ip_status::ok) { co_return; // Handle error, see net::ip_status for detailed error states. } @@ -460,8 +460,8 @@ suite io_scheduler_test = [] { // Re-assign remaining bytes for the next loop iteration and poll for the socket to be // able to be written to again. remaining = r; - auto pstatus = co_await client.poll(glz::poll_op::write); - if (pstatus != glz::poll_status::event) { + poll_status = co_await client.poll(glz::poll_op::write); + if (poll_status != glz::poll_status::event) { co_return; // Handle error. } } while (true); diff --git a/tests/socket_test/socket_test.cpp b/tests/socket_test/socket_test.cpp index 96c7bda597..c5fbdbf58e 100644 --- a/tests/socket_test/socket_test.cpp +++ b/tests/socket_test/socket_test.cpp @@ -27,7 +27,7 @@ static std::atomic_int working_clients{n_clients}; glz::windows_socket_startup_t<> wsa; // wsa_startup (ignored on macOS and Linux) -glz::server server{service_0_port}; +glz::server server{.port = service_0_port}; suite make_server = [] { std::cout << std::format("Server started on port: {}\n", server.port); From d727e85e92167a288b3511f9f5b7f7adf6401a8b Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 09:26:18 -0500 Subject: [PATCH 265/309] updates --- include/glaze/coroutine/scheduler.hpp | 1 - include/glaze/network/socket.hpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index 75f36b8e7e..770c0e3a64 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -318,7 +318,6 @@ namespace glz [[nodiscard]] task poll(const socket& sock, poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { - static_assert(false, "Fix recursive poll call"); return poll(sock.socket_fd, op, timeout); } diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 77b558e383..7b84ba08a6 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -51,7 +51,7 @@ namespace glz socket() = default; - socket(socket_t fd) : socket_fd(fd) { set_non_blocking(); } + explicit socket(socket_t fd) : socket_fd(fd) { set_non_blocking(); } void close() { From 03b23eb0cadd33cf71d777b2a658dd1a933c9d61 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 10:17:33 -0500 Subject: [PATCH 266/309] make_accept_socket --- include/glaze/network/server.hpp | 6 +- include/glaze/network/socket.hpp | 84 ++++++++++++++++--------- tests/coroutine_test/coroutine_test.cpp | 1 + 3 files changed, 59 insertions(+), 32 deletions(-) diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index 2ce848d7ec..bdb0a34912 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -17,7 +17,7 @@ namespace glz uint16_t port{8080}; int32_t backlog{128}; // The kernel backlog of connections to buffer. /// The socket for accepting new tcp connections on. - socket accept_socket{}; + std::shared_ptr accept_socket = make_accept_socket(address, port); /** * Polls for new incoming tcp connections. @@ -27,7 +27,7 @@ namespace glz */ task poll(std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { - return scheduler->poll(accept_socket.socket_fd, poll_op::read, timeout); + return scheduler->poll(accept_socket->socket_fd, poll_op::read, timeout); } /** @@ -39,7 +39,7 @@ namespace glz { sockaddr_in client{}; constexpr int len = sizeof(struct sockaddr_in); - socket s{::accept(accept_socket.socket_fd, (struct sockaddr*)(&client), + socket sock{::accept(accept_socket->socket_fd, (struct sockaddr*)(&client), const_cast((const socklen_t*)(&len)))}; std::string_view ip_addr_view{ diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 7b84ba08a6..d2403b91aa 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -34,25 +34,10 @@ namespace glz namespace glz { - struct socket + struct socket final { socket_t socket_fd{net::invalid_socket}; - void set_non_blocking() - { -#ifdef _WIN32 - u_long mode = 1; - ioctlsocket(socket_fd, FIONBIO, &mode); -#else - int flags = fcntl(socket_fd, F_GETFL, 0); - fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); -#endif - } - - socket() = default; - - explicit socket(socket_t fd) : socket_fd(fd) { set_non_blocking(); } - void close() { if (socket_fd != net::invalid_socket) { @@ -74,10 +59,23 @@ namespace glz } }; - [[nodiscard]] inline std::error_code connect(socket& sckt, const std::string& address, const int port) + inline void set_non_blocking(socket& sock) noexcept + { +#ifdef _WIN32 + u_long mode = 1; + ioctlsocket(socket_fd, FIONBIO, &mode); +#else + int flags = fcntl(sock.socket_fd, F_GETFL, 0); + fcntl(sock.socket_fd, F_SETFL, flags | O_NONBLOCK); +#endif + } + + [[nodiscard]] inline std::error_code connect(socket& sock, const std::string& address, const int port) { - sckt.socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); - if (sckt.socket_fd == net::invalid_socket) { + // TODO: Support ipv6 + sock.socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + set_non_blocking(sock); + if (sock.socket_fd == net::invalid_socket) { return {int(ip_error::socket_connect_failed), ip_error_category::instance()}; } @@ -86,43 +84,71 @@ namespace glz server_addr.sin_port = htons(uint16_t(port)); ::inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr); - if (::connect(sckt.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { + if (::connect(sock.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { return {int(ip_error::socket_connect_failed), ip_error_category::instance()}; } - sckt.set_non_blocking(); + set_non_blocking(sock); return {}; } - [[nodiscard]] inline std::error_code bind_and_listen(socket& sckt, int port) + [[nodiscard]] inline std::error_code bind_and_listen(socket& sock, const std::string& address, int port) { - sckt.socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); - if (sckt.socket_fd == net::invalid_socket) { + sock.socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + set_non_blocking(sock); + if (sock.socket_fd == net::invalid_socket) { return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; } + + int sock_opt{1}; + if (setsockopt(sock.socket_fd, SOL_SOCKET, SO_REUSEADDR, &sock_opt, sizeof(sock_opt)) < 0) + { + std::cerr << "setsockopt SO_REUSEADDR: " << get_socket_error_message(errno) << '\n'; + return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; + } + + #ifdef SO_REUSEPORT + if (setsockopt(sock.socket_fd, SOL_SOCKET, SO_REUSEPORT, &sock_opt, sizeof(sock_opt)) < 0) + { + std::cerr << "setsockopt SO_REUSEPORT: " << get_socket_error_message(errno) << '\n'; + // You might want to handle this error differently, as it's not critical + } + #endif sockaddr_in server_addr; server_addr.sin_family = AF_INET; - server_addr.sin_addr.s_addr = INADDR_ANY; // TODO: Make this support a specific address server_addr.sin_port = htons(uint16_t(port)); + ::inet_pton(glz::net::asize_t(AF_INET), address.c_str(), &server_addr.sin_addr); - if (::bind(sckt.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { + if (::bind(sock.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) { return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; } - if (::listen(sckt.socket_fd, SOMAXCONN) == -1) { + if (::listen(sock.socket_fd, SOMAXCONN) == -1) { return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; } - sckt.set_non_blocking(); - if (not sckt.no_delay()) { + set_non_blocking(sock); + if (not sock.no_delay()) { return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; } return {}; } + [[nodiscard]] inline std::shared_ptr make_accept_socket(const std::string& address, int port) + { + auto sock = std::make_shared(); + sock->socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + + if (auto ec = bind_and_listen(*sock, address, port)) { + return {}; + } + + return sock; + } + GLZ_ENUM(socket_event, bytes_read, wait, client_disconnected, receive_failed); struct socket_state diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 30b1b7f8bd..1731e05c40 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -500,6 +500,7 @@ suite io_scheduler_test = [] { }; // Create and wait for the server and client tasks to complete. + //glz::sync_wait(glz::when_all(make_server_task(), make_client_task())); glz::sync_wait(glz::when_all(make_server_task(), make_client_task())); }; From d7d4d8e56c55a5ee2153b33fae2b194afe59e95c Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 10:19:51 -0500 Subject: [PATCH 267/309] Update scheduler.hpp --- include/glaze/coroutine/scheduler.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index 770c0e3a64..8554a5832f 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -290,8 +290,7 @@ namespace glz std::cerr << "epoll ctl error on fd " << fd << "\n"; } #elif defined(__APPLE__) - net::poll_event_t e{ - .ident = uintptr_t(fd), .filter = EVFILT_READ, .flags = EV_ADD | EV_EOF, .udata = &poll_info}; + net::poll_event_t e{.filter = EVFILT_READ, .flags = EV_ADD | EV_EOF, .udata = &poll_info}; if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { std::cerr << "kqueue failed to register for file_descriptor: " << fd << "\n"; } From f8a5e7565fd66f789c1833e586d98f9fc69c9c4e Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 10:31:28 -0500 Subject: [PATCH 268/309] fix pegging 100% cpu --- include/glaze/coroutine/scheduler.hpp | 1 + tests/coroutine_test/coroutine_test.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index 8554a5832f..0ffb62e88c 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -501,6 +501,7 @@ namespace glz m_io_processing.exchange(true, std::memory_order::release); // Execute tasks until stopped or there are no more tasks to complete. while (!m_shutdown_requested.load(std::memory_order::acquire) || size() > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); // prevent pegging 100% process_events_execute(m_default_timeout); } m_io_processing.exchange(false, std::memory_order::release); diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 1731e05c40..03858788a3 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -469,7 +469,7 @@ suite io_scheduler_test = [] { co_return; }; - auto make_client_task = [&]() -> glz::task { + [[maybe_unused]] auto make_client_task = [&]() -> glz::task { // Immediately schedule onto the scheduler. co_await scheduler->schedule(); @@ -501,7 +501,7 @@ suite io_scheduler_test = [] { // Create and wait for the server and client tasks to complete. //glz::sync_wait(glz::when_all(make_server_task(), make_client_task())); - glz::sync_wait(glz::when_all(make_server_task(), make_client_task())); + glz::sync_wait(glz::when_all(make_server_task())); }; int main() From 780ad2b2f59f1bb54345d3f1bde7a475b399de22 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 10:37:21 -0500 Subject: [PATCH 269/309] updated --- include/glaze/coroutine/scheduler.hpp | 46 ++++++++++++------------- tests/coroutine_test/coroutine_test.cpp | 9 ++--- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index 0ffb62e88c..d57cae8d48 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -78,7 +78,7 @@ namespace glz scheduler() { init(); } - scheduler(options opts) : m_opts(std::move(opts)) { init(); } + scheduler(options opts) : opts(std::move(opts)) { init(); } scheduler(const scheduler&) = delete; scheduler(scheduler&&) = delete; @@ -124,7 +124,7 @@ namespace glz struct schedule_operation { /// The thread pool that this operation will execute on. - scheduler& m_scheduler; + glz::scheduler& scheduler; /** * Operations always pause so the executing thread can be switched. @@ -135,33 +135,33 @@ namespace glz * Suspending always returns to the caller (using void return of await_suspend()) and * stores the coroutine internally for the executing thread to resume from. */ - auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> void + void await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept { - if (m_scheduler.m_opts.execution_strategy == execution_strategy::process_tasks_inline) { - m_scheduler.n_active_tasks.fetch_add(1, std::memory_order::release); + if (scheduler.opts.execution_strategy == execution_strategy::process_tasks_inline) { + scheduler.n_active_tasks.fetch_add(1, std::memory_order::release); { - std::scoped_lock lk{m_scheduler.m_scheduled_tasks_mutex}; - m_scheduler.m_scheduled_tasks.emplace_back(awaiting_coroutine); + std::scoped_lock lk{scheduler.m_scheduled_tasks_mutex}; + scheduler.m_scheduled_tasks.emplace_back(awaiting_coroutine); } // Trigger the event to wake-up the scheduler if this event isn't currently triggered. bool expected{false}; - if (m_scheduler.m_schedule_fd_triggered.compare_exchange_strong( + if (scheduler.m_schedule_fd_triggered.compare_exchange_strong( expected, true, std::memory_order::release, std::memory_order::relaxed)) { #if defined(__linux__) eventfd_t value{1}; - eventfd_write(m_scheduler.schedule_fd, value); + eventfd_write(scheduler.schedule_fd, value); #elif defined(__APPLE__) net::poll_event_t e{ .filter = EVFILT_USER, .fflags = NOTE_TRIGGER, .udata = const_cast(m_schedule_ptr)}; - if (::kevent(m_scheduler.event_fd, &e, 1, nullptr, 0, nullptr) == -1) { + if (::kevent(scheduler.event_fd, &e, 1, nullptr, 0, nullptr) == -1) { GLZ_THROW_OR_ABORT(std::runtime_error("Failed to trigger wke up")); } #endif } } else { - m_scheduler.m_thread_pool->resume(awaiting_coroutine); + scheduler.m_thread_pool->resume(awaiting_coroutine); } } @@ -174,7 +174,7 @@ namespace glz /** * Schedules the current task onto this scheduler for execution. */ - auto schedule() -> schedule_operation { return schedule_operation{*this}; } + schedule_operation schedule() { return {*this}; } /** * Schedules the current task to run after the given amount of time has elapsed. @@ -334,7 +334,7 @@ namespace glz return false; } - if (m_opts.execution_strategy == execution_strategy::process_tasks_inline) { + if (opts.execution_strategy == execution_strategy::process_tasks_inline) { { std::scoped_lock lk{m_scheduled_tasks_mutex}; m_scheduled_tasks.emplace_back(handle); @@ -367,7 +367,7 @@ namespace glz */ size_t size() const noexcept { - if (m_opts.execution_strategy == execution_strategy::process_tasks_inline) { + if (opts.execution_strategy == execution_strategy::process_tasks_inline) { return n_active_tasks.load(std::memory_order::acquire); } else { @@ -415,7 +415,7 @@ namespace glz private: /// The configuration options. - options m_opts{}; + options opts{}; /// The event loop epoll file descriptor. net::event_handle_t event_fd{net::create_event_poll()}; @@ -456,8 +456,8 @@ namespace glz void init() { - if (m_opts.execution_strategy == execution_strategy::process_tasks_on_thread_pool) { - m_thread_pool = std::make_unique(std::move(m_opts.pool)); + if (opts.execution_strategy == execution_strategy::process_tasks_on_thread_pool) { + m_thread_pool = std::make_unique(std::move(opts.pool)); } [[maybe_unused]] net::poll_event_t e{}; @@ -486,7 +486,7 @@ namespace glz ::kevent(event_fd, &e_timer, 1, nullptr, 0, nullptr); #endif - if (m_opts.thread_strategy == glz::thread_strategy::spawn) { + if (opts.thread_strategy == glz::thread_strategy::spawn) { m_io_thread = std::thread([this]() { process_events_dedicated_thread(); }); } // else manual mode, the user must call process_events. @@ -494,8 +494,8 @@ namespace glz void process_events_dedicated_thread() { - if (m_opts.on_io_thread_start_functor) { - m_opts.on_io_thread_start_functor(); + if (opts.on_io_thread_start_functor) { + opts.on_io_thread_start_functor(); } m_io_processing.exchange(true, std::memory_order::release); @@ -506,8 +506,8 @@ namespace glz } m_io_processing.exchange(false, std::memory_order::release); - if (m_opts.on_io_thread_stop_functor) { - m_opts.on_io_thread_stop_functor(); + if (opts.on_io_thread_stop_functor) { + opts.on_io_thread_stop_functor(); } } @@ -609,7 +609,7 @@ namespace glz // the thread switch required. If max_events == 1 this would be unnecessary. if (!m_handles_to_resume.empty()) { - if (m_opts.execution_strategy == execution_strategy::process_tasks_inline) { + if (opts.execution_strategy == execution_strategy::process_tasks_inline) { for (auto& handle : m_handles_to_resume) { handle.resume(); } diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 03858788a3..4f86ceef96 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -367,7 +367,9 @@ suite ring_buffer_test = [] { glz::sync_wait(glz::when_all(std::move(tasks))); }; -suite io_scheduler_test = [] { +suite server_client_test = [] { + std::cout << "\n\nServer/Client test:\n"; + auto scheduler = std::make_shared(glz::scheduler::options{ // The scheduler will spawn a dedicated event processing thread. This is the default, but // it is possible to use 'manual' and call 'process_events()' to drive the scheduler yourself. @@ -469,7 +471,7 @@ suite io_scheduler_test = [] { co_return; }; - [[maybe_unused]] auto make_client_task = [&]() -> glz::task { + auto make_client_task = [&]() -> glz::task { // Immediately schedule onto the scheduler. co_await scheduler->schedule(); @@ -500,8 +502,7 @@ suite io_scheduler_test = [] { }; // Create and wait for the server and client tasks to complete. - //glz::sync_wait(glz::when_all(make_server_task(), make_client_task())); - glz::sync_wait(glz::when_all(make_server_task())); + glz::sync_wait(glz::when_all(make_server_task(), make_client_task())); }; int main() From f865f7f4141ee508fedfd9ceec165dc981969de4 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 10:40:31 -0500 Subject: [PATCH 270/309] Update client.hpp --- include/glaze/network/client.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 9f601258f3..18a877d1b7 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -54,7 +54,7 @@ namespace glz co_return return_value(ip_status::connected); } else if (result == -1) { - // If the connect is happening in the background poll for write on the socket to trigger + // If the connect is happening in the background, poll for write on the socket to trigger // when the connection is established. if (errno == EAGAIN || errno == EINPROGRESS) { auto pstatus = co_await scheduler->poll(socket.socket_fd, poll_op::write, timeout); From 6ede79655d86bc464ff748ffe3447b150f249c2c Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 10:46:41 -0500 Subject: [PATCH 271/309] updates --- include/glaze/network/client.hpp | 5 +++++ include/glaze/network/ip.hpp | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 18a877d1b7..810fda937f 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -48,6 +48,10 @@ namespace glz server_addr.sin_family = glz::net::asize_t(ipv); server_addr.sin_port = htons(port); ::inet_pton(glz::net::asize_t(ipv), address.c_str(), &server_addr.sin_addr); + + if (socket.socket_fd == net::invalid_socket) { + co_return return_value(ip_status::invalid_socket); + } auto result = ::connect(socket.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)); if (result == 0) { @@ -82,6 +86,7 @@ namespace glz } } + std::cerr << "connect: " << get_socket_error_message(errno) << '\n'; co_return return_value(ip_status::error); } diff --git a/include/glaze/network/ip.hpp b/include/glaze/network/ip.hpp index ad42f5f16e..cbb54dc1d3 100644 --- a/include/glaze/network/ip.hpp +++ b/include/glaze/network/ip.hpp @@ -47,7 +47,8 @@ namespace glz error, // try_again, // would_block, // - bad_file_descriptor // + bad_file_descriptor, // + invalid_socket, // ); inline ip_status errno_to_ip_status() noexcept From 0ae583302105a7bafc25eba5c7a565d568f636c4 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 10:52:48 -0500 Subject: [PATCH 272/309] make_socket --- include/glaze/network/client.hpp | 16 ++++++++-------- include/glaze/network/socket.hpp | 7 +++++++ tests/coroutine_test/coroutine_test.cpp | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 810fda937f..b83098632c 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -20,7 +20,7 @@ namespace glz std::string address{"127.0.0.1"}; uint16_t port{8080}; ip_version ipv{}; - glz::socket socket{}; + std::shared_ptr socket = make_socket(); /// Cache the status of the connect in the event the user calls connect() again. glz::ip_status connect_status{}; @@ -49,11 +49,11 @@ namespace glz server_addr.sin_port = htons(port); ::inet_pton(glz::net::asize_t(ipv), address.c_str(), &server_addr.sin_addr); - if (socket.socket_fd == net::invalid_socket) { + if (socket->socket_fd == net::invalid_socket) { co_return return_value(ip_status::invalid_socket); } - auto result = ::connect(socket.socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)); + auto result = ::connect(socket->socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)); if (result == 0) { co_return return_value(ip_status::connected); } @@ -61,7 +61,7 @@ namespace glz // If the connect is happening in the background, poll for write on the socket to trigger // when the connection is established. if (errno == EAGAIN || errno == EINPROGRESS) { - auto pstatus = co_await scheduler->poll(socket.socket_fd, poll_op::write, timeout); + auto pstatus = co_await scheduler->poll(socket->socket_fd, poll_op::write, timeout); if (pstatus == poll_status::event) { result = 0; socklen_t result_length{sizeof(result)}; @@ -69,7 +69,7 @@ namespace glz #if defined(_WIN32) if (getsockopt(socket.socket_fd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&result), #else - if (getsockopt(socket.socket_fd, SOL_SOCKET, SO_ERROR, &result, + if (getsockopt(socket->socket_fd, SOL_SOCKET, SO_ERROR, &result, #endif &result_length) < 0) { @@ -100,7 +100,7 @@ namespace glz */ task poll(glz::poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { - return scheduler->poll(socket.socket_fd, op, timeout); + return scheduler->poll(socket->socket_fd, op, timeout); } /** @@ -118,7 +118,7 @@ namespace glz return {ip_status::ok, std::span{}}; } - auto bytes_recv = ::recv(socket.socket_fd, buffer.data(), glz::net::ssize_t(buffer.size()), 0); + auto bytes_recv = ::recv(socket->socket_fd, buffer.data(), glz::net::ssize_t(buffer.size()), 0); if (bytes_recv > 0) { // Ok, we've recieved some data. return {ip_status::ok, std::span{buffer.data(), size_t(bytes_recv)}}; @@ -150,7 +150,7 @@ namespace glz return {ip_status::ok, std::string_view{buffer.data(), buffer.size()}}; } - auto bytes_sent = ::send(socket.socket_fd, buffer.data(), glz::net::ssize_t(buffer.size()), 0); + auto bytes_sent = ::send(socket->socket_fd, buffer.data(), glz::net::ssize_t(buffer.size()), 0); if (bytes_sent >= 0) { // Some or all of the bytes were written. return {ip_status::ok, std::string_view{buffer.data() + bytes_sent, buffer.size() - bytes_sent}}; diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index d2403b91aa..afe5ce7a11 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -137,6 +137,13 @@ namespace glz return {}; } + [[nodiscard]] inline std::shared_ptr make_socket() + { + auto sock = std::make_shared(); + sock->socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + return sock; + } + [[nodiscard]] inline std::shared_ptr make_accept_socket(const std::string& address, int port) { auto sock = std::make_shared(); diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 4f86ceef96..e3c6e97f36 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -413,7 +413,7 @@ suite server_client_test = [] { auto client = server.accept(); // Verify the incoming connection was accepted correctly. - if (!client.socket.valid()) { + if (not client.socket->valid()) { co_return; // Handle error. } From e2ab50f9c9befe0db728534181f32f97b12a474d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 10:55:30 -0500 Subject: [PATCH 273/309] updates --- include/glaze/network/client.hpp | 2 +- include/glaze/network/socket.hpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index b83098632c..0962df2a34 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -20,7 +20,7 @@ namespace glz std::string address{"127.0.0.1"}; uint16_t port{8080}; ip_version ipv{}; - std::shared_ptr socket = make_socket(); + std::shared_ptr socket = make_aysync_socket(); /// Cache the status of the connect in the event the user calls connect() again. glz::ip_status connect_status{}; diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index afe5ce7a11..6119866348 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -137,10 +137,11 @@ namespace glz return {}; } - [[nodiscard]] inline std::shared_ptr make_socket() + [[nodiscard]] inline std::shared_ptr make_aysync_socket() { auto sock = std::make_shared(); sock->socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); + set_non_blocking(*sock); return sock; } From c6efc85b9f51679a7e29fd308b09548317f8df5d Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 10:59:59 -0500 Subject: [PATCH 274/309] cleaning --- include/glaze/coroutine/thread_pool.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/glaze/coroutine/thread_pool.hpp b/include/glaze/coroutine/thread_pool.hpp index 1b48d064b8..ba183084b2 100644 --- a/include/glaze/coroutine/thread_pool.hpp +++ b/include/glaze/coroutine/thread_pool.hpp @@ -231,7 +231,7 @@ namespace glz * FIFO task queue. This function is useful to yielding long processing tasks to let other tasks * get processing time. */ - [[nodiscard]] auto yield() -> operation { return schedule(); } + [[nodiscard]] operation yield() { return schedule(); } /** * Shutsdown the thread pool. This will finish any tasks scheduled prior to calling this @@ -260,17 +260,17 @@ namespace glz /** * @return The number of tasks waiting in the task queue + the executing tasks. */ - auto size() const noexcept -> size_t { return m_size.load(std::memory_order::acquire); } + size_t size() const noexcept { return m_size.load(std::memory_order::acquire); } /** * @return True if the task queue is empty and zero tasks are currently executing. */ - auto empty() const noexcept -> bool { return size() == 0; } + bool empty() const noexcept { return size() == 0; } /** * @return The number of tasks waiting in the task queue to be executed. */ - auto queue_size() const noexcept -> size_t + size_t queue_size() const noexcept { std::atomic_thread_fence(std::memory_order::acquire); return m_queue.size(); @@ -279,7 +279,7 @@ namespace glz /** * @return True if the task queue is currently empty. */ - auto queue_empty() const noexcept -> bool { return queue_size() == 0; } + bool queue_empty() const noexcept { return queue_size() == 0; } private: /// The configuration options. From 3cf86ed3194acbc09b412a238fe3e4e6c464bc57 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 11:13:01 -0500 Subject: [PATCH 275/309] error checking --- include/glaze/coroutine/poll.hpp | 50 ++----------------------- tests/coroutine_test/coroutine_test.cpp | 8 +++- 2 files changed, 10 insertions(+), 48 deletions(-) diff --git a/include/glaze/coroutine/poll.hpp b/include/glaze/coroutine/poll.hpp index 2e42babd89..58b3026506 100644 --- a/include/glaze/coroutine/poll.hpp +++ b/include/glaze/coroutine/poll.hpp @@ -8,53 +8,11 @@ #include #include "glaze/network/core.hpp" +#include "glaze/reflection/enum_macro.hpp" namespace glz { - enum struct poll_op : uint32_t { - read, - write, - read_write - }; - - std::string to_string(poll_op op) - { - switch (op) { - case poll_op::read: - return "read"; - case poll_op::write: - return "write"; - case poll_op::read_write: - return "read_write"; - default: - return "unknown"; - } - } - - enum struct poll_status { - /// The poll operation was was successful. - event, - /// The poll operation timed out. - timeout, - /// The file descriptor had an error while polling. - error, - /// The file descriptor has been closed by the remote or an internal error/close. - closed - }; - - std::string to_string(poll_status status) - { - switch (status) { - case poll_status::event: - return "event"; - case poll_status::timeout: - return "timeout"; - case poll_status::error: - return "error"; - case poll_status::closed: - return "closed"; - default: - return "unknown"; - } - } + GLZ_ENUM(poll_op, read, write, read_write); + + GLZ_ENUM(poll_status, event, timeout, error, closed); } diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index e3c6e97f36..f5040663f4 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -483,10 +483,14 @@ suite server_client_test = [] { // verify the number of bytes sent or received. // Connect to the server. - co_await client.connect(); + if (auto ip_status = co_await client.connect(std::chrono::milliseconds(100)); ip_status != glz::ip_status::ok) { + std::cerr << "ip_status: " << glz::nameof(ip_status) << '\n'; + } // Make sure the client socket can be written to. - co_await client.poll(glz::poll_op::write); + if (auto status = co_await client.poll(glz::poll_op::write); bool(status)) { + std::cerr << "poll_status: " << glz::nameof(status) << '\n'; + } // Send the request data. client.send(std::string_view{"Hello from client."}); From 703f74b04cac44c3ec77c39c22f44107cfcee7c9 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 11:16:52 -0500 Subject: [PATCH 276/309] Update server.hpp --- include/glaze/network/server.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index bdb0a34912..f7faaf219c 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -13,7 +13,7 @@ namespace glz struct server { std::shared_ptr scheduler{}; - std::string address{"0.0.0.0"}; + std::string address{"127.0.0.1"}; uint16_t port{8080}; int32_t backlog{128}; // The kernel backlog of connections to buffer. /// The socket for accepting new tcp connections on. From 6937aea2e1999ef76c955148ff2734cf0b34809e Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 11:29:24 -0500 Subject: [PATCH 277/309] proper op handling --- include/glaze/coroutine/scheduler.hpp | 55 +++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index d57cae8d48..fd3761cf4c 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -284,15 +284,62 @@ namespace glz #if defined(__linux__) net::poll_event_t e{}; - e.events = uint32_t(op) | EPOLLONESHOT | EPOLLRDHUP; + + switch (op) + { + case poll_op::read: { + e.events = EPOLLIN | EPOLLONESHOT | EPOLLRDHUP; + break; + } + case poll_op::write: { + e.events = EPOLLOUT | EPOLLONESHOT | EPOLLRDHUP; + break; + } + case poll_op::read_write: { + e.events = EPOLLIN | EPOLLOUT | EPOLLONESHOT | EPOLLRDHUP; + break; + } + default { + break; + } + } + e.data.ptr = &poll_info; if (epoll_ctl(event_fd, EPOLL_CTL_ADD, fd, &e) == -1) { std::cerr << "epoll ctl error on fd " << fd << "\n"; } #elif defined(__APPLE__) - net::poll_event_t e{.filter = EVFILT_READ, .flags = EV_ADD | EV_EOF, .udata = &poll_info}; - if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { - std::cerr << "kqueue failed to register for file_descriptor: " << fd << "\n"; + if (op == poll_op::read_write) { + struct kevent events[2]; + + EV_SET(&events[0], fd, EVFILT_READ, EV_ADD | EV_EOF, 0, 0, &poll_info); + EV_SET(&events[1], fd, EVFILT_WRITE, EV_ADD | EV_EOF, 0, 0, &poll_info); + + if (kevent(event_fd, events, 2, nullptr, 0, nullptr) == -1) { + std::cerr << "kqueue failed to register read/write for file_descriptor: " << fd << "\n"; + } + } + else { + net::poll_event_t e{.flags = EV_ADD | EV_EOF, .udata = &poll_info}; + + switch (op) + { + case poll_op::read: { + e.filter = EVFILT_READ; + break; + } + case poll_op::write: { + e.filter = EVFILT_WRITE; + break; + } + default: { + break; + } + } + + if (::kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { + std::cerr << "kqueue failed to register for file_descriptor: " << fd << "\n"; + } } #elif defined(_WIN32) From a8a84f7a404e4b6c78e21471ccbd78f25e5cc219 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 11:42:48 -0500 Subject: [PATCH 278/309] rename --- include/glaze/network/client.hpp | 2 +- include/glaze/network/socket.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 0962df2a34..ddc2210c28 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -20,7 +20,7 @@ namespace glz std::string address{"127.0.0.1"}; uint16_t port{8080}; ip_version ipv{}; - std::shared_ptr socket = make_aysync_socket(); + std::shared_ptr socket = make_async_socket(); /// Cache the status of the connect in the event the user calls connect() again. glz::ip_status connect_status{}; diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index 6119866348..aa069a630a 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -137,7 +137,7 @@ namespace glz return {}; } - [[nodiscard]] inline std::shared_ptr make_aysync_socket() + [[nodiscard]] inline std::shared_ptr make_async_socket() { auto sock = std::make_shared(); sock->socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); From 013c51b87b779cc350676eb26d7c853e5b9a2b62 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 11:56:52 -0500 Subject: [PATCH 279/309] removing fd from poll --- include/glaze/coroutine/poll_info.hpp | 3 ++ include/glaze/coroutine/scheduler.hpp | 51 ++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/include/glaze/coroutine/poll_info.hpp b/include/glaze/coroutine/poll_info.hpp index 59587f4250..ccc1226dfa 100644 --- a/include/glaze/coroutine/poll_info.hpp +++ b/include/glaze/coroutine/poll_info.hpp @@ -72,5 +72,8 @@ namespace glz /// Did the timeout and event trigger at the same time on the same epoll_wait call? /// Once this is set to true all future events on this poll info are null and void. bool m_processed{false}; + + /// The operation for deleting on Mac + glz::poll_op op{}; }; } diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index fd3761cf4c..4868a40492 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -277,6 +277,7 @@ namespace glz glz::poll_info poll_info{}; poll_info.m_fd = fd; + poll_info.op = op; if (timeout_requested) { poll_info.m_timer_pos = add_timer_token(clock::now() + timeout, poll_info); @@ -742,7 +743,55 @@ namespace glz // Given a valid fd always remove it from epoll so the next poll can blindly EPOLL_CTL_ADD. if (poll_info->m_fd != net::invalid_event_handle) { -#if defined(__linux__) +#if defined(__APPLE__) + if (poll_info->op == poll_op::read_write) { + struct kevent event; + EV_SET(&event, poll_info->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + + if (kevent(event_fd, &event, 1, nullptr, 0, nullptr) == -1) { + // It's often okay if this fails, as the descriptor might not have been registered + // But you might want to log it for debugging purposes + // std::cerr << "kqueue failed to delete read event for fd: " << poll_info->m_fd << "\n"; + } + + EV_SET(&event, poll_info->m_fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); + + if (kevent(event_fd, &event, 1, nullptr, 0, nullptr) == -1) { + // Again, failure here is often okay + // std::cerr << "kqueue failed to delete write event for fd: " << poll_info->m_fd << "\n"; + } + } + else { + switch (poll_info->op) + { + case poll_op::read: { + struct kevent e; + EV_SET(&e, poll_info->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + + if (kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { + // It's often okay if this fails, as the descriptor might not have been registered + // But you might want to log it for debugging purposes + // std::cerr << "kqueue failed to delete read event for fd: " << poll_info->m_fd << "\n"; + } + + break; + } + case poll_op::write: { + struct kevent e; + EV_SET(&e, poll_info->m_fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); + + if (kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { + // Failure here is often okay + // std::cerr << "kqueue failed to delete write event for fd: " << poll_info->m_fd << "\n"; + } + break; + } + default: { + break; + } + } + } +#elif defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, poll_info->m_fd, nullptr); #endif } From 3c83362d40cceebcb90c282a05a34224306d2c83 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 12:00:40 -0500 Subject: [PATCH 280/309] Update scheduler.hpp --- include/glaze/coroutine/scheduler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index 4868a40492..38899d88ea 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -535,7 +535,7 @@ namespace glz #endif if (opts.thread_strategy == glz::thread_strategy::spawn) { - m_io_thread = std::thread([this]() { process_events_dedicated_thread(); }); + m_io_thread = std::thread([this] { process_events_dedicated_thread(); }); } // else manual mode, the user must call process_events. } From 5248ae2c9e2d84d8a461f995b362e85fd54ff1f1 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 12:13:22 -0500 Subject: [PATCH 281/309] Update scheduler.hpp --- include/glaze/coroutine/scheduler.hpp | 102 ++++++++++++++------------ 1 file changed, 55 insertions(+), 47 deletions(-) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index 38899d88ea..6a4f21c7bd 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -728,6 +728,59 @@ namespace glz static constexpr size_t max_events = 16; std::array m_events{}; std::vector> m_handles_to_resume{}; + + void apple_delete_poll_event(glz::poll_info* poll_info) + { +#if defined(__APPLE__) + if (poll_info->op == poll_op::read_write) { + struct kevent event; + EV_SET(&event, poll_info->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + + if (kevent(event_fd, &event, 1, nullptr, 0, nullptr) == -1) { + // It's often okay if this fails, as the descriptor might not have been registered + // But you might want to log it for debugging purposes + // std::cerr << "kqueue failed to delete read event for fd: " << poll_info->m_fd << "\n"; + } + + EV_SET(&event, poll_info->m_fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); + + if (kevent(event_fd, &event, 1, nullptr, 0, nullptr) == -1) { + // Again, failure here is often okay + // std::cerr << "kqueue failed to delete write event for fd: " << poll_info->m_fd << "\n"; + } + } + else { + switch (poll_info->op) + { + case poll_op::read: { + struct kevent e; + EV_SET(&e, poll_info->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); + + if (kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { + // It's often okay if this fails, as the descriptor might not have been registered + // But you might want to log it for debugging purposes + // std::cerr << "kqueue failed to delete read event for fd: " << poll_info->m_fd << "\n"; + } + + break; + } + case poll_op::write: { + struct kevent e; + EV_SET(&e, poll_info->m_fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); + + if (kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { + // Failure here is often okay + // std::cerr << "kqueue failed to delete write event for fd: " << poll_info->m_fd << "\n"; + } + break; + } + default: { + break; + } + } + } +#endif + } void process_event_execute(glz::poll_info* poll_info, poll_status status) { @@ -744,53 +797,7 @@ namespace glz // Given a valid fd always remove it from epoll so the next poll can blindly EPOLL_CTL_ADD. if (poll_info->m_fd != net::invalid_event_handle) { #if defined(__APPLE__) - if (poll_info->op == poll_op::read_write) { - struct kevent event; - EV_SET(&event, poll_info->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); - - if (kevent(event_fd, &event, 1, nullptr, 0, nullptr) == -1) { - // It's often okay if this fails, as the descriptor might not have been registered - // But you might want to log it for debugging purposes - // std::cerr << "kqueue failed to delete read event for fd: " << poll_info->m_fd << "\n"; - } - - EV_SET(&event, poll_info->m_fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); - - if (kevent(event_fd, &event, 1, nullptr, 0, nullptr) == -1) { - // Again, failure here is often okay - // std::cerr << "kqueue failed to delete write event for fd: " << poll_info->m_fd << "\n"; - } - } - else { - switch (poll_info->op) - { - case poll_op::read: { - struct kevent e; - EV_SET(&e, poll_info->m_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); - - if (kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { - // It's often okay if this fails, as the descriptor might not have been registered - // But you might want to log it for debugging purposes - // std::cerr << "kqueue failed to delete read event for fd: " << poll_info->m_fd << "\n"; - } - - break; - } - case poll_op::write: { - struct kevent e; - EV_SET(&e, poll_info->m_fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); - - if (kevent(event_fd, &e, 1, nullptr, 0, nullptr) == -1) { - // Failure here is often okay - // std::cerr << "kqueue failed to delete write event for fd: " << poll_info->m_fd << "\n"; - } - break; - } - default: { - break; - } - } - } + apple_delete_poll_event(poll_info); #elif defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, poll_info->m_fd, nullptr); #endif @@ -843,6 +850,7 @@ namespace glz #if defined(__linux__) epoll_ctl(event_fd, EPOLL_CTL_DEL, pi->m_fd, nullptr); #elif defined(__APPLE__) + apple_delete_poll_event(pi); #endif } From f78f614cbd95c766be78ca55fad9c526acd67fac Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 12:14:06 -0500 Subject: [PATCH 282/309] Update scheduler.hpp --- include/glaze/coroutine/scheduler.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index 6a4f21c7bd..034c13b6df 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -779,6 +779,8 @@ namespace glz } } } +#else + (void)poll_info; #endif } From 13b7039251c96ed34330a8ed45b44c0d32f3fa93 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Wed, 3 Jul 2024 13:10:17 -0500 Subject: [PATCH 283/309] Corrections for Linux implementation. --- include/glaze/coroutine/scheduler.hpp | 4 ++-- include/glaze/network/server.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index 034c13b6df..69dec3d269 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -73,7 +73,7 @@ namespace glz /// If inline task processing is enabled then the io worker will resume tasks on its thread /// rather than scheduling them to be picked up by the thread pool. - const execution_strategy execution_strategy{execution_strategy::process_tasks_on_thread_pool}; + const glz::scheduler::execution_strategy execution_strategy{glz::scheduler::execution_strategy::process_tasks_on_thread_pool}; }; scheduler() { init(); } @@ -300,7 +300,7 @@ namespace glz e.events = EPOLLIN | EPOLLOUT | EPOLLONESHOT | EPOLLRDHUP; break; } - default { + default: { break; } } diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index f7faaf219c..87bfd390ba 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -12,7 +12,7 @@ namespace glz { struct server { - std::shared_ptr scheduler{}; + std::shared_ptr scheduler{}; std::string address{"127.0.0.1"}; uint16_t port{8080}; int32_t backlog{128}; // The kernel backlog of connections to buffer. From a10b635916dc4d53ac4653b55a431d8ea22846fe Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 13:15:11 -0500 Subject: [PATCH 284/309] oneshot for mac poll events --- include/glaze/coroutine/scheduler.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index 69dec3d269..f85adda7fe 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -313,15 +313,15 @@ namespace glz if (op == poll_op::read_write) { struct kevent events[2]; - EV_SET(&events[0], fd, EVFILT_READ, EV_ADD | EV_EOF, 0, 0, &poll_info); - EV_SET(&events[1], fd, EVFILT_WRITE, EV_ADD | EV_EOF, 0, 0, &poll_info); + EV_SET(&events[0], fd, EVFILT_READ, EV_ADD | EV_EOF | EV_ONESHOT, 0, 0, &poll_info); + EV_SET(&events[1], fd, EVFILT_WRITE, EV_ADD | EV_EOF | EV_ONESHOT, 0, 0, &poll_info); if (kevent(event_fd, events, 2, nullptr, 0, nullptr) == -1) { std::cerr << "kqueue failed to register read/write for file_descriptor: " << fd << "\n"; } } else { - net::poll_event_t e{.flags = EV_ADD | EV_EOF, .udata = &poll_info}; + net::poll_event_t e{.flags = EV_ADD | EV_EOF | EV_ONESHOT, .udata = &poll_info}; switch (op) { From 525ba5ca919e3dc2955312056f18eba9a0e17e34 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 3 Jul 2024 13:22:57 -0500 Subject: [PATCH 285/309] Update scheduler.hpp --- include/glaze/coroutine/scheduler.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index f85adda7fe..771729ac36 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -608,10 +608,10 @@ namespace glz #if defined(__linux__) || defined(__APPLE__) - if (event_count == -1) { - net::close_event(event_fd); - GLZ_THROW_OR_ABORT(std::runtime_error{"wait for event failed"}); - } + //if (event_count == -1) { + // net::close_event(event_fd); + // GLZ_THROW_OR_ABORT(std::runtime_error{"wait for event failed"}); + //} if (event_count > 0) { for (size_t i = 0; i < size_t(event_count); ++i) { From 3514f92372c553a93cf0fd25544c1d0ca123ab2e Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Wed, 3 Jul 2024 14:39:50 -0500 Subject: [PATCH 286/309] Added debug code and removed socket creation duplication --- include/glaze/coroutine/scheduler.hpp | 8 ++++---- include/glaze/network/client.hpp | 9 +++++++++ include/glaze/network/socket.hpp | 2 -- tests/coroutine_test/coroutine_test.cpp | 2 ++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/include/glaze/coroutine/scheduler.hpp b/include/glaze/coroutine/scheduler.hpp index 69dec3d269..3353c37b64 100644 --- a/include/glaze/coroutine/scheduler.hpp +++ b/include/glaze/coroutine/scheduler.hpp @@ -608,10 +608,10 @@ namespace glz #if defined(__linux__) || defined(__APPLE__) - if (event_count == -1) { - net::close_event(event_fd); - GLZ_THROW_OR_ABORT(std::runtime_error{"wait for event failed"}); - } + //if (event_count == -1) { + // net::close_event(event_fd); + // GLZ_THROW_OR_ABORT(std::runtime_error{"wait for event failed"}); + //} if (event_count > 0) { for (size_t i = 0; i < size_t(event_count); ++i) { diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index ddc2210c28..5056d88c19 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -150,6 +150,15 @@ namespace glz return {ip_status::ok, std::string_view{buffer.data(), buffer.size()}}; } + int error = 0; + socklen_t len = sizeof(error); + int result = getsockopt(socket->socket_fd, SOL_SOCKET, SO_ERROR, (char*)&error, &len); + if (result == int(ip_status::error)) { + auto err = get_socket_error_message(errno); + std::cerr << err; + return {ip_status::invalid_socket, err}; + } + auto bytes_sent = ::send(socket->socket_fd, buffer.data(), glz::net::ssize_t(buffer.size()), 0); if (bytes_sent >= 0) { // Some or all of the bytes were written. diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index aa069a630a..f8fc745235 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -95,7 +95,6 @@ namespace glz [[nodiscard]] inline std::error_code bind_and_listen(socket& sock, const std::string& address, int port) { - sock.socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); set_non_blocking(sock); if (sock.socket_fd == net::invalid_socket) { return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; @@ -129,7 +128,6 @@ namespace glz return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; } - set_non_blocking(sock); if (not sock.no_delay()) { return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; } diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index f5040663f4..505ea2bde8 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -10,6 +10,7 @@ using namespace ut; +#ifdef TEST_ALL suite generator = [] { std::atomic result{}; auto task = [&](uint64_t count_to) -> glz::task { @@ -366,6 +367,7 @@ suite ring_buffer_test = [] { // Wait for all the values to be produced and consumed through the ring buffer. glz::sync_wait(glz::when_all(std::move(tasks))); }; +#endif suite server_client_test = [] { std::cout << "\n\nServer/Client test:\n"; From 53295cc8131c95d97b99effb57dbb6def84fe3cf Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Wed, 3 Jul 2024 16:36:09 -0500 Subject: [PATCH 287/309] Correct ip errors and added debugging outputs; this still is not working. --- include/glaze/network/client.hpp | 21 +++++++++++----- include/glaze/network/server.hpp | 43 ++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 5056d88c19..0fd63c559a 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -44,22 +44,31 @@ namespace glz return s; }; - sockaddr_in server_addr; - server_addr.sin_family = glz::net::asize_t(ipv); - server_addr.sin_port = htons(port); - ::inet_pton(glz::net::asize_t(ipv), address.c_str(), &server_addr.sin_addr); - if (socket->socket_fd == net::invalid_socket) { co_return return_value(ip_status::invalid_socket); } + sockaddr_in server_addr{}; + server_addr.sin_family = AF_INET; // Use AF_INET for IPv4 + server_addr.sin_port = htons(port); + + if (::inet_pton(AF_INET, address.c_str(), &server_addr.sin_addr) <= 0) { + std::cerr << "Invalid address/ Address not supported: " << inet_ntoa(server_addr.sin_addr) << ":" << ntohs(server_addr.sin_port) << '\n'; + co_return return_value(ip_status::invalid_ip_address); + } + + std::cout << "Attempting client connection to: " << inet_ntoa(server_addr.sin_addr) << ":" << ntohs(server_addr.sin_port) << '\n'; auto result = ::connect(socket->socket_fd, (sockaddr*)&server_addr, sizeof(server_addr)); if (result == 0) { + std::cout << "Client connected to: " << inet_ntoa(server_addr.sin_addr) << ":" << ntohs(server_addr.sin_port) << '\n'; co_return return_value(ip_status::connected); } else if (result == -1) { + // // If the connect is happening in the background, poll for write on the socket to trigger // when the connection is established. + // + std::cout << "Connection failed, polling for connection: " << inet_ntoa(server_addr.sin_addr) << ":" << ntohs(server_addr.sin_port) << '\n'; if (errno == EAGAIN || errno == EINPROGRESS) { auto pstatus = co_await scheduler->poll(socket->socket_fd, poll_op::write, timeout); if (pstatus == poll_status::event) { @@ -158,7 +167,7 @@ namespace glz std::cerr << err; return {ip_status::invalid_socket, err}; } - + auto bytes_sent = ::send(socket->socket_fd, buffer.data(), glz::net::ssize_t(buffer.size()), 0); if (bytes_sent >= 0) { // Some or all of the bytes were written. diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index 87bfd390ba..8c636fb27f 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -37,17 +37,40 @@ namespace glz */ client accept() { - sockaddr_in client{}; + sockaddr_in client_addr{}; + client_addr.sin_family = AF_INET; // Use AF_INET for IPv4 + client_addr.sin_port = htons(port); + + if (::inet_pton(AF_INET, address.c_str(), &client_addr.sin_addr) <= 0) { + std::cerr << "Invalid address/ Address not supported: " << inet_ntoa(client_addr.sin_addr) << ":" + << ntohs(client_addr.sin_port) << '\n'; + return {}; + } + + std::ostringstream addr; + addr << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << '\n'; + std::cout << "Accepting incoming client connection to: " << addr.str(); + constexpr int len = sizeof(struct sockaddr_in); - socket sock{::accept(accept_socket->socket_fd, (struct sockaddr*)(&client), - const_cast((const socklen_t*)(&len)))}; - - std::string_view ip_addr_view{ - (char*)(&client.sin_addr.s_addr), - sizeof(client.sin_addr.s_addr), - }; - - return {scheduler, binary_to_ip_string(ip_addr_view).value(), ntohs(client.sin_port), ip_version(client.sin_family)}; + + auto new_client_id = ::accept(accept_socket->socket_fd, (struct sockaddr*)(&client_addr), + const_cast((const socklen_t*)(&len))); + + if (new_client_id < 0) { + std::cerr << "Client rejected from " << addr.str(); + // + // TODO: Handle Error + // + return {}; + } + socket sock{new_client_id}; + + std::cout << "Client Id, " << new_client_id << ", " << "accepted on " << addr.str(); + + std::string_view ip_addr_view{addr.str()}; + + return {scheduler, binary_to_ip_string(ip_addr_view).value(), ntohs(client_addr.sin_port), + ip_version(client_addr.sin_family)}; } }; } From 4be32975343d579bbac12a968cdc040e42653a66 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Fri, 5 Jul 2024 09:57:05 -0500 Subject: [PATCH 288/309] Renamed binary_to_ip_string to 'to_ip_string'; added support for IPV4 and IPV6 conversions; added support for to return ip's of human readable socke addresses (i.e., '127.0.0.1:8080', etc.). Update fixes 'inet_ntop' memory exception. --- include/glaze/network/ip.hpp | 179 ++++++++++++++++++++----------- include/glaze/network/server.hpp | 20 ++-- 2 files changed, 128 insertions(+), 71 deletions(-) diff --git a/include/glaze/network/ip.hpp b/include/glaze/network/ip.hpp index cbb54dc1d3..90159f7e5d 100644 --- a/include/glaze/network/ip.hpp +++ b/include/glaze/network/ip.hpp @@ -18,62 +18,115 @@ #include "glaze/reflection/enum_macro.hpp" -namespace glz -{ +namespace glz { enum struct ip_version : int { ipv4 = AF_INET, ipv6 = AF_INET6 }; - inline std::optional binary_to_ip_string(const std::string_view binary_address, - ip_version ipv = ip_version::ipv6) - { - std::string output{}; - output.resize(16); - - auto success = ::inet_ntop(int(ipv), binary_address.data(), output.data(), uint32_t(output.size())); - if (success) { - output.resize(::strnlen(success, output.length())); - return {output}; - } - return {}; - } - - GLZ_ENUM(ip_status, // - unset, // - ok, // - closed, // - connected, // - connection_refused, // - invalid_ip_address, // - timeout, // - error, // - try_again, // - would_block, // - bad_file_descriptor, // - invalid_socket, // + GLZ_ENUM(ip_status, + unset, + ok, + closed, + connected, + connection_refused, + invalid_ip_address, + timeout, + error, + try_again, + would_block, + bad_file_descriptor, + invalid_socket ); - - inline ip_status errno_to_ip_status() noexcept - { + + inline ip_status errno_to_ip_status() noexcept { #if defined(__linux__) || defined(__APPLE__) const auto err = errno; using enum ip_status; switch (err) { - case 0: { - return ok; - } - case -1: { - return closed; + case 0: + return ok; + case -1: + return closed; + case EWOULDBLOCK: + return would_block; + case ECONNREFUSED: + return connection_refused; + default: + return error; + } +#endif + } + + // Get's human readable ip if binary; returns ip if full socket address (ip::port); handles IPV4 and + // IPV6 formats. + // + inline std::optional to_ip_string(std::string_view input, ip_version ipv = ip_version::ipv6) { + if (input.empty()) return std::nullopt; + + // Remove trailing whitespace...here because input contained a '\n'... + while (!input.empty() && std::isspace(static_cast(input.back()))) { + input.remove_suffix(1); + } + + size_t buffer_size = (ipv == ip_version::ipv4) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN; + std::string output(buffer_size, '\0'); + + // Handle human-readable IP addresses (with or without port) + std::string_view ip_part = input; + if (input.find_first_not_of("0123456789.:abcdefABCDEF[]") == std::string_view::npos) { + // Remove port if present + size_t colon_pos = input.rfind(':'); + size_t bracket_pos = input.rfind(']'); + if (colon_pos != std::string_view::npos && + (bracket_pos == std::string_view::npos || colon_pos > bracket_pos)) { + ip_part = input.substr(0, colon_pos); } - case EWOULDBLOCK: { - return would_block; + + // Remove brackets for IPv6 addresses + if (ip_part.front() == '[' && ip_part.back() == ']') { + ip_part = ip_part.substr(1, ip_part.size() - 2); } - case ECONNREFUSED: { - return connection_refused; + + // Try to convert the string representation + int family = (ip_part.find(':') != std::string_view::npos) ? AF_INET6 : AF_INET; + unsigned char buf[sizeof(struct in6_addr)]; + if (inet_pton(family, std::string(ip_part).c_str(), buf) == 1) { + if (inet_ntop(family, buf, output.data(), output.size())) { + output.resize(std::strlen(output.c_str())); + return output; + } } - default: { - return error; + } + + // Handle binary IP addresses here + const void* addr_ptr; + int family; + if (input.size() == sizeof(struct in_addr)) { + addr_ptr = input.data(); + family = AF_INET; + } else if (input.size() == sizeof(struct in6_addr)) { + addr_ptr = input.data(); + family = AF_INET6; + } else { + return std::nullopt; // Invalid input size for binary IP + } + + if (inet_ntop(family, addr_ptr, output.data(), output.size())) { + output.resize(std::strlen(output.c_str())); + return output; + } else { + // TODO: Implement Error handling for cross-platform portability. + int error_code = errno; + switch (error_code) { + case EAFNOSUPPORT: + std::cerr << "Address family not supported for socket address: " << input << '\n'; + break; + case ENOSPC: + std::cerr << "Insufficient space in output buffer for socket address: " << input << '\n'; + break; + default: + std::cerr << "Unexpected 'inet_ntop' error converting socket address: " << input << '\n'; } } -#endif + return std::nullopt; } /* @@ -84,7 +137,7 @@ namespace glz /// The udp socket has not been bind()'ed to a local port. udp_not_bound = -2, try_again = EAGAIN, - // Note: that only the tcp::client will return this, a tls::client returns the specific ssl_would_block_* status'. + // Note: that only the tcp::client will return this, a tls::client returns the specific ssl_would_block_* status. would_block = EWOULDBLOCK, bad_file_descriptor = EBADF, connection_refused = ECONNREFUSED, @@ -99,23 +152,23 @@ namespace glz /* // ip_status ok = 0, - closed = -1, - permission_denied = EACCES, - try_again = EAGAIN, - would_block = EWOULDBLOCK, - already_in_progress = EALREADY, - bad_file_descriptor = EBADF, - connection_reset = ECONNRESET, - no_peer_address = EDESTADDRREQ, - memory_fault = EFAULT, - interrupted = EINTR, - is_connection = EISCONN, - message_size = EMSGSIZE, - output_queue_full = ENOBUFS, - no_memory = ENOMEM, - not_connected = ENOTCONN, - not_a_socket = ENOTSOCK, - operationg_not_supported = EOPNOTSUPP, - pipe_closed = EPIPE, + closed = -1, + permission_denied = EACCES, + try_again = EAGAIN, + would_block = EWOULDBLOCK, + already_in_progress = EALREADY, + bad_file_descriptor = EBADF, + connection_reset = ECONNRESET, + no_peer_address = EDESTADDRREQ, + memory_fault = EFAULT, + interrupted = EINTR, + is_connection = EISCONN, + message_size = EMSGSIZE, + output_queue_full = ENOBUFS, + no_memory = ENOMEM, + not_connected = ENOTCONN, + not_a_socket = ENOTSOCK, + operation_not_supported = EOPNOTSUPP, + pipe_closed = EPIPE, */ } diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index 8c636fb27f..2246d8746b 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -47,9 +47,9 @@ namespace glz return {}; } - std::ostringstream addr; - addr << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << '\n'; - std::cout << "Accepting incoming client connection to: " << addr.str(); + std::ostringstream sockaddr; + sockaddr << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << '\n'; + std::cout << "Accepting incoming client connection to: " << sockaddr.str(); constexpr int len = sizeof(struct sockaddr_in); @@ -57,7 +57,7 @@ namespace glz const_cast((const socklen_t*)(&len))); if (new_client_id < 0) { - std::cerr << "Client rejected from " << addr.str(); + std::cerr << "Client rejected from " << sockaddr.str(); // // TODO: Handle Error // @@ -65,12 +65,16 @@ namespace glz } socket sock{new_client_id}; - std::cout << "Client Id, " << new_client_id << ", " << "accepted on " << addr.str(); + std::cout << "New Client Id, " << new_client_id << ", " << "Accepted On " << sockaddr.str(); - std::string_view ip_addr_view{addr.str()}; + std::string_view ip_addr_view{sockaddr.str()}; - return {scheduler, binary_to_ip_string(ip_addr_view).value(), ntohs(client_addr.sin_port), - ip_version(client_addr.sin_family)}; + // clang-format off + return { .scheduler = scheduler, + .address = to_ip_string(ip_addr_view).value(), + .port = ntohs(client_addr.sin_port), + .ipv = ip_version(client_addr.sin_family)}; + // clang-format on } }; } From a263c5810a4de75e7faeb46e1976c087db50df10 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Fri, 5 Jul 2024 10:20:06 -0500 Subject: [PATCH 289/309] Added debug messaging output when waiting for client messages via 'client.poll'. --- tests/coroutine_test/coroutine_test.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 505ea2bde8..a3a2577948 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -4,8 +4,8 @@ #define UT_RUN_TIME_ONLY #include "glaze/coroutine.hpp" -#include "glaze/network.hpp" +#include "glaze/network.hpp" #include "ut/ut.hpp" using namespace ut; @@ -371,7 +371,7 @@ suite ring_buffer_test = [] { suite server_client_test = [] { std::cout << "\n\nServer/Client test:\n"; - + auto scheduler = std::make_shared(glz::scheduler::options{ // The scheduler will spawn a dedicated event processing thread. This is the default, but // it is possible to use 'manual' and call 'process_events()' to drive the scheduler yourself. @@ -386,7 +386,7 @@ suite server_client_test = [] { // You can use an execution strategy of `process_tasks_inline` to have the event loop thread // directly process the tasks, this might be desirable for small tasks vs a thread pool for large tasks. .pool = - glz::thread_pool::options{ + glz::thread_pool::options{ .thread_count = 1, .on_thread_start_functor = [](size_t i) { std::cout << "scheduler::thread_pool worker " << i << " starting\n"; }, @@ -423,6 +423,12 @@ suite server_client_test = [] { // with a single recv() call. poll_status = co_await client.poll(glz::poll_op::read); if (poll_status != glz::poll_status::event) { + if (glz::poll_status::closed == glz::poll_status::event) { + std::cerr << "Error on: co_await client.poll(glz::poll_op::read): client Id, " << client.socket->socket_fd << ", socket closed) Disconnected.\n"; + } + else { + std::cerr << "Error on: co_await client.poll(glz::poll_op::read): client Id, " << client.socket->socket_fd << ". Reason: " << glz::nameof(poll_status) << '\n'; + } co_return; // Handle error. } From a23e80b201d5e260a370a3059849d0052994a857 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Fri, 5 Jul 2024 10:58:22 -0500 Subject: [PATCH 290/309] Added error message details for debugging. --- include/glaze/network/server.hpp | 2 +- tests/coroutine_test/coroutine_test.cpp | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/glaze/network/server.hpp b/include/glaze/network/server.hpp index 2246d8746b..520fd314c1 100644 --- a/include/glaze/network/server.hpp +++ b/include/glaze/network/server.hpp @@ -57,7 +57,7 @@ namespace glz const_cast((const socklen_t*)(&len))); if (new_client_id < 0) { - std::cerr << "Client rejected from " << sockaddr.str(); + std::cerr << "Unable to accept client on socket address " << sockaddr.str() << '\n'; // // TODO: Handle Error // diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index a3a2577948..b21d3f118f 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -408,14 +408,14 @@ suite server_client_test = [] { // Wait for an incoming connection and accept it. auto poll_status = co_await server.poll(); if (poll_status != glz::poll_status::event) { + std::cerr << "Incoming client connection failed!\n" << "Poll Status Detail: " << glz::nameof(poll_status) << '\n'; co_return; // Handle error, see poll_status for detailed error states. } - // Accept the incoming client connection. auto client = server.accept(); - // Verify the incoming connection was accepted correctly. if (not client.socket->valid()) { + std::cerr << "Incoming client connection failed!\n"; co_return; // Handle error. } @@ -424,10 +424,10 @@ suite server_client_test = [] { poll_status = co_await client.poll(glz::poll_op::read); if (poll_status != glz::poll_status::event) { if (glz::poll_status::closed == glz::poll_status::event) { - std::cerr << "Error on: co_await client.poll(glz::poll_op::read): client Id, " << client.socket->socket_fd << ", socket closed) Disconnected.\n"; + std::cerr << "Error on: co_await client.poll(glz::poll_op::read): client Id, " << client.socket->socket_fd << ", the socket is closed.\n"; } else { - std::cerr << "Error on: co_await client.poll(glz::poll_op::read): client Id, " << client.socket->socket_fd << ". Reason: " << glz::nameof(poll_status) << '\n'; + std::cerr << "Error on: co_await client.poll(glz::poll_op::read): client Id, " << client.socket->socket_fd << ".\nDetails: " << glz::nameof(poll_status) << '\n'; } co_return; // Handle error. } @@ -438,6 +438,7 @@ suite server_client_test = [] { std::string request(256, '\0'); auto [ip_status, recv_bytes] = client.recv(request); if (ip_status != glz::ip_status::ok) { + std::cerr << "client::recv error:\n" << "Details: " << glz::nameof(poll_status) << '\n'; co_return; // Handle error, see net::ip_status for detailed error states. } @@ -447,6 +448,7 @@ suite server_client_test = [] { // Make sure the client socket can be written to. poll_status = co_await client.poll(glz::poll_op::write); if (poll_status != glz::poll_status::event) { + std::cerr << "Error on: co_await client.poll(glz::poll_op::write): client Id" << client.socket->socket_fd << ".\nDetails: " << glz::nameof(poll_status) << '\n'; co_return; // Handle error. } @@ -509,7 +511,7 @@ suite server_client_test = [] { auto [ip_status, recv_bytes] = client.recv(response); response.resize(recv_bytes.size()); - std::cout << "client: " << response << "\n"; + std::cout << "client id " << client.socket->socket_fd << ", recieved: " << response << '\n'; co_return; }; From 03129363554ebd9b00acb621cd94eee4b2e1ab1e Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Fri, 5 Jul 2024 11:18:52 -0500 Subject: [PATCH 291/309] Added debug outputs to client. --- include/glaze/network/client.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/glaze/network/client.hpp b/include/glaze/network/client.hpp index 0fd63c559a..90ad2e39ca 100644 --- a/include/glaze/network/client.hpp +++ b/include/glaze/network/client.hpp @@ -68,7 +68,9 @@ namespace glz // If the connect is happening in the background, poll for write on the socket to trigger // when the connection is established. // - std::cout << "Connection failed, polling for connection: " << inet_ntoa(server_addr.sin_addr) << ":" << ntohs(server_addr.sin_port) << '\n'; + // TODO: Handle cross-platform... + // + std::cout << "Connection failed, polling for connection: " << inet_ntoa(server_addr.sin_addr) << ":" << ntohs(server_addr.sin_port) << "\nDetails: " << strerror(errno) << '\n'; if (errno == EAGAIN || errno == EINPROGRESS) { auto pstatus = co_await scheduler->poll(socket->socket_fd, poll_op::write, timeout); if (pstatus == poll_status::event) { From b1ce317e41fb53092b559ab5a250508afcdc9c40 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 9 Jul 2024 11:21:05 -0500 Subject: [PATCH 292/309] working state for coroutines --- include/glaze/network/ip.hpp | 4 ++-- tests/coroutine_test/coroutine_test.cpp | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/include/glaze/network/ip.hpp b/include/glaze/network/ip.hpp index 90159f7e5d..ad4c9479a9 100644 --- a/include/glaze/network/ip.hpp +++ b/include/glaze/network/ip.hpp @@ -89,7 +89,7 @@ namespace glz { int family = (ip_part.find(':') != std::string_view::npos) ? AF_INET6 : AF_INET; unsigned char buf[sizeof(struct in6_addr)]; if (inet_pton(family, std::string(ip_part).c_str(), buf) == 1) { - if (inet_ntop(family, buf, output.data(), output.size())) { + if (inet_ntop(family, buf, output.data(), net::asize_t(output.size()))) { output.resize(std::strlen(output.c_str())); return output; } @@ -109,7 +109,7 @@ namespace glz { return std::nullopt; // Invalid input size for binary IP } - if (inet_ntop(family, addr_ptr, output.data(), output.size())) { + if (inet_ntop(family, addr_ptr, output.data(), net::asize_t(output.size()))) { output.resize(std::strlen(output.c_str())); return output; } else { diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index b21d3f118f..22c17f3e46 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -10,6 +10,8 @@ using namespace ut; +#define TEST_ALL + #ifdef TEST_ALL suite generator = [] { std::atomic result{}; @@ -369,6 +371,9 @@ suite ring_buffer_test = [] { }; #endif +//#define SERVER_CLIENT_TEST + +#ifdef SERVER_CLIENT_TEST suite server_client_test = [] { std::cout << "\n\nServer/Client test:\n"; @@ -518,6 +523,7 @@ suite server_client_test = [] { // Create and wait for the server and client tasks to complete. glz::sync_wait(glz::when_all(make_server_task(), make_client_task())); }; +#endif int main() { From 6bc9fe32304b041eb22fe86bbbf31792d13451b1 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 9 Jul 2024 11:23:01 -0500 Subject: [PATCH 293/309] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index ed69583f50..55965fc32c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ Glaze also supports: - [BEVE](https://github.com/beve-org/beve) (binary efficient versatile encoding) - [CSV](./docs/csv.md) (comma separated value) +> [!IMPORTANT] +> +> This `main` branch is moving towards version 3.0.0, which will remove comment and `glz::schema` support from `glz::object` within `glz::meta`. + ## With compile time reflection for MSVC, Clang, and GCC! - Read/write aggregate initializable structs without writing any metadata or macros! From 77797e6bbee6351ba1bac0279f7d17d4fade7dcc Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 9 Jul 2024 14:09:43 -0500 Subject: [PATCH 294/309] adding stdexec --- CMakeLists.txt | 45 +- cmake/CPM.cmake | 1225 +++++++++++++++++++++++ tests/coroutine_test/CMakeLists.txt | 2 +- tests/coroutine_test/coroutine_test.cpp | 31 + 4 files changed, 1301 insertions(+), 2 deletions(-) create mode 100644 cmake/CPM.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b0d49c008..7e231acf21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ include(cmake/prelude.cmake) project( glaze - VERSION 2.9.5 + VERSION 2.9.4 LANGUAGES CXX ) @@ -36,11 +36,54 @@ endif() set_property(TARGET glaze_glaze PROPERTY EXPORT_NAME glaze) +#include(FetchContent) +# +#FetchContent_Declare( +# stdexec +# GIT_REPOSITORY https://github.com/NVIDIA/stdexec.git +# GIT_TAG main +# GIT_SHALLOW TRUE +#) +#FetchContent_MakeAvailable(stdexec) + +#include(cmake/CPM.cmake) +# +#CPMAddPackage( +# NAME stdexec +# GITHUB_REPOSITORY NVIDIA/stdexec +# GIT_TAG main +#) + +#if (EXISTS ${stdexec_SOURCE_DIR}/include) +#message("stdexec headers available") +#endif() + +#find_package(Git REQUIRED) +#execute_process( +# COMMAND ${GIT_EXECUTABLE} clone --depth 1 --branch "main" --recursive "https://github.com/NVIDIA/stdexec.git" "stdexec-src" +# WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/_deps +#) +# +#set(STDEXEC_INCLUDE_DIR ${CMAKE_BINARY_DIR}/_deps/stdexec-src/include) +# +#target_compile_features(glaze_glaze INTERFACE cxx_std_20) +#target_include_directories( +# glaze_glaze ${warning_guard} +# INTERFACE +# "$" +# "$" +#) + target_compile_features(glaze_glaze INTERFACE cxx_std_20) target_include_directories( glaze_glaze ${warning_guard} INTERFACE "$" + "$" ) +#if (TARGET stdexec::stdexec) +#message("stdexec::stdexec found") +#endif() +#target_link_libraries(${PROJECT_NAME} INTERFACE STDEXEC::stdexec) if(NOT CMAKE_SKIP_INSTALL_RULES) include(cmake/install-rules.cmake) diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 0000000000..b273c3bba4 --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,1225 @@ +# CPM.cmake - CMake's missing package manager +# =========================================== +# See https://github.com/cpm-cmake/CPM.cmake for usage and update instructions. +# +# MIT License +# ----------- +#[[ + Copyright (c) 2019-2023 Lars Melchior and contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +]] + +cmake_minimum_required(VERSION 3.14 FATAL_ERROR) + +# Initialize logging prefix +if(NOT CPM_INDENT) + set(CPM_INDENT + "CPM:" + CACHE INTERNAL "" + ) +endif() + +if(NOT COMMAND cpm_message) + function(cpm_message) + message(${ARGV}) + endfunction() +endif() + +set(CURRENT_CPM_VERSION 1.0.0-development-version) + +get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH) +if(CPM_DIRECTORY) + if(NOT CPM_DIRECTORY STREQUAL CPM_CURRENT_DIRECTORY) + if(CPM_VERSION VERSION_LESS CURRENT_CPM_VERSION) + message( + AUTHOR_WARNING + "${CPM_INDENT} \ +A dependency is using a more recent CPM version (${CURRENT_CPM_VERSION}) than the current project (${CPM_VERSION}). \ +It is recommended to upgrade CPM to the most recent version. \ +See https://github.com/cpm-cmake/CPM.cmake for more information." + ) + endif() + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + include(FetchContent) + endif() + return() + endif() + + get_property( + CPM_INITIALIZED GLOBAL "" + PROPERTY CPM_INITIALIZED + SET + ) + if(CPM_INITIALIZED) + return() + endif() +endif() + +if(CURRENT_CPM_VERSION MATCHES "development-version") + message( + WARNING "${CPM_INDENT} Your project is using an unstable development version of CPM.cmake. \ +Please update to a recent release if possible. \ +See https://github.com/cpm-cmake/CPM.cmake for details." + ) +endif() + +set_property(GLOBAL PROPERTY CPM_INITIALIZED true) + +macro(cpm_set_policies) + # the policy allows us to change options without caching + cmake_policy(SET CMP0077 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) + + # the policy allows us to change set(CACHE) without caching + if(POLICY CMP0126) + cmake_policy(SET CMP0126 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) + endif() + + # The policy uses the download time for timestamp, instead of the timestamp in the archive. This + # allows for proper rebuilds when a projects url changes + if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) + endif() + + # treat relative git repository paths as being relative to the parent project's remote + if(POLICY CMP0150) + cmake_policy(SET CMP0150 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0150 NEW) + endif() +endmacro() +cpm_set_policies() + +option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependencies" + $ENV{CPM_USE_LOCAL_PACKAGES} +) +option(CPM_LOCAL_PACKAGES_ONLY "Only use `find_package` to get dependencies" + $ENV{CPM_LOCAL_PACKAGES_ONLY} +) +option(CPM_DOWNLOAD_ALL "Always download dependencies from source" $ENV{CPM_DOWNLOAD_ALL}) +option(CPM_DONT_UPDATE_MODULE_PATH "Don't update the module path to allow using find_package" + $ENV{CPM_DONT_UPDATE_MODULE_PATH} +) +option(CPM_DONT_CREATE_PACKAGE_LOCK "Don't create a package lock file in the binary path" + $ENV{CPM_DONT_CREATE_PACKAGE_LOCK} +) +option(CPM_INCLUDE_ALL_IN_PACKAGE_LOCK + "Add all packages added through CPM.cmake to the package lock" + $ENV{CPM_INCLUDE_ALL_IN_PACKAGE_LOCK} +) +option(CPM_USE_NAMED_CACHE_DIRECTORIES + "Use additional directory of package name in cache on the most nested level." + $ENV{CPM_USE_NAMED_CACHE_DIRECTORIES} +) + +set(CPM_VERSION + ${CURRENT_CPM_VERSION} + CACHE INTERNAL "" +) +set(CPM_DIRECTORY + ${CPM_CURRENT_DIRECTORY} + CACHE INTERNAL "" +) +set(CPM_FILE + ${CMAKE_CURRENT_LIST_FILE} + CACHE INTERNAL "" +) +set(CPM_PACKAGES + "" + CACHE INTERNAL "" +) +set(CPM_DRY_RUN + OFF + CACHE INTERNAL "Don't download or configure dependencies (for testing)" +) + +if(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_SOURCE_CACHE_DEFAULT $ENV{CPM_SOURCE_CACHE}) +else() + set(CPM_SOURCE_CACHE_DEFAULT OFF) +endif() + +set(CPM_SOURCE_CACHE + ${CPM_SOURCE_CACHE_DEFAULT} + CACHE PATH "Directory to download CPM dependencies" +) + +if(NOT CPM_DONT_UPDATE_MODULE_PATH) + set(CPM_MODULE_PATH + "${CMAKE_BINARY_DIR}/CPM_modules" + CACHE INTERNAL "" + ) + # remove old modules + file(REMOVE_RECURSE ${CPM_MODULE_PATH}) + file(MAKE_DIRECTORY ${CPM_MODULE_PATH}) + # locally added CPM modules should override global packages + set(CMAKE_MODULE_PATH "${CPM_MODULE_PATH};${CMAKE_MODULE_PATH}") +endif() + +if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) + set(CPM_PACKAGE_LOCK_FILE + "${CMAKE_BINARY_DIR}/cpm-package-lock.cmake" + CACHE INTERNAL "" + ) + file(WRITE ${CPM_PACKAGE_LOCK_FILE} + "# CPM Package Lock\n# This file should be committed to version control\n\n" + ) +endif() + +include(FetchContent) + +# Try to infer package name from git repository uri (path or url) +function(cpm_package_name_from_git_uri URI RESULT) + if("${URI}" MATCHES "([^/:]+)/?.git/?$") + set(${RESULT} + ${CMAKE_MATCH_1} + PARENT_SCOPE + ) + else() + unset(${RESULT} PARENT_SCOPE) + endif() +endfunction() + +# Try to infer package name and version from a url +function(cpm_package_name_and_ver_from_url url outName outVer) + if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)") + # We matched an archive + set(filename "${CMAKE_MATCH_1}") + + if(filename MATCHES "([a-zA-Z0-9_\\.-]+)[_-]v?(([0-9]+\\.)*[0-9]+[a-zA-Z0-9]*)") + # We matched - (ie foo-1.2.3) + set(${outName} + "${CMAKE_MATCH_1}" + PARENT_SCOPE + ) + set(${outVer} + "${CMAKE_MATCH_2}" + PARENT_SCOPE + ) + elseif(filename MATCHES "(([0-9]+\\.)+[0-9]+[a-zA-Z0-9]*)") + # We couldn't find a name, but we found a version + # + # In many cases (which we don't handle here) the url would look something like + # `irrelevant/ACTUAL_PACKAGE_NAME/irrelevant/1.2.3.zip`. In such a case we can't possibly + # distinguish the package name from the irrelevant bits. Moreover if we try to match the + # package name from the filename, we'd get bogus at best. + unset(${outName} PARENT_SCOPE) + set(${outVer} + "${CMAKE_MATCH_1}" + PARENT_SCOPE + ) + else() + # Boldly assume that the file name is the package name. + # + # Yes, something like `irrelevant/ACTUAL_NAME/irrelevant/download.zip` will ruin our day, but + # such cases should be quite rare. No popular service does this... we think. + set(${outName} + "${filename}" + PARENT_SCOPE + ) + unset(${outVer} PARENT_SCOPE) + endif() + else() + # No ideas yet what to do with non-archives + unset(${outName} PARENT_SCOPE) + unset(${outVer} PARENT_SCOPE) + endif() +endfunction() + +function(cpm_find_package NAME VERSION) + string(REPLACE " " ";" EXTRA_ARGS "${ARGN}") + find_package(${NAME} ${VERSION} ${EXTRA_ARGS} QUIET) + if(${CPM_ARGS_NAME}_FOUND) + if(DEFINED ${CPM_ARGS_NAME}_VERSION) + set(VERSION ${${CPM_ARGS_NAME}_VERSION}) + endif() + cpm_message(STATUS "${CPM_INDENT} Using local package ${CPM_ARGS_NAME}@${VERSION}") + CPMRegisterPackage(${CPM_ARGS_NAME} "${VERSION}") + set(CPM_PACKAGE_FOUND + YES + PARENT_SCOPE + ) + else() + set(CPM_PACKAGE_FOUND + NO + PARENT_SCOPE + ) + endif() +endfunction() + +# Create a custom FindXXX.cmake module for a CPM package This prevents `find_package(NAME)` from +# finding the system library +function(cpm_create_module_file Name) + if(NOT CPM_DONT_UPDATE_MODULE_PATH) + # erase any previous modules + file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake + "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)" + ) + endif() +endfunction() + +# Find a package locally or fallback to CPMAddPackage +function(CPMFindPackage) + set(oneValueArgs NAME VERSION GIT_TAG FIND_PACKAGE_ARGUMENTS) + + cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "" ${ARGN}) + + if(NOT DEFINED CPM_ARGS_VERSION) + if(DEFINED CPM_ARGS_GIT_TAG) + cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) + endif() + endif() + + set(downloadPackage ${CPM_DOWNLOAD_ALL}) + if(DEFINED CPM_DOWNLOAD_${CPM_ARGS_NAME}) + set(downloadPackage ${CPM_DOWNLOAD_${CPM_ARGS_NAME}}) + elseif(DEFINED ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) + set(downloadPackage $ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) + endif() + if(downloadPackage) + CPMAddPackage(${ARGN}) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) + + if(NOT CPM_PACKAGE_FOUND) + CPMAddPackage(${ARGN}) + cpm_export_variables(${CPM_ARGS_NAME}) + endif() + +endfunction() + +# checks if a package has been added before +function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION) + if("${CPM_ARGS_NAME}" IN_LIST CPM_PACKAGES) + CPMGetPackageVersion(${CPM_ARGS_NAME} CPM_PACKAGE_VERSION) + if("${CPM_PACKAGE_VERSION}" VERSION_LESS "${CPM_ARGS_VERSION}") + message( + WARNING + "${CPM_INDENT} Requires a newer version of ${CPM_ARGS_NAME} (${CPM_ARGS_VERSION}) than currently included (${CPM_PACKAGE_VERSION})." + ) + endif() + cpm_get_fetch_properties(${CPM_ARGS_NAME}) + set(${CPM_ARGS_NAME}_ADDED NO) + set(CPM_PACKAGE_ALREADY_ADDED + YES + PARENT_SCOPE + ) + cpm_export_variables(${CPM_ARGS_NAME}) + else() + set(CPM_PACKAGE_ALREADY_ADDED + NO + PARENT_SCOPE + ) + endif() +endfunction() + +# Parse the argument of CPMAddPackage in case a single one was provided and convert it to a list of +# arguments which can then be parsed idiomatically. For example gh:foo/bar@1.2.3 will be converted +# to: GITHUB_REPOSITORY;foo/bar;VERSION;1.2.3 +function(cpm_parse_add_package_single_arg arg outArgs) + # Look for a scheme + if("${arg}" MATCHES "^([a-zA-Z]+):(.+)$") + string(TOLOWER "${CMAKE_MATCH_1}" scheme) + set(uri "${CMAKE_MATCH_2}") + + # Check for CPM-specific schemes + if(scheme STREQUAL "gh") + set(out "GITHUB_REPOSITORY;${uri}") + set(packageType "git") + elseif(scheme STREQUAL "gl") + set(out "GITLAB_REPOSITORY;${uri}") + set(packageType "git") + elseif(scheme STREQUAL "bb") + set(out "BITBUCKET_REPOSITORY;${uri}") + set(packageType "git") + # A CPM-specific scheme was not found. Looks like this is a generic URL so try to determine + # type + elseif(arg MATCHES ".git/?(@|#|$)") + set(out "GIT_REPOSITORY;${arg}") + set(packageType "git") + else() + # Fall back to a URL + set(out "URL;${arg}") + set(packageType "archive") + + # We could also check for SVN since FetchContent supports it, but SVN is so rare these days. + # We just won't bother with the additional complexity it will induce in this function. SVN is + # done by multi-arg + endif() + else() + if(arg MATCHES ".git/?(@|#|$)") + set(out "GIT_REPOSITORY;${arg}") + set(packageType "git") + else() + # Give up + message(FATAL_ERROR "${CPM_INDENT} Can't determine package type of '${arg}'") + endif() + endif() + + # For all packages we interpret @... as version. Only replace the last occurrence. Thus URIs + # containing '@' can be used + string(REGEX REPLACE "@([^@]+)$" ";VERSION;\\1" out "${out}") + + # Parse the rest according to package type + if(packageType STREQUAL "git") + # For git repos we interpret #... as a tag or branch or commit hash + string(REGEX REPLACE "#([^#]+)$" ";GIT_TAG;\\1" out "${out}") + elseif(packageType STREQUAL "archive") + # For archives we interpret #... as a URL hash. + string(REGEX REPLACE "#([^#]+)$" ";URL_HASH;\\1" out "${out}") + # We don't try to parse the version if it's not provided explicitly. cpm_get_version_from_url + # should do this at a later point + else() + # We should never get here. This is an assertion and hitting it means there's a problem with the + # code above. A packageType was set, but not handled by this if-else. + message(FATAL_ERROR "${CPM_INDENT} Unsupported package type '${packageType}' of '${arg}'") + endif() + + set(${outArgs} + ${out} + PARENT_SCOPE + ) +endfunction() + +# Check that the working directory for a git repo is clean +function(cpm_check_git_working_dir_is_clean repoPath gitTag isClean) + + find_package(Git REQUIRED) + + if(NOT GIT_EXECUTABLE) + # No git executable, assume directory is clean + set(${isClean} + TRUE + PARENT_SCOPE + ) + return() + endif() + + # check for uncommitted changes + execute_process( + COMMAND ${GIT_EXECUTABLE} status --porcelain + RESULT_VARIABLE resultGitStatus + OUTPUT_VARIABLE repoStatus + OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET + WORKING_DIRECTORY ${repoPath} + ) + if(resultGitStatus) + # not supposed to happen, assume clean anyway + message(WARNING "${CPM_INDENT} Calling git status on folder ${repoPath} failed") + set(${isClean} + TRUE + PARENT_SCOPE + ) + return() + endif() + + if(NOT "${repoStatus}" STREQUAL "") + set(${isClean} + FALSE + PARENT_SCOPE + ) + return() + endif() + + # check for committed changes + execute_process( + COMMAND ${GIT_EXECUTABLE} diff -s --exit-code ${gitTag} + RESULT_VARIABLE resultGitDiff + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_QUIET + WORKING_DIRECTORY ${repoPath} + ) + + if(${resultGitDiff} EQUAL 0) + set(${isClean} + TRUE + PARENT_SCOPE + ) + else() + set(${isClean} + FALSE + PARENT_SCOPE + ) + endif() + +endfunction() + +# Add PATCH_COMMAND to CPM_ARGS_UNPARSED_ARGUMENTS. This method consumes a list of files in ARGN +# then generates a `PATCH_COMMAND` appropriate for `ExternalProject_Add()`. This command is appended +# to the parent scope's `CPM_ARGS_UNPARSED_ARGUMENTS`. +function(cpm_add_patches) + # Return if no patch files are supplied. + if(NOT ARGN) + return() + endif() + + # Find the patch program. + find_program(PATCH_EXECUTABLE patch) + if(WIN32 AND NOT PATCH_EXECUTABLE) + # The Windows git executable is distributed with patch.exe. Find the path to the executable, if + # it exists, then search `../../usr/bin` for patch.exe. + find_package(Git QUIET) + if(GIT_EXECUTABLE) + get_filename_component(extra_search_path ${GIT_EXECUTABLE} DIRECTORY) + get_filename_component(extra_search_path ${extra_search_path} DIRECTORY) + get_filename_component(extra_search_path ${extra_search_path} DIRECTORY) + find_program(PATCH_EXECUTABLE patch HINTS "${extra_search_path}/usr/bin") + endif() + endif() + if(NOT PATCH_EXECUTABLE) + message(FATAL_ERROR "Couldn't find `patch` executable to use with PATCHES keyword.") + endif() + + # Create a temporary + set(temp_list ${CPM_ARGS_UNPARSED_ARGUMENTS}) + + # Ensure each file exists (or error out) and add it to the list. + set(first_item True) + foreach(PATCH_FILE ${ARGN}) + # Make sure the patch file exists, if we can't find it, try again in the current directory. + if(NOT EXISTS "${PATCH_FILE}") + if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}") + message(FATAL_ERROR "Couldn't find patch file: '${PATCH_FILE}'") + endif() + set(PATCH_FILE "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}") + endif() + + # Convert to absolute path for use with patch file command. + get_filename_component(PATCH_FILE "${PATCH_FILE}" ABSOLUTE) + + # The first patch entry must be preceded by "PATCH_COMMAND" while the following items are + # preceded by "&&". + if(first_item) + set(first_item False) + list(APPEND temp_list "PATCH_COMMAND") + else() + list(APPEND temp_list "&&") + endif() + # Add the patch command to the list + list(APPEND temp_list "${PATCH_EXECUTABLE}" "-p1" "<" "${PATCH_FILE}") + endforeach() + + # Move temp out into parent scope. + set(CPM_ARGS_UNPARSED_ARGUMENTS + ${temp_list} + PARENT_SCOPE + ) + +endfunction() + +# method to overwrite internal FetchContent properties, to allow using CPM.cmake to overload +# FetchContent calls. As these are internal cmake properties, this method should be used carefully +# and may need modification in future CMake versions. Source: +# https://github.com/Kitware/CMake/blob/dc3d0b5a0a7d26d43d6cfeb511e224533b5d188f/Modules/FetchContent.cmake#L1152 +function(cpm_override_fetchcontent contentName) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "SOURCE_DIR;BINARY_DIR" "") + if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "") + message(FATAL_ERROR "${CPM_INDENT} Unsupported arguments: ${arg_UNPARSED_ARGUMENTS}") + endif() + + string(TOLOWER ${contentName} contentNameLower) + set(prefix "_FetchContent_${contentNameLower}") + + set(propertyName "${prefix}_sourceDir") + define_property( + GLOBAL + PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} "${arg_SOURCE_DIR}") + + set(propertyName "${prefix}_binaryDir") + define_property( + GLOBAL + PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} "${arg_BINARY_DIR}") + + set(propertyName "${prefix}_populated") + define_property( + GLOBAL + PROPERTY ${propertyName} + BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" + FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" + ) + set_property(GLOBAL PROPERTY ${propertyName} TRUE) +endfunction() + +# Download and add a package from source +function(CPMAddPackage) + cpm_set_policies() + + list(LENGTH ARGN argnLength) + if(argnLength EQUAL 1) + cpm_parse_add_package_single_arg("${ARGN}" ARGN) + + # The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM + set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;") + endif() + + set(oneValueArgs + NAME + FORCE + VERSION + GIT_TAG + DOWNLOAD_ONLY + GITHUB_REPOSITORY + GITLAB_REPOSITORY + BITBUCKET_REPOSITORY + GIT_REPOSITORY + SOURCE_DIR + FIND_PACKAGE_ARGUMENTS + NO_CACHE + SYSTEM + GIT_SHALLOW + EXCLUDE_FROM_ALL + SOURCE_SUBDIR + CUSTOM_CACHE_KEY + ) + + set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND PATCHES) + + cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") + + # Set default values for arguments + + if(NOT DEFINED CPM_ARGS_VERSION) + if(DEFINED CPM_ARGS_GIT_TAG) + cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) + endif() + endif() + + if(CPM_ARGS_DOWNLOAD_ONLY) + set(DOWNLOAD_ONLY ${CPM_ARGS_DOWNLOAD_ONLY}) + else() + set(DOWNLOAD_ONLY NO) + endif() + + if(DEFINED CPM_ARGS_GITHUB_REPOSITORY) + set(CPM_ARGS_GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git") + elseif(DEFINED CPM_ARGS_GITLAB_REPOSITORY) + set(CPM_ARGS_GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git") + elseif(DEFINED CPM_ARGS_BITBUCKET_REPOSITORY) + set(CPM_ARGS_GIT_REPOSITORY "https://bitbucket.org/${CPM_ARGS_BITBUCKET_REPOSITORY}.git") + endif() + + if(DEFINED CPM_ARGS_GIT_REPOSITORY) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY ${CPM_ARGS_GIT_REPOSITORY}) + if(NOT DEFINED CPM_ARGS_GIT_TAG) + set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION}) + endif() + + # If a name wasn't provided, try to infer it from the git repo + if(NOT DEFINED CPM_ARGS_NAME) + cpm_package_name_from_git_uri(${CPM_ARGS_GIT_REPOSITORY} CPM_ARGS_NAME) + endif() + endif() + + set(CPM_SKIP_FETCH FALSE) + + if(DEFINED CPM_ARGS_GIT_TAG) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_TAG ${CPM_ARGS_GIT_TAG}) + # If GIT_SHALLOW is explicitly specified, honor the value. + if(DEFINED CPM_ARGS_GIT_SHALLOW) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW ${CPM_ARGS_GIT_SHALLOW}) + endif() + endif() + + if(DEFINED CPM_ARGS_URL) + # If a name or version aren't provided, try to infer them from the URL + list(GET CPM_ARGS_URL 0 firstUrl) + cpm_package_name_and_ver_from_url(${firstUrl} nameFromUrl verFromUrl) + # If we fail to obtain name and version from the first URL, we could try other URLs if any. + # However multiple URLs are expected to be quite rare, so for now we won't bother. + + # If the caller provided their own name and version, they trump the inferred ones. + if(NOT DEFINED CPM_ARGS_NAME) + set(CPM_ARGS_NAME ${nameFromUrl}) + endif() + if(NOT DEFINED CPM_ARGS_VERSION) + set(CPM_ARGS_VERSION ${verFromUrl}) + endif() + + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS URL "${CPM_ARGS_URL}") + endif() + + # Check for required arguments + + if(NOT DEFINED CPM_ARGS_NAME) + message( + FATAL_ERROR + "${CPM_INDENT} 'NAME' was not provided and couldn't be automatically inferred for package added with arguments: '${ARGN}'" + ) + endif() + + # Check if package has been added before + cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") + if(CPM_PACKAGE_ALREADY_ADDED) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + # Check for manual overrides + if(NOT CPM_ARGS_FORCE AND NOT "${CPM_${CPM_ARGS_NAME}_SOURCE}" STREQUAL "") + set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE}) + set(CPM_${CPM_ARGS_NAME}_SOURCE "") + CPMAddPackage( + NAME "${CPM_ARGS_NAME}" + SOURCE_DIR "${PACKAGE_SOURCE}" + EXCLUDE_FROM_ALL "${CPM_ARGS_EXCLUDE_FROM_ALL}" + SYSTEM "${CPM_ARGS_SYSTEM}" + PATCHES "${CPM_ARGS_PATCHES}" + OPTIONS "${CPM_ARGS_OPTIONS}" + SOURCE_SUBDIR "${CPM_ARGS_SOURCE_SUBDIR}" + DOWNLOAD_ONLY "${DOWNLOAD_ONLY}" + FORCE True + ) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + # Check for available declaration + if(NOT CPM_ARGS_FORCE AND NOT "${CPM_DECLARATION_${CPM_ARGS_NAME}}" STREQUAL "") + set(declaration ${CPM_DECLARATION_${CPM_ARGS_NAME}}) + set(CPM_DECLARATION_${CPM_ARGS_NAME} "") + CPMAddPackage(${declaration}) + cpm_export_variables(${CPM_ARGS_NAME}) + # checking again to ensure version and option compatibility + cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") + return() + endif() + + if(NOT CPM_ARGS_FORCE) + if(CPM_USE_LOCAL_PACKAGES OR CPM_LOCAL_PACKAGES_ONLY) + cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) + + if(CPM_PACKAGE_FOUND) + cpm_export_variables(${CPM_ARGS_NAME}) + return() + endif() + + if(CPM_LOCAL_PACKAGES_ONLY) + message( + SEND_ERROR + "${CPM_INDENT} ${CPM_ARGS_NAME} not found via find_package(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION})" + ) + endif() + endif() + endif() + + CPMRegisterPackage("${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}") + + if(DEFINED CPM_ARGS_GIT_TAG) + set(PACKAGE_INFO "${CPM_ARGS_GIT_TAG}") + elseif(DEFINED CPM_ARGS_SOURCE_DIR) + set(PACKAGE_INFO "${CPM_ARGS_SOURCE_DIR}") + else() + set(PACKAGE_INFO "${CPM_ARGS_VERSION}") + endif() + + if(DEFINED FETCHCONTENT_BASE_DIR) + # respect user's FETCHCONTENT_BASE_DIR if set + set(CPM_FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR}) + else() + set(CPM_FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/_deps) + endif() + + cpm_add_patches(${CPM_ARGS_PATCHES}) + + if(DEFINED CPM_ARGS_DOWNLOAD_COMMAND) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND}) + elseif(DEFINED CPM_ARGS_SOURCE_DIR) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${CPM_ARGS_SOURCE_DIR}) + if(NOT IS_ABSOLUTE ${CPM_ARGS_SOURCE_DIR}) + # Expand `CPM_ARGS_SOURCE_DIR` relative path. This is important because EXISTS doesn't work + # for relative paths. + get_filename_component( + source_directory ${CPM_ARGS_SOURCE_DIR} REALPATH BASE_DIR ${CMAKE_CURRENT_BINARY_DIR} + ) + else() + set(source_directory ${CPM_ARGS_SOURCE_DIR}) + endif() + if(NOT EXISTS ${source_directory}) + string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) + # remove timestamps so CMake will re-download the dependency + file(REMOVE_RECURSE "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild") + endif() + elseif(CPM_SOURCE_CACHE AND NOT CPM_ARGS_NO_CACHE) + string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) + set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS}) + list(SORT origin_parameters) + if(CPM_ARGS_CUSTOM_CACHE_KEY) + # Application set a custom unique directory name + set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${CPM_ARGS_CUSTOM_CACHE_KEY}) + elseif(CPM_USE_NAMED_CACHE_DIRECTORIES) + string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG") + set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME}) + else() + string(SHA1 origin_hash "${origin_parameters}") + set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}) + endif() + # Expand `download_directory` relative path. This is important because EXISTS doesn't work for + # relative paths. + get_filename_component(download_directory ${download_directory} ABSOLUTE) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${download_directory}) + + if(CPM_SOURCE_CACHE) + file(LOCK ${download_directory}/../cmake.lock) + endif() + + if(EXISTS ${download_directory}) + if(CPM_SOURCE_CACHE) + file(LOCK ${download_directory}/../cmake.lock RELEASE) + endif() + + cpm_store_fetch_properties( + ${CPM_ARGS_NAME} "${download_directory}" + "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" + ) + cpm_get_fetch_properties("${CPM_ARGS_NAME}") + + if(DEFINED CPM_ARGS_GIT_TAG AND NOT (PATCH_COMMAND IN_LIST CPM_ARGS_UNPARSED_ARGUMENTS)) + # warn if cache has been changed since checkout + cpm_check_git_working_dir_is_clean(${download_directory} ${CPM_ARGS_GIT_TAG} IS_CLEAN) + if(NOT ${IS_CLEAN}) + message( + WARNING "${CPM_INDENT} Cache for ${CPM_ARGS_NAME} (${download_directory}) is dirty" + ) + endif() + endif() + + cpm_add_subdirectory( + "${CPM_ARGS_NAME}" + "${DOWNLOAD_ONLY}" + "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" + "${${CPM_ARGS_NAME}_BINARY_DIR}" + "${CPM_ARGS_EXCLUDE_FROM_ALL}" + "${CPM_ARGS_SYSTEM}" + "${CPM_ARGS_OPTIONS}" + ) + set(PACKAGE_INFO "${PACKAGE_INFO} at ${download_directory}") + + # As the source dir is already cached/populated, we override the call to FetchContent. + set(CPM_SKIP_FETCH TRUE) + cpm_override_fetchcontent( + "${lower_case_name}" SOURCE_DIR "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" + BINARY_DIR "${${CPM_ARGS_NAME}_BINARY_DIR}" + ) + + else() + # Enable shallow clone when GIT_TAG is not a commit hash. Our guess may not be accurate, but + # it should guarantee no commit hash get mis-detected. + if(NOT DEFINED CPM_ARGS_GIT_SHALLOW) + cpm_is_git_tag_commit_hash("${CPM_ARGS_GIT_TAG}" IS_HASH) + if(NOT ${IS_HASH}) + list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW TRUE) + endif() + endif() + + # remove timestamps so CMake will re-download the dependency + file(REMOVE_RECURSE ${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild) + set(PACKAGE_INFO "${PACKAGE_INFO} to ${download_directory}") + endif() + endif() + + cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")") + + if(CPM_PACKAGE_LOCK_ENABLED) + if((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK) + cpm_add_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") + elseif(CPM_ARGS_SOURCE_DIR) + cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "local directory") + else() + cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") + endif() + endif() + + cpm_message( + STATUS "${CPM_INDENT} Adding package ${CPM_ARGS_NAME}@${CPM_ARGS_VERSION} (${PACKAGE_INFO})" + ) + + if(NOT CPM_SKIP_FETCH) + cpm_declare_fetch( + "${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}" "${PACKAGE_INFO}" "${CPM_ARGS_UNPARSED_ARGUMENTS}" + ) + cpm_fetch_package("${CPM_ARGS_NAME}" populated) + if(CPM_SOURCE_CACHE AND download_directory) + file(LOCK ${download_directory}/../cmake.lock RELEASE) + endif() + if(${populated}) + cpm_add_subdirectory( + "${CPM_ARGS_NAME}" + "${DOWNLOAD_ONLY}" + "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" + "${${CPM_ARGS_NAME}_BINARY_DIR}" + "${CPM_ARGS_EXCLUDE_FROM_ALL}" + "${CPM_ARGS_SYSTEM}" + "${CPM_ARGS_OPTIONS}" + ) + endif() + cpm_get_fetch_properties("${CPM_ARGS_NAME}") + endif() + + set(${CPM_ARGS_NAME}_ADDED YES) + cpm_export_variables("${CPM_ARGS_NAME}") +endfunction() + +# Fetch a previously declared package +macro(CPMGetPackage Name) + if(DEFINED "CPM_DECLARATION_${Name}") + CPMAddPackage(NAME ${Name}) + else() + message(SEND_ERROR "${CPM_INDENT} Cannot retrieve package ${Name}: no declaration available") + endif() +endmacro() + +# export variables available to the caller to the parent scope expects ${CPM_ARGS_NAME} to be set +macro(cpm_export_variables name) + set(${name}_SOURCE_DIR + "${${name}_SOURCE_DIR}" + PARENT_SCOPE + ) + set(${name}_BINARY_DIR + "${${name}_BINARY_DIR}" + PARENT_SCOPE + ) + set(${name}_ADDED + "${${name}_ADDED}" + PARENT_SCOPE + ) + set(CPM_LAST_PACKAGE_NAME + "${name}" + PARENT_SCOPE + ) +endmacro() + +# declares a package, so that any call to CPMAddPackage for the package name will use these +# arguments instead. Previous declarations will not be overridden. +macro(CPMDeclarePackage Name) + if(NOT DEFINED "CPM_DECLARATION_${Name}") + set("CPM_DECLARATION_${Name}" "${ARGN}") + endif() +endmacro() + +function(cpm_add_to_package_lock Name) + if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) + cpm_prettify_package_arguments(PRETTY_ARGN false ${ARGN}) + file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name}\nCPMDeclarePackage(${Name}\n${PRETTY_ARGN})\n") + endif() +endfunction() + +function(cpm_add_comment_to_package_lock Name) + if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) + cpm_prettify_package_arguments(PRETTY_ARGN true ${ARGN}) + file(APPEND ${CPM_PACKAGE_LOCK_FILE} + "# ${Name} (unversioned)\n# CPMDeclarePackage(${Name}\n${PRETTY_ARGN}#)\n" + ) + endif() +endfunction() + +# includes the package lock file if it exists and creates a target `cpm-update-package-lock` to +# update it +macro(CPMUsePackageLock file) + if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) + get_filename_component(CPM_ABSOLUTE_PACKAGE_LOCK_PATH ${file} ABSOLUTE) + if(EXISTS ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) + include(${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) + endif() + if(NOT TARGET cpm-update-package-lock) + add_custom_target( + cpm-update-package-lock COMMAND ${CMAKE_COMMAND} -E copy ${CPM_PACKAGE_LOCK_FILE} + ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH} + ) + endif() + set(CPM_PACKAGE_LOCK_ENABLED true) + endif() +endmacro() + +# registers a package that has been added to CPM +function(CPMRegisterPackage PACKAGE VERSION) + list(APPEND CPM_PACKAGES ${PACKAGE}) + set(CPM_PACKAGES + ${CPM_PACKAGES} + CACHE INTERNAL "" + ) + set("CPM_PACKAGE_${PACKAGE}_VERSION" + ${VERSION} + CACHE INTERNAL "" + ) +endfunction() + +# retrieve the current version of the package to ${OUTPUT} +function(CPMGetPackageVersion PACKAGE OUTPUT) + set(${OUTPUT} + "${CPM_PACKAGE_${PACKAGE}_VERSION}" + PARENT_SCOPE + ) +endfunction() + +# declares a package in FetchContent_Declare +function(cpm_declare_fetch PACKAGE VERSION INFO) + if(${CPM_DRY_RUN}) + cpm_message(STATUS "${CPM_INDENT} Package not declared (dry run)") + return() + endif() + + FetchContent_Declare(${PACKAGE} ${ARGN}) +endfunction() + +# returns properties for a package previously defined by cpm_declare_fetch +function(cpm_get_fetch_properties PACKAGE) + if(${CPM_DRY_RUN}) + return() + endif() + + set(${PACKAGE}_SOURCE_DIR + "${CPM_PACKAGE_${PACKAGE}_SOURCE_DIR}" + PARENT_SCOPE + ) + set(${PACKAGE}_BINARY_DIR + "${CPM_PACKAGE_${PACKAGE}_BINARY_DIR}" + PARENT_SCOPE + ) +endfunction() + +function(cpm_store_fetch_properties PACKAGE source_dir binary_dir) + if(${CPM_DRY_RUN}) + return() + endif() + + set(CPM_PACKAGE_${PACKAGE}_SOURCE_DIR + "${source_dir}" + CACHE INTERNAL "" + ) + set(CPM_PACKAGE_${PACKAGE}_BINARY_DIR + "${binary_dir}" + CACHE INTERNAL "" + ) +endfunction() + +# adds a package as a subdirectory if viable, according to provided options +function( + cpm_add_subdirectory + PACKAGE + DOWNLOAD_ONLY + SOURCE_DIR + BINARY_DIR + EXCLUDE + SYSTEM + OPTIONS +) + + if(NOT DOWNLOAD_ONLY AND EXISTS ${SOURCE_DIR}/CMakeLists.txt) + set(addSubdirectoryExtraArgs "") + if(EXCLUDE) + list(APPEND addSubdirectoryExtraArgs EXCLUDE_FROM_ALL) + endif() + if("${SYSTEM}" AND "${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.25") + # https://cmake.org/cmake/help/latest/prop_dir/SYSTEM.html#prop_dir:SYSTEM + list(APPEND addSubdirectoryExtraArgs SYSTEM) + endif() + if(OPTIONS) + foreach(OPTION ${OPTIONS}) + cpm_parse_option("${OPTION}") + set(${OPTION_KEY} "${OPTION_VALUE}") + endforeach() + endif() + set(CPM_OLD_INDENT "${CPM_INDENT}") + set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:") + add_subdirectory(${SOURCE_DIR} ${BINARY_DIR} ${addSubdirectoryExtraArgs}) + set(CPM_INDENT "${CPM_OLD_INDENT}") + endif() +endfunction() + +# downloads a previously declared package via FetchContent and exports the variables +# `${PACKAGE}_SOURCE_DIR` and `${PACKAGE}_BINARY_DIR` to the parent scope +function(cpm_fetch_package PACKAGE populated) + set(${populated} + FALSE + PARENT_SCOPE + ) + if(${CPM_DRY_RUN}) + cpm_message(STATUS "${CPM_INDENT} Package ${PACKAGE} not fetched (dry run)") + return() + endif() + + FetchContent_GetProperties(${PACKAGE}) + + string(TOLOWER "${PACKAGE}" lower_case_name) + + if(NOT ${lower_case_name}_POPULATED) + FetchContent_Populate(${PACKAGE}) + set(${populated} + TRUE + PARENT_SCOPE + ) + endif() + + cpm_store_fetch_properties( + ${CPM_ARGS_NAME} ${${lower_case_name}_SOURCE_DIR} ${${lower_case_name}_BINARY_DIR} + ) + + set(${PACKAGE}_SOURCE_DIR + ${${lower_case_name}_SOURCE_DIR} + PARENT_SCOPE + ) + set(${PACKAGE}_BINARY_DIR + ${${lower_case_name}_BINARY_DIR} + PARENT_SCOPE + ) +endfunction() + +# splits a package option +function(cpm_parse_option OPTION) + string(REGEX MATCH "^[^ ]+" OPTION_KEY "${OPTION}") + string(LENGTH "${OPTION}" OPTION_LENGTH) + string(LENGTH "${OPTION_KEY}" OPTION_KEY_LENGTH) + if(OPTION_KEY_LENGTH STREQUAL OPTION_LENGTH) + # no value for key provided, assume user wants to set option to "ON" + set(OPTION_VALUE "ON") + else() + math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1") + string(SUBSTRING "${OPTION}" "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE) + endif() + set(OPTION_KEY + "${OPTION_KEY}" + PARENT_SCOPE + ) + set(OPTION_VALUE + "${OPTION_VALUE}" + PARENT_SCOPE + ) +endfunction() + +# guesses the package version from a git tag +function(cpm_get_version_from_git_tag GIT_TAG RESULT) + string(LENGTH ${GIT_TAG} length) + if(length EQUAL 40) + # GIT_TAG is probably a git hash + set(${RESULT} + 0 + PARENT_SCOPE + ) + else() + string(REGEX MATCH "v?([0123456789.]*).*" _ ${GIT_TAG}) + set(${RESULT} + ${CMAKE_MATCH_1} + PARENT_SCOPE + ) + endif() +endfunction() + +# guesses if the git tag is a commit hash or an actual tag or a branch name. +function(cpm_is_git_tag_commit_hash GIT_TAG RESULT) + string(LENGTH "${GIT_TAG}" length) + # full hash has 40 characters, and short hash has at least 7 characters. + if(length LESS 7 OR length GREATER 40) + set(${RESULT} + 0 + PARENT_SCOPE + ) + else() + if(${GIT_TAG} MATCHES "^[a-fA-F0-9]+$") + set(${RESULT} + 1 + PARENT_SCOPE + ) + else() + set(${RESULT} + 0 + PARENT_SCOPE + ) + endif() + endif() +endfunction() + +function(cpm_prettify_package_arguments OUT_VAR IS_IN_COMMENT) + set(oneValueArgs + NAME + FORCE + VERSION + GIT_TAG + DOWNLOAD_ONLY + GITHUB_REPOSITORY + GITLAB_REPOSITORY + BITBUCKET_REPOSITORY + GIT_REPOSITORY + SOURCE_DIR + FIND_PACKAGE_ARGUMENTS + NO_CACHE + SYSTEM + GIT_SHALLOW + EXCLUDE_FROM_ALL + SOURCE_SUBDIR + ) + set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND) + cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + foreach(oneArgName ${oneValueArgs}) + if(DEFINED CPM_ARGS_${oneArgName}) + if(${IS_IN_COMMENT}) + string(APPEND PRETTY_OUT_VAR "#") + endif() + if(${oneArgName} STREQUAL "SOURCE_DIR") + string(REPLACE ${CMAKE_SOURCE_DIR} "\${CMAKE_SOURCE_DIR}" CPM_ARGS_${oneArgName} + ${CPM_ARGS_${oneArgName}} + ) + endif() + string(APPEND PRETTY_OUT_VAR " ${oneArgName} ${CPM_ARGS_${oneArgName}}\n") + endif() + endforeach() + foreach(multiArgName ${multiValueArgs}) + if(DEFINED CPM_ARGS_${multiArgName}) + if(${IS_IN_COMMENT}) + string(APPEND PRETTY_OUT_VAR "#") + endif() + string(APPEND PRETTY_OUT_VAR " ${multiArgName}\n") + foreach(singleOption ${CPM_ARGS_${multiArgName}}) + if(${IS_IN_COMMENT}) + string(APPEND PRETTY_OUT_VAR "#") + endif() + string(APPEND PRETTY_OUT_VAR " \"${singleOption}\"\n") + endforeach() + endif() + endforeach() + + if(NOT "${CPM_ARGS_UNPARSED_ARGUMENTS}" STREQUAL "") + if(${IS_IN_COMMENT}) + string(APPEND PRETTY_OUT_VAR "#") + endif() + string(APPEND PRETTY_OUT_VAR " ") + foreach(CPM_ARGS_UNPARSED_ARGUMENT ${CPM_ARGS_UNPARSED_ARGUMENTS}) + string(APPEND PRETTY_OUT_VAR " ${CPM_ARGS_UNPARSED_ARGUMENT}") + endforeach() + string(APPEND PRETTY_OUT_VAR "\n") + endif() + + set(${OUT_VAR} + ${PRETTY_OUT_VAR} + PARENT_SCOPE + ) + +endfunction() diff --git a/tests/coroutine_test/CMakeLists.txt b/tests/coroutine_test/CMakeLists.txt index 5d5bafd5f5..7bd7b13606 100644 --- a/tests/coroutine_test/CMakeLists.txt +++ b/tests/coroutine_test/CMakeLists.txt @@ -4,7 +4,7 @@ add_compile_definitions(CURRENT_DIRECTORY="${CMAKE_CURRENT_SOURCE_DIR}") add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp) -target_link_libraries(${PROJECT_NAME} PRIVATE glz_test_common) +target_link_libraries(${PROJECT_NAME} PRIVATE glz_test_exceptions) add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 22c17f3e46..4833347eb6 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -8,6 +8,9 @@ #include "glaze/network.hpp" #include "ut/ut.hpp" +#include "stdexec/execution.hpp" +#include "exec/task.hpp" + using namespace ut; #define TEST_ALL @@ -525,6 +528,34 @@ suite server_client_test = [] { }; #endif +template +exec::task async_answer(S1 s1, S2 s2) { + // Senders are implicitly awaitable (in this coroutine type): + co_await static_cast(s2); + co_return co_await static_cast(s1); +} + +template +exec::task> async_answer2(S1 s1, S2 s2) { + co_return co_await stdexec::stopped_as_optional(async_answer(s1, s2)); +} + +// tasks have an associated stop token +exec::task> async_stop_token() { + co_return co_await stdexec::stopped_as_optional(stdexec::get_stop_token()); +} + +suite stdexec_coroutine_test = [] +{ + try { + // Awaitables are implicitly senders: + auto [i] = stdexec::sync_wait(async_answer2(stdexec::just(42), stdexec::just())).value(); + std::cout << "The answer is " << i.value() << '\n'; + } catch (std::exception& e) { + std::cout << e.what() << '\n'; + } +}; + int main() { std::cout << '\n'; From f3e7a4732d7869e1cac4040adbc90dddc993d2bb Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 9 Jul 2024 16:07:29 -0500 Subject: [PATCH 295/309] updates --- tests/coroutine_test/coroutine_test.cpp | 89 ++++++++++++++----------- 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 4833347eb6..7daf6d30b4 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -10,6 +10,8 @@ #include "stdexec/execution.hpp" #include "exec/task.hpp" +#include "exec/static_thread_pool.hpp" +#include "exec/async_scope.hpp" using namespace ut; @@ -18,7 +20,7 @@ using namespace ut; #ifdef TEST_ALL suite generator = [] { std::atomic result{}; - auto task = [&](uint64_t count_to) -> glz::task { + auto task = [&](uint64_t count_to) -> exec::task { // Create a generator function that will yield and incrementing // number each time its called. auto gen = []() -> glz::generator { @@ -41,7 +43,7 @@ suite generator = [] { co_return; }; - glz::sync_wait(task(100)); + stdexec::sync_wait(task(100)); expect(result == 5050) << result; }; @@ -49,73 +51,82 @@ suite generator = [] { suite thread_pool = [] { // This lambda will create a glz::task that returns a unit64_t. // It can be invoked many times with different arguments. - auto make_task_inline = [](uint64_t x) -> glz::task { co_return x + x; }; + auto make_task_inline = [](uint64_t x) -> exec::task { co_return x + x; }; // This will block the calling thread until the created task completes. // Since this task isn't scheduled on any glz::thread_pool or glz::scheduler // it will execute directly on the calling thread. - auto result = glz::sync_wait(make_task_inline(5)); - expect(result == 10); + auto result = stdexec::sync_wait(make_task_inline(5)); + expect(std::get<0>(result.value()) == 10); // std::cout << "Inline Result = " << result << "\n"; - // We'll make a 1 thread glz::thread_pool to demonstrate offloading the task's - // execution to another thread. We'll capture the thread pool in the lambda, - // note that you will need to guarantee the thread pool outlives the coroutine. - glz::thread_pool tp{glz::thread_pool::options{.thread_count = 1}}; + exec::static_thread_pool pool(1); + auto sched = pool.get_scheduler(); - auto make_task_offload = [&tp](uint64_t x) -> glz::task { - co_await tp.schedule(); // Schedules execution on the thread pool. - co_return x + x; // This will execute on the thread pool. + auto make_task_offload = [&sched](uint64_t x) { + return stdexec::on(sched, stdexec::just() | stdexec::then([=] { return x + x; })); }; // This will still block the calling thread, but it will now offload to the - // glz::thread_pool since the coroutine task is immediately scheduled. - result = glz::sync_wait(make_task_offload(10)); - expect(result == 20); - // std::cout << "Offload Result = " << result << "\n"; + // thread pool since the coroutine task is immediately scheduled. + result = stdexec::sync_wait(make_task_offload(10)); + expect(std::get<0>(result.value()) == 20); }; suite when_all = [] { // Create a thread pool to execute all the tasks in parallel. - glz::thread_pool tp{glz::thread_pool::options{.thread_count = 4}}; - // Create the task we want to invoke multiple times and execute in parallel on the thread pool. - auto twice = [&](uint64_t x) -> glz::task { - co_await tp.schedule(); // Schedule onto the thread pool. - co_return x + x; // Executed on the thread pool. + exec::static_thread_pool tp{4}; + auto scheduler = tp.get_scheduler(); + auto twice = [&](uint64_t x) { + return x + x; // Executed on the thread pool. }; - - // Make our tasks to execute, tasks can be passed in via a std::ranges::range type or var args. - std::vector> tasks{}; + + exec::async_scope scope; + std::mutex mtx{}; + std::vector results{}; for (std::size_t i = 0; i < 5; ++i) { - tasks.emplace_back(twice(i + 1)); + scope.spawn(stdexec::on(scheduler, stdexec::just() | stdexec::then([&, i] { + const auto value = twice(i + 1); + std::unique_lock lock{mtx}; + results.emplace_back(value); + }))); } // Synchronously wait on this thread for the thread pool to finish executing all the tasks in parallel. - auto results = glz::sync_wait(glz::when_all(std::move(tasks))); - expect(results[0].return_value() == 2); - expect(results[1].return_value() == 4); - expect(results[2].return_value() == 6); - expect(results[3].return_value() == 8); - expect(results[4].return_value() == 10); + [[maybe_unused]] auto ret = stdexec::sync_wait(scope.on_empty()); + std::ranges::sort(results); + expect(results[0] == 2); + expect(results[1] == 4); + expect(results[2] == 6); + expect(results[3] == 8); + expect(results[4] == 10); // Use var args instead of a container as input to glz::when_all. - auto square = [&](uint8_t x) -> glz::task { - co_await tp.schedule(); - co_return x* x; + auto square = [](uint64_t x) { + return [=] { return x * x; }; }; + + auto parallel = [](auto& scheduler, auto... fs) { + return stdexec::when_all(stdexec::on(scheduler, stdexec::just() | stdexec::then(fs))...); + }; + + /*auto chain(auto& scheduler, auto... fs) { + return stdexec::on(scheduler, stdexec::just() | (stdexec::then(fs) | ...)); + }*/ // Var args allows you to pass in tasks with different return types and returns // the result as a std::tuple. - auto tuple_results = glz::sync_wait(glz::when_all(square(2), twice(10))); + //auto tuple_results = stdexec::sync_wait(chain_workers(scheduler, square(2), square(10))).value(); + auto tuple_results = stdexec::sync_wait(parallel(scheduler, square(2), [&]{ return twice(10); })).value(); - auto first = std::get<0>(tuple_results).return_value(); - auto second = std::get<1>(tuple_results).return_value(); + auto first = std::get<0>(tuple_results); + auto second = std::get<1>(tuple_results); expect(first == 4); expect(second == 20); }; -suite event = [] { +/*suite event = [] { std::cout << "\nEvent test:\n"; glz::event e; @@ -371,7 +382,7 @@ suite ring_buffer_test = [] { // Wait for all the values to be produced and consumed through the ring buffer. glz::sync_wait(glz::when_all(std::move(tasks))); -}; +};*/ #endif //#define SERVER_CLIENT_TEST From d02956e5a59c586830f1f4efd4b1bb0414f15048 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 9 Jul 2024 16:09:37 -0500 Subject: [PATCH 296/309] Update coroutine_test.cpp --- tests/coroutine_test/coroutine_test.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 7daf6d30b4..67515c811b 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -126,12 +126,12 @@ suite when_all = [] { expect(second == 20); }; -/*suite event = [] { +suite event = [] { std::cout << "\nEvent test:\n"; glz::event e; // These tasks will wait until the given event has been set before advancing. - auto make_wait_task = [](const glz::event& e, uint64_t i) -> glz::task { + auto make_wait_task = [](const glz::event& e, uint64_t i) -> exec::task { std::cout << "task " << i << " is waiting on the event...\n"; co_await e; std::cout << "task " << i << " event triggered, now resuming.\n"; @@ -139,7 +139,7 @@ suite when_all = [] { }; // This task will trigger the event allowing all waiting tasks to proceed. - auto make_set_task = [](glz::event& e) -> glz::task { + auto make_set_task = [](glz::event& e) -> exec::task { std::cout << "set task is triggering the event\n"; e.set(); co_return; @@ -147,10 +147,10 @@ suite when_all = [] { // Given more than a single task to synchronously wait on, use when_all() to execute all the // tasks concurrently on this thread and then sync_wait() for them all to complete. - glz::sync_wait(glz::when_all(make_wait_task(e, 1), make_wait_task(e, 2), make_wait_task(e, 3), make_set_task(e))); + stdexec::sync_wait(stdexec::when_all(make_wait_task(e, 1), make_wait_task(e, 2), make_wait_task(e, 3), make_set_task(e))); }; -suite latch = [] { +/*suite latch = [] { std::cout << "\nLatch test:\n"; // Complete worker tasks faster on a thread pool, using the scheduler version so the worker // tasks can yield for a specific amount of time to mimic difficult work. The pool is only From 64a8d322bfd00a83258543e77170798247398cfe Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Tue, 9 Jul 2024 16:43:04 -0500 Subject: [PATCH 297/309] Update coroutine_test.cpp --- tests/coroutine_test/coroutine_test.cpp | 90 +++++++++++++++++++++---- 1 file changed, 76 insertions(+), 14 deletions(-) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 67515c811b..703fafb714 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -63,8 +63,13 @@ suite thread_pool = [] { exec::static_thread_pool pool(1); auto sched = pool.get_scheduler(); - auto make_task_offload = [&sched](uint64_t x) { + /*auto make_task_offload = [&sched](uint64_t x) { return stdexec::on(sched, stdexec::just() | stdexec::then([=] { return x + x; })); + };*/ + + auto make_task_offload = [&sched](uint64_t x) -> exec::task { + co_await sched.schedule(); + co_return x + x; }; // This will still block the calling thread, but it will now offload to the @@ -150,15 +155,70 @@ suite event = [] { stdexec::sync_wait(stdexec::when_all(make_wait_task(e, 1), make_wait_task(e, 2), make_wait_task(e, 3), make_set_task(e))); }; -/*suite latch = [] { +using namespace std::chrono_literals; + +struct Timer { + struct promise_type { + auto get_return_object() { + return Timer{ std::coroutine_handle::from_promise(*this) }; + } + auto initial_suspend() noexcept { + return std::suspend_always{}; + } + auto final_suspend() noexcept { + return std::suspend_always{}; + } + void return_void() {} + void unhandled_exception() { + std::terminate(); + } + std::chrono::milliseconds duration; + }; + + std::coroutine_handle handle; + + Timer(std::coroutine_handle h) : handle(h) {} + ~Timer() { + if (handle) handle.destroy(); + } + + bool await_ready() const noexcept { + return handle.done(); + } + void await_suspend(std::coroutine_handle<> awaiting) noexcept { + std::thread([=] { + std::this_thread::sleep_for(handle.promise().duration); + awaiting.resume(); + }).detach(); + } + void await_resume() noexcept {} +}; + +inline Timer sleep_for(std::chrono::milliseconds duration) { + struct awaiter { + std::chrono::milliseconds duration; + bool await_ready() const noexcept { return false; } + void await_suspend(std::coroutine_handle<> h) const { + std::thread([=] { + std::this_thread::sleep_for(duration); + h.resume(); + }).detach(); + } + void await_resume() const noexcept {} + }; + co_await awaiter{ duration }; +} + +suite latch = [] { std::cout << "\nLatch test:\n"; // Complete worker tasks faster on a thread pool, using the scheduler version so the worker // tasks can yield for a specific amount of time to mimic difficult work. The pool is only // setup with a single thread to showcase yield_for(). - glz::scheduler tp{glz::scheduler::options{.pool = glz::thread_pool::options{.thread_count = 1}}}; + exec::static_thread_pool tp{1}; + auto scheduler = tp.get_scheduler(); // This task will wait until the given latch setters have completed. - auto make_latch_task = [](glz::latch& l) -> glz::task { + auto make_latch_task = [](glz::latch& l) -> exec::task { // It seems like the dependent worker tasks could be created here, but in that case it would // be superior to simply do: `co_await coro::when_all(tasks);` // It is also important to note that the last dependent task will resume the waiting latch @@ -174,14 +234,14 @@ suite event = [] { // This task does 'work' and counts down on the latch when completed. The final child task to // complete will end up resuming the latch task when the latch's count reaches zero. - auto make_worker_task = [](glz::scheduler& tp, glz::latch& l, int64_t i) -> glz::task { + auto make_worker_task = [](auto& sched, glz::latch& l, int64_t i) -> exec::task { // Schedule the worker task onto the thread pool. - co_await tp.schedule(); + co_await sched.schedule(); std::cout << "worker task " << i << " is working...\n"; // Do some expensive calculations, yield to mimic work...! Its also important to never use // std::this_thread::sleep_for() within the context of coroutines, it will block the thread // and other tasks that are ready to execute will be blocked. - co_await tp.yield_for(std::chrono::milliseconds{i * 20}); + co_await sleep_for(std::chrono::milliseconds{i * 20}); std::cout << "worker task " << i << " is done, counting down on the latch\n"; l.count_down(); co_return; @@ -189,19 +249,21 @@ suite event = [] { const int64_t num_tasks{5}; glz::latch l{num_tasks}; - std::vector> tasks{}; // Make the latch task first so it correctly waits for all worker tasks to count down. - tasks.emplace_back(make_latch_task(l)); - for (int64_t i = 1; i <= num_tasks; ++i) { - tasks.emplace_back(make_worker_task(tp, l, i)); - } + auto work = [&](auto) -> exec::task { + co_await make_latch_task(l); + for (int64_t i = 1; i <= num_tasks; ++i) { + co_await make_worker_task(scheduler, l, i); + } + co_return; + }; // Wait for all tasks to complete. - glz::sync_wait(glz::when_all(std::move(tasks))); + //stdexec::sync_wait(work(5)); }; -suite mutex_test = [] { +/*suite mutex_test = [] { std::cout << "\nMutex test:\n"; glz::thread_pool tp{glz::thread_pool::options{.thread_count = 4}}; From 6d7f7bfc1c73ee75122ba85032396abcca74f1bb Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 10 Jul 2024 09:21:42 -0500 Subject: [PATCH 298/309] FetchContent_Declare working for stdexec --- CMakeLists.txt | 49 +++++++++----------------------------------- cmake/dev-mode.cmake | 2 +- 2 files changed, 11 insertions(+), 40 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e231acf21..38cb269756 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,54 +36,25 @@ endif() set_property(TARGET glaze_glaze PROPERTY EXPORT_NAME glaze) -#include(FetchContent) -# -#FetchContent_Declare( -# stdexec -# GIT_REPOSITORY https://github.com/NVIDIA/stdexec.git -# GIT_TAG main -# GIT_SHALLOW TRUE -#) -#FetchContent_MakeAvailable(stdexec) +include(FetchContent) -#include(cmake/CPM.cmake) -# -#CPMAddPackage( -# NAME stdexec -# GITHUB_REPOSITORY NVIDIA/stdexec -# GIT_TAG main -#) +FetchContent_Declare( + stdexec + GIT_REPOSITORY https://github.com/NVIDIA/stdexec.git + GIT_TAG main + GIT_SHALLOW TRUE +) -#if (EXISTS ${stdexec_SOURCE_DIR}/include) -#message("stdexec headers available") -#endif() +set(STDEXEC_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) -#find_package(Git REQUIRED) -#execute_process( -# COMMAND ${GIT_EXECUTABLE} clone --depth 1 --branch "main" --recursive "https://github.com/NVIDIA/stdexec.git" "stdexec-src" -# WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/_deps -#) -# -#set(STDEXEC_INCLUDE_DIR ${CMAKE_BINARY_DIR}/_deps/stdexec-src/include) -# -#target_compile_features(glaze_glaze INTERFACE cxx_std_20) -#target_include_directories( -# glaze_glaze ${warning_guard} -# INTERFACE -# "$" -# "$" -#) +FetchContent_MakeAvailable(stdexec) target_compile_features(glaze_glaze INTERFACE cxx_std_20) target_include_directories( glaze_glaze ${warning_guard} INTERFACE "$" - "$" + "$" ) -#if (TARGET stdexec::stdexec) -#message("stdexec::stdexec found") -#endif() -#target_link_libraries(${PROJECT_NAME} INTERFACE STDEXEC::stdexec) if(NOT CMAKE_SKIP_INSTALL_RULES) include(cmake/install-rules.cmake) diff --git a/cmake/dev-mode.cmake b/cmake/dev-mode.cmake index 169bf1df9f..33699c735d 100644 --- a/cmake/dev-mode.cmake +++ b/cmake/dev-mode.cmake @@ -8,7 +8,7 @@ endif() set_property(GLOBAL PROPERTY USE_FOLDERS YES) include(CTest) -if(BUILD_TESTING) +if(BUILD_TESTING OR glaze_DEVELOPER_MODE) add_subdirectory(tests) endif() From e6a936591a35b7943c603d6f9ecb326312e9af32 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Wed, 10 Jul 2024 10:52:16 -0500 Subject: [PATCH 299/309] Window's support updates. --- CMakeLists.txt | 2 +- include/glaze/coroutine.hpp | 1 - include/glaze/network/socket.hpp | 151 ++++++++++++------------ tests/CMakeLists.txt | 4 + tests/coroutine_test/coroutine_test.cpp | 3 +- 5 files changed, 83 insertions(+), 78 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38cb269756..641d7bab4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ if (MSVC) string(REGEX MATCH "\/cl(.exe)?$" matched_cl ${CMAKE_CXX_COMPILER}) if (matched_cl) # for a C++ standards compliant preprocessor, not needed for clang-cl - target_compile_options(glaze_glaze INTERFACE "/Zc:preprocessor" /permissive- /Zc:lambda) + target_compile_options(glaze_glaze INTERFACE "/Zc:preprocessor" /permissive- /Zc:lambda /Zc:__cplusplus) if(PROJECT_IS_TOP_LEVEL) target_compile_options(glaze_glaze INTERFACE diff --git a/include/glaze/coroutine.hpp b/include/glaze/coroutine.hpp index e29396ab24..c4eaccc125 100644 --- a/include/glaze/coroutine.hpp +++ b/include/glaze/coroutine.hpp @@ -5,7 +5,6 @@ #include "glaze/coroutine/awaitable.hpp" #include "glaze/coroutine/generator.hpp" -#include "glaze/coroutine/scheduler.hpp" #include "glaze/coroutine/latch.hpp" #include "glaze/coroutine/mutex.hpp" #include "glaze/coroutine/ring_buffer.hpp" diff --git a/include/glaze/network/socket.hpp b/include/glaze/network/socket.hpp index f8fc745235..619176a791 100644 --- a/include/glaze/network/socket.hpp +++ b/include/glaze/network/socket.hpp @@ -6,12 +6,12 @@ #pragma once +#include + #include "glaze/network/core.hpp" #include "glaze/network/ip.hpp" #include "glaze/network/socket_core.hpp" -#include - namespace glz { #ifdef _WIN32 @@ -44,10 +44,8 @@ namespace glz net::close_socket(socket_fd); } } - - bool valid() const { - return socket_fd != net::invalid_socket; - } + + bool valid() const { return socket_fd != net::invalid_socket; } ~socket() { close(); } @@ -58,18 +56,18 @@ namespace glz return result == 0; } }; - + inline void set_non_blocking(socket& sock) noexcept { #ifdef _WIN32 u_long mode = 1; - ioctlsocket(socket_fd, FIONBIO, &mode); + ioctlsocket(sock.socket_fd, FIONBIO, &mode); #else int flags = fcntl(sock.socket_fd, F_GETFL, 0); fcntl(sock.socket_fd, F_SETFL, flags | O_NONBLOCK); #endif } - + [[nodiscard]] inline std::error_code connect(socket& sock, const std::string& address, const int port) { // TODO: Support ipv6 @@ -92,28 +90,30 @@ namespace glz return {}; } - + [[nodiscard]] inline std::error_code bind_and_listen(socket& sock, const std::string& address, int port) { set_non_blocking(sock); if (sock.socket_fd == net::invalid_socket) { return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; } - - int sock_opt{1}; - if (setsockopt(sock.socket_fd, SOL_SOCKET, SO_REUSEADDR, &sock_opt, sizeof(sock_opt)) < 0) - { - std::cerr << "setsockopt SO_REUSEADDR: " << get_socket_error_message(errno) << '\n'; - return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; + +#ifdef _WIN32 + char sock_opt{1}; +#else + int sock_opt{1}; +#endif + if (setsockopt(sock.socket_fd, SOL_SOCKET, SO_REUSEADDR, &sock_opt, sizeof(sock_opt)) < 0) { + std::cerr << "setsockopt SO_REUSEADDR: " << get_socket_error_message(errno) << '\n'; + return {int(ip_error::socket_bind_failed), ip_error_category::instance()}; } - #ifdef SO_REUSEPORT - if (setsockopt(sock.socket_fd, SOL_SOCKET, SO_REUSEPORT, &sock_opt, sizeof(sock_opt)) < 0) - { - std::cerr << "setsockopt SO_REUSEPORT: " << get_socket_error_message(errno) << '\n'; - // You might want to handle this error differently, as it's not critical +#ifdef SO_REUSEPORT + if (setsockopt(sock.socket_fd, SOL_SOCKET, SO_REUSEPORT, &sock_opt, sizeof(sock_opt)) < 0) { + std::cerr << "setsockopt SO_REUSEPORT: " << get_socket_error_message(errno) << '\n'; + // You might want to handle this error differently, as it's not critical } - #endif +#endif sockaddr_in server_addr; server_addr.sin_family = AF_INET; @@ -134,7 +134,7 @@ namespace glz return {}; } - + [[nodiscard]] inline std::shared_ptr make_async_socket() { auto sock = std::make_shared(); @@ -142,27 +142,27 @@ namespace glz set_non_blocking(*sock); return sock; } - + [[nodiscard]] inline std::shared_ptr make_accept_socket(const std::string& address, int port) { auto sock = std::make_shared(); sock->socket_fd = ::socket(AF_INET, SOCK_STREAM, 0); - + if (auto ec = bind_and_listen(*sock, address, port)) { return {}; } - + return sock; } - + GLZ_ENUM(socket_event, bytes_read, wait, client_disconnected, receive_failed); - + struct socket_state { size_t bytes_read{}; socket_event event{}; }; - + [[nodiscard]] inline socket_state async_recv(socket& sckt, char* buffer, size_t size) { auto bytes = ::recv(sckt.socket_fd, buffer, net::ssize_t(size), 0); @@ -179,9 +179,10 @@ namespace glz } return {size_t(bytes), socket_event::bytes_read}; } - + template - [[nodiscard]] std::error_code blocking_header_receive(socket& sckt, Header& header, std::string& buffer, size_t timeout_ms) + [[nodiscard]] std::error_code blocking_header_receive(socket& sckt, Header& header, std::string& buffer, + size_t timeout_ms) { // first receive the header auto t0 = std::chrono::steady_clock::now(); @@ -192,28 +193,28 @@ namespace glz std::cout << std::chrono::duration_cast(t1 - t0).count() << '\n'; return {int(ip_error::receive_timeout), ip_error_category::instance()}; } - auto[bytes, event] = async_recv(sckt, reinterpret_cast(&header) + total_bytes, sizeof(Header) - total_bytes); + auto [bytes, event] = + async_recv(sckt, reinterpret_cast(&header) + total_bytes, sizeof(Header) - total_bytes); using enum socket_event; - switch (event) - { - case bytes_read: { - total_bytes += bytes; - break; - } - case wait: { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; - } - case client_disconnected: { - return {int(ip_error::client_disconnected), ip_error_category::instance()}; - } - case receive_failed: { - [[fallthrough]]; - } - default: { - buffer.clear(); - return {int(ip_error::receive_failed), ip_error_category::instance()}; - } + switch (event) { + case bytes_read: { + total_bytes += bytes; + break; + } + case wait: { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + case client_disconnected: { + return {int(ip_error::client_disconnected), ip_error_category::instance()}; + } + case receive_failed: { + [[fallthrough]]; + } + default: { + buffer.clear(); + return {int(ip_error::receive_failed), ip_error_category::instance()}; + } } } @@ -235,39 +236,39 @@ namespace glz std::cout << std::chrono::duration_cast(t1 - t0).count() << '\n'; return {int(ip_error::receive_timeout), ip_error_category::instance()}; } - auto[bytes, event] = async_recv(sckt, buffer.data() + total_bytes, buffer.size() - total_bytes); + auto [bytes, event] = async_recv(sckt, buffer.data() + total_bytes, buffer.size() - total_bytes); using enum socket_event; - switch (event) - { - case bytes_read: { - total_bytes += bytes; - break; - } - case wait: { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; - } - case client_disconnected: { - return {int(ip_error::client_disconnected), ip_error_category::instance()}; - } - case receive_failed: { - [[fallthrough]]; - } - default: { - buffer.clear(); - return {int(ip_error::receive_failed), ip_error_category::instance()}; - } + switch (event) { + case bytes_read: { + total_bytes += bytes; + break; + } + case wait: { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + case client_disconnected: { + return {int(ip_error::client_disconnected), ip_error_category::instance()}; + } + case receive_failed: { + [[fallthrough]]; + } + default: { + buffer.clear(); + return {int(ip_error::receive_failed), ip_error_category::instance()}; + } } } return {}; } - + [[nodiscard]] inline std::error_code blocking_send(socket& sckt, const std::string_view buffer) { const size_t size = buffer.size(); size_t total_bytes{}; while (total_bytes < size) { - auto bytes = ::send(sckt.socket_fd, buffer.data() + total_bytes, glz::net::ssize_t(buffer.size() - total_bytes), 0); + auto bytes = + ::send(sckt.socket_fd, buffer.data() + total_bytes, glz::net::ssize_t(buffer.size() - total_bytes), 0); if (bytes == -1) { if (GLZ_SOCKET_ERROR_CODE == e_would_block || GLZ_SOCKET_ERROR_CODE == EAGAIN) { std::this_thread::yield(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3bda4510f5..f2f229873e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,9 @@ include(FetchContent) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + set(BOOST_UT_ENABLE_RUN_AFTER_BUILD OFF CACHE INTERNAL "") set(BOOST_UT_DISABLE_MODULE ON CACHE INTERNAL "") diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 703fafb714..fa703086e7 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -5,7 +5,8 @@ #include "glaze/coroutine.hpp" -#include "glaze/network.hpp" +// #include "glaze/network.hpp" + #include "ut/ut.hpp" #include "stdexec/execution.hpp" From 33b7dfaf6c57b0f840d52a083aeb223f1c91d2d5 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 10 Jul 2024 14:38:23 -0500 Subject: [PATCH 300/309] fix error.hpp message --- include/glaze/core/error.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/glaze/core/error.hpp b/include/glaze/core/error.hpp index f991b15316..dae1540f59 100644 --- a/include/glaze/core/error.hpp +++ b/include/glaze/core/error.hpp @@ -21,8 +21,7 @@ namespace glz std::string message(int ec) const override { - static constexpr auto arr = detail::make_enum_to_string_array(); - return std::string{arr[ec]}; + return std::string{nameof(error_code(ec))}; } }; } From 35959dd659c156fdec0ce9ac7edb9e655b920021 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 10 Jul 2024 14:47:01 -0500 Subject: [PATCH 301/309] Update dev-mode.cmake --- cmake/dev-mode.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/dev-mode.cmake b/cmake/dev-mode.cmake index 33699c735d..169bf1df9f 100644 --- a/cmake/dev-mode.cmake +++ b/cmake/dev-mode.cmake @@ -8,7 +8,7 @@ endif() set_property(GLOBAL PROPERTY USE_FOLDERS YES) include(CTest) -if(BUILD_TESTING OR glaze_DEVELOPER_MODE) +if(BUILD_TESTING) add_subdirectory(tests) endif() From c2e3656df23215e32ac1d4ad3b1a2e578ceb9ed3 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 10 Jul 2024 15:35:44 -0500 Subject: [PATCH 302/309] Update coroutine_test.cpp --- tests/coroutine_test/coroutine_test.cpp | 82 +++++++++---------------- 1 file changed, 29 insertions(+), 53 deletions(-) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index fa703086e7..95a5606e5e 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -13,6 +13,9 @@ #include "exec/task.hpp" #include "exec/static_thread_pool.hpp" #include "exec/async_scope.hpp" +#include "exec/finally.hpp" +#include "exec/timed_thread_scheduler.hpp" +#include "exec/when_any.hpp" using namespace ut; @@ -158,57 +161,28 @@ suite event = [] { using namespace std::chrono_literals; -struct Timer { - struct promise_type { - auto get_return_object() { - return Timer{ std::coroutine_handle::from_promise(*this) }; - } - auto initial_suspend() noexcept { - return std::suspend_always{}; - } - auto final_suspend() noexcept { - return std::suspend_always{}; - } - void return_void() {} - void unhandled_exception() { - std::terminate(); - } - std::chrono::milliseconds duration; - }; - - std::coroutine_handle handle; - - Timer(std::coroutine_handle h) : handle(h) {} - ~Timer() { - if (handle) handle.destroy(); - } +/*struct timer { + std::chrono::milliseconds duration_; + + auto operator co_await() const noexcept { + struct awaiter { + std::chrono::milliseconds duration; - bool await_ready() const noexcept { - return handle.done(); - } - void await_suspend(std::coroutine_handle<> awaiting) noexcept { - std::thread([=] { - std::this_thread::sleep_for(handle.promise().duration); - awaiting.resume(); - }).detach(); - } - void await_resume() noexcept {} -}; + bool await_ready() const noexcept { return false; } -inline Timer sleep_for(std::chrono::milliseconds duration) { - struct awaiter { - std::chrono::milliseconds duration; - bool await_ready() const noexcept { return false; } - void await_suspend(std::coroutine_handle<> h) const { - std::thread([=] { - std::this_thread::sleep_for(duration); - h.resume(); - }).detach(); - } - void await_resume() const noexcept {} - }; - co_await awaiter{ duration }; -} + void await_suspend(std::coroutine_handle<> h) const { + std::thread([h, this]() { + std::this_thread::sleep_for(duration); + h.resume(); + }).detach(); + } + + void await_resume() const noexcept {} + }; + + return awaiter{duration_}; + } +};*/ suite latch = [] { std::cout << "\nLatch test:\n"; @@ -242,7 +216,10 @@ suite latch = [] { // Do some expensive calculations, yield to mimic work...! Its also important to never use // std::this_thread::sleep_for() within the context of coroutines, it will block the thread // and other tasks that are ready to execute will be blocked. - co_await sleep_for(std::chrono::milliseconds{i * 20}); + //co_await timer{std::chrono::milliseconds{i * 20}}(sched); + co_await [&]() -> exec::task { + co_return std::thread([i]{ std::this_thread::sleep_for(std::chrono::milliseconds(i * 20)); }).detach(); + }(); std::cout << "worker task " << i << " is done, counting down on the latch\n"; l.count_down(); co_return; @@ -252,8 +229,7 @@ suite latch = [] { glz::latch l{num_tasks}; // Make the latch task first so it correctly waits for all worker tasks to count down. - auto work = [&](auto) -> exec::task { - co_await make_latch_task(l); + auto work = [&]() -> exec::task { for (int64_t i = 1; i <= num_tasks; ++i) { co_await make_worker_task(scheduler, l, i); } @@ -261,7 +237,7 @@ suite latch = [] { }; // Wait for all tasks to complete. - //stdexec::sync_wait(work(5)); + stdexec::sync_wait(stdexec::when_all(make_latch_task(l), work())); }; /*suite mutex_test = [] { From 297e80be45007b1a75d5c6a3cd5ecdea4565c315 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 10 Jul 2024 15:46:20 -0500 Subject: [PATCH 303/309] mutex --- tests/coroutine_test/coroutine_test.cpp | 33 +++++++++++-------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 95a5606e5e..36743910b2 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -240,33 +240,28 @@ suite latch = [] { stdexec::sync_wait(stdexec::when_all(make_latch_task(l), work())); }; -/*suite mutex_test = [] { +suite mutex_test = [] { std::cout << "\nMutex test:\n"; - glz::thread_pool tp{glz::thread_pool::options{.thread_count = 4}}; + exec::static_thread_pool tp{4}; + auto scheduler = tp.get_scheduler(); std::vector output{}; - glz::mutex mutex; + std::mutex mtx{}; - auto make_critical_section_task = [&](uint64_t i) -> glz::task { - co_await tp.schedule(); - // To acquire a mutex lock co_await its lock() function. Upon acquiring the lock the - // lock() function returns a coro::scoped_lock that holds the mutex and automatically - // unlocks the mutex upon destruction. This behaves just like std::scoped_lock. - { - auto scoped_lock = co_await mutex.lock(); - output.emplace_back(i); - } // <-- scoped lock unlocks the mutex here. - co_return; + auto make_critical_section_task = [&](uint64_t i) { + std::unique_lock lock{mtx}; + output.emplace_back(i); }; const size_t num_tasks{100}; - std::vector> tasks{}; - tasks.reserve(num_tasks); - for (size_t i = 1; i <= num_tasks; ++i) { - tasks.emplace_back(make_critical_section_task(i)); + exec::async_scope scope; + for (std::size_t i = 1; i < num_tasks; ++i) { + scope.spawn(stdexec::on(scheduler, stdexec::just() | stdexec::then([&, i]() { + make_critical_section_task(i); + }))); } - glz::sync_wait(glz::when_all(std::move(tasks))); + stdexec::sync_wait(scope.on_empty()); // The output will be variable per run depending on how the tasks are picked up on the // thread pool workers. @@ -275,7 +270,7 @@ suite latch = [] { } }; -suite shared_mutex_test = [] { +/*suite shared_mutex_test = [] { std::cout << "\nShared Mutex test:\n"; // Shared mutexes require an excutor type to be able to wake up multiple shared waiters when // there is an exclusive lock holder releasing the lock. This example uses a single thread From c938b2a7f0849793c54889cf2b63058eeb76c462 Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Wed, 10 Jul 2024 15:50:07 -0500 Subject: [PATCH 304/309] Update coroutine_test.cpp --- tests/coroutine_test/coroutine_test.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 36743910b2..708f87ebcb 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -319,9 +319,9 @@ suite mutex_test = [] { } glz::sync_wait(glz::when_all(std::move(tasks))); -}; +};*/ -suite semaphore_test = [] { +/*suite semaphore_test = [] { std::cout << "\nSemaphore test:\n"; // Have more threads/tasks than the semaphore will allow for at any given point in time. glz::thread_pool tp{glz::thread_pool::options{.thread_count = 8}}; @@ -350,9 +350,9 @@ suite semaphore_test = [] { } glz::sync_wait(glz::when_all(std::move(tasks))); -}; +};*/ -suite ring_buffer_test = [] { +/*suite ring_buffer_test = [] { std::cout << "\nRing Buffer test:\n"; const size_t iterations = 100; From 04f34675c594db61a9bda449631caec639b1acd3 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Thu, 11 Jul 2024 14:39:54 -0500 Subject: [PATCH 305/309] Temporary fix to cmake generation error caused by catch repo --- CMakeLists.txt | 18 ++++-------------- cmake/stdexec.cmake | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 cmake/stdexec.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 641d7bab4f..7843ad72b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,9 @@ project( include(cmake/project-is-top-level.cmake) include(cmake/variables.cmake) +include(cmake/stdexec.cmake) + +fetch_stdexec() add_library(glaze_glaze INTERFACE) add_library(glaze::glaze ALIAS glaze_glaze) @@ -36,24 +39,11 @@ endif() set_property(TARGET glaze_glaze PROPERTY EXPORT_NAME glaze) -include(FetchContent) - -FetchContent_Declare( - stdexec - GIT_REPOSITORY https://github.com/NVIDIA/stdexec.git - GIT_TAG main - GIT_SHALLOW TRUE -) - -set(STDEXEC_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - -FetchContent_MakeAvailable(stdexec) - target_compile_features(glaze_glaze INTERFACE cxx_std_20) target_include_directories( glaze_glaze ${warning_guard} INTERFACE "$" - "$" + "$" ) if(NOT CMAKE_SKIP_INSTALL_RULES) diff --git a/cmake/stdexec.cmake b/cmake/stdexec.cmake new file mode 100644 index 0000000000..6e7c8fd5aa --- /dev/null +++ b/cmake/stdexec.cmake @@ -0,0 +1,38 @@ +function (fetch_stdexec) + set(branch_or_tag "main") + set(url "https://github.com/NVIDIA/stdexec.git") + set(target_folder "${CMAKE_BINARY_DIR}/_deps/stdexec-src") + + if (NOT EXISTS ${target_folder}) + execute_process( + COMMAND git clone --depth 1 --branch "${branch_or_tag}" --recursive "${url}" "${target_folder}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE exec_process_result + OUTPUT_VARIABLE exec_process_output + ) + if(NOT exec_process_result EQUAL "0") + message(FATAL_ERROR "Git clone failed: ${exec_process_output}") + else() + message(STATUS "Git clone succeeded: ${exec_process_output}") + endif() + endif() + + set(stdexec_SOURCE_DIR ${target_folder} CACHE INTERNAL "stdexec source folder" FORCE) + set(stdexec_INCLUDE_DIR ${target_folder}/include CACHE INTERNAL "stdexec include folder" FORCE) + + #[[ + include(FetchContent) + + FetchContent_Declare( + stdexec + GIT_REPOSITORY https://github.com/NVIDIA/stdexec.git + GIT_TAG main + GIT_SHALLOW TRUE + ) + + set(STDEXEC_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + + FetchContent_MakeAvailable(stdexec) + #]] + +endfunction() \ No newline at end of file From 7cc43b6905be0202f59eca91abf94fc3f8e9a809 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Thu, 11 Jul 2024 16:24:16 -0500 Subject: [PATCH 306/309] Updated latch test. --- tests/coroutine_test/coroutine_test.cpp | 144 ++++++++++++++++-------- 1 file changed, 94 insertions(+), 50 deletions(-) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 708f87ebcb..3cfd7f7185 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -7,15 +7,16 @@ // #include "glaze/network.hpp" -#include "ut/ut.hpp" +#include -#include "stdexec/execution.hpp" -#include "exec/task.hpp" -#include "exec/static_thread_pool.hpp" #include "exec/async_scope.hpp" #include "exec/finally.hpp" +#include "exec/static_thread_pool.hpp" +#include "exec/task.hpp" #include "exec/timed_thread_scheduler.hpp" #include "exec/when_any.hpp" +#include "stdexec/execution.hpp" +#include "ut/ut.hpp" using namespace ut; @@ -67,17 +68,10 @@ suite thread_pool = [] { exec::static_thread_pool pool(1); auto sched = pool.get_scheduler(); - /*auto make_task_offload = [&sched](uint64_t x) { + auto make_task_offload = [&sched](uint64_t x) { return stdexec::on(sched, stdexec::just() | stdexec::then([=] { return x + x; })); - };*/ - - auto make_task_offload = [&sched](uint64_t x) -> exec::task { - co_await sched.schedule(); - co_return x + x; }; - // This will still block the calling thread, but it will now offload to the - // thread pool since the coroutine task is immediately scheduled. result = stdexec::sync_wait(make_task_offload(10)); expect(std::get<0>(result.value()) == 20); }; @@ -89,16 +83,16 @@ suite when_all = [] { auto twice = [&](uint64_t x) { return x + x; // Executed on the thread pool. }; - + exec::async_scope scope; std::mutex mtx{}; std::vector results{}; for (std::size_t i = 0; i < 5; ++i) { scope.spawn(stdexec::on(scheduler, stdexec::just() | stdexec::then([&, i] { - const auto value = twice(i + 1); - std::unique_lock lock{mtx}; - results.emplace_back(value); - }))); + const auto value = twice(i + 1); + std::unique_lock lock{mtx}; + results.emplace_back(value); + }))); } // Synchronously wait on this thread for the thread pool to finish executing all the tasks in parallel. @@ -111,22 +105,20 @@ suite when_all = [] { expect(results[4] == 10); // Use var args instead of a container as input to glz::when_all. - auto square = [](uint64_t x) { - return [=] { return x * x; }; - }; - + auto square = [](uint64_t x) { return [=] { return x * x; }; }; + auto parallel = [](auto& scheduler, auto... fs) { return stdexec::when_all(stdexec::on(scheduler, stdexec::just() | stdexec::then(fs))...); }; - + /*auto chain(auto& scheduler, auto... fs) { return stdexec::on(scheduler, stdexec::just() | (stdexec::then(fs) | ...)); }*/ // Var args allows you to pass in tasks with different return types and returns // the result as a std::tuple. - //auto tuple_results = stdexec::sync_wait(chain_workers(scheduler, square(2), square(10))).value(); - auto tuple_results = stdexec::sync_wait(parallel(scheduler, square(2), [&]{ return twice(10); })).value(); + // auto tuple_results = stdexec::sync_wait(chain_workers(scheduler, square(2), square(10))).value(); + auto tuple_results = stdexec::sync_wait(parallel(scheduler, square(2), [&] { return twice(10); })).value(); auto first = std::get<0>(tuple_results); auto second = std::get<1>(tuple_results); @@ -156,14 +148,15 @@ suite event = [] { // Given more than a single task to synchronously wait on, use when_all() to execute all the // tasks concurrently on this thread and then sync_wait() for them all to complete. - stdexec::sync_wait(stdexec::when_all(make_wait_task(e, 1), make_wait_task(e, 2), make_wait_task(e, 3), make_set_task(e))); + stdexec::sync_wait( + stdexec::when_all(make_wait_task(e, 1), make_wait_task(e, 2), make_wait_task(e, 3), make_set_task(e))); }; using namespace std::chrono_literals; /*struct timer { std::chrono::milliseconds duration_; - + auto operator co_await() const noexcept { struct awaiter { std::chrono::milliseconds duration; @@ -184,6 +177,7 @@ using namespace std::chrono_literals; } };*/ +/* suite latch = [] { std::cout << "\nLatch test:\n"; // Complete worker tasks faster on a thread pool, using the scheduler version so the worker @@ -239,6 +233,50 @@ suite latch = [] { // Wait for all tasks to complete. stdexec::sync_wait(stdexec::when_all(make_latch_task(l), work())); }; +*/ + +suite latch = [] { + namespace ex = stdexec; + + std::cout << "\nLatch test:\n"; + + // Create a thread pool with a single thread + exec::static_thread_pool tp{1}; + auto scheduler = tp.get_scheduler(); + + // Create a latch with count 3 (for 3 worker tasks) + std::latch l(3); + + // Define the latch task + auto latch_task = ex::let_value(ex::schedule(scheduler), [&]() { + return ex::just() | ex::then([&]() { + std::cout << "latch task is now waiting on all children tasks...\n"; + l.wait(); + std::cout << "latch task dependency tasks completed, resuming.\n"; + }); + }); + + // Define the worker task + auto make_worker_task = [&](int64_t i) { + return ex::let_value(ex::schedule(scheduler), [i, &l]() { + return ex::just() | ex::then([i]() { + std::cout << "worker task " << i << " is working...\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(i * 20)); + std::cout << "worker task " << i << " is done, counting down on the latch\n"; + }) | + ex::then([&l]() { l.count_down(); }); + }); + }; + + // Create and schedule tasks + auto scheduled_latch_task = latch_task; + auto scheduled_worker_tasks = ex::when_all(make_worker_task(1), make_worker_task(2), make_worker_task(3)); + + // Run all tasks + ex::sync_wait(ex::when_all(scheduled_latch_task, scheduled_worker_tasks)); + + return 0; +}; suite mutex_test = [] { std::cout << "\nMutex test:\n"; @@ -256,9 +294,7 @@ suite mutex_test = [] { const size_t num_tasks{100}; exec::async_scope scope; for (std::size_t i = 1; i < num_tasks; ++i) { - scope.spawn(stdexec::on(scheduler, stdexec::just() | stdexec::then([&, i]() { - make_critical_section_task(i); - }))); + scope.spawn(stdexec::on(scheduler, stdexec::just() | stdexec::then([&, i]() { make_critical_section_task(i); }))); } stdexec::sync_wait(scope.on_empty()); @@ -419,7 +455,7 @@ suite mutex_test = [] { };*/ #endif -//#define SERVER_CLIENT_TEST +// #define SERVER_CLIENT_TEST #ifdef SERVER_CLIENT_TEST suite server_client_test = [] { @@ -461,7 +497,8 @@ suite server_client_test = [] { // Wait for an incoming connection and accept it. auto poll_status = co_await server.poll(); if (poll_status != glz::poll_status::event) { - std::cerr << "Incoming client connection failed!\n" << "Poll Status Detail: " << glz::nameof(poll_status) << '\n'; + std::cerr << "Incoming client connection failed!\n" + << "Poll Status Detail: " << glz::nameof(poll_status) << '\n'; co_return; // Handle error, see poll_status for detailed error states. } @@ -477,10 +514,12 @@ suite server_client_test = [] { poll_status = co_await client.poll(glz::poll_op::read); if (poll_status != glz::poll_status::event) { if (glz::poll_status::closed == glz::poll_status::event) { - std::cerr << "Error on: co_await client.poll(glz::poll_op::read): client Id, " << client.socket->socket_fd << ", the socket is closed.\n"; + std::cerr << "Error on: co_await client.poll(glz::poll_op::read): client Id, " << client.socket->socket_fd + << ", the socket is closed.\n"; } else { - std::cerr << "Error on: co_await client.poll(glz::poll_op::read): client Id, " << client.socket->socket_fd << ".\nDetails: " << glz::nameof(poll_status) << '\n'; + std::cerr << "Error on: co_await client.poll(glz::poll_op::read): client Id, " << client.socket->socket_fd + << ".\nDetails: " << glz::nameof(poll_status) << '\n'; } co_return; // Handle error. } @@ -491,7 +530,8 @@ suite server_client_test = [] { std::string request(256, '\0'); auto [ip_status, recv_bytes] = client.recv(request); if (ip_status != glz::ip_status::ok) { - std::cerr << "client::recv error:\n" << "Details: " << glz::nameof(poll_status) << '\n'; + std::cerr << "client::recv error:\n" + << "Details: " << glz::nameof(poll_status) << '\n'; co_return; // Handle error, see net::ip_status for detailed error states. } @@ -501,7 +541,8 @@ suite server_client_test = [] { // Make sure the client socket can be written to. poll_status = co_await client.poll(glz::poll_op::write); if (poll_status != glz::poll_status::event) { - std::cerr << "Error on: co_await client.poll(glz::poll_op::write): client Id" << client.socket->socket_fd << ".\nDetails: " << glz::nameof(poll_status) << '\n'; + std::cerr << "Error on: co_await client.poll(glz::poll_op::write): client Id" << client.socket->socket_fd + << ".\nDetails: " << glz::nameof(poll_status) << '\n'; co_return; // Handle error. } @@ -574,30 +615,33 @@ suite server_client_test = [] { #endif template -exec::task async_answer(S1 s1, S2 s2) { - // Senders are implicitly awaitable (in this coroutine type): - co_await static_cast(s2); - co_return co_await static_cast(s1); +exec::task async_answer(S1 s1, S2 s2) +{ + // Senders are implicitly awaitable (in this coroutine type): + co_await static_cast(s2); + co_return co_await static_cast(s1); } template -exec::task> async_answer2(S1 s1, S2 s2) { - co_return co_await stdexec::stopped_as_optional(async_answer(s1, s2)); +exec::task> async_answer2(S1 s1, S2 s2) +{ + co_return co_await stdexec::stopped_as_optional(async_answer(s1, s2)); } // tasks have an associated stop token -exec::task> async_stop_token() { - co_return co_await stdexec::stopped_as_optional(stdexec::get_stop_token()); +exec::task> async_stop_token() +{ + co_return co_await stdexec::stopped_as_optional(stdexec::get_stop_token()); } -suite stdexec_coroutine_test = [] -{ +suite stdexec_coroutine_test = [] { try { - // Awaitables are implicitly senders: - auto [i] = stdexec::sync_wait(async_answer2(stdexec::just(42), stdexec::just())).value(); - std::cout << "The answer is " << i.value() << '\n'; - } catch (std::exception& e) { - std::cout << e.what() << '\n'; + // Awaitables are implicitly senders: + auto [i] = stdexec::sync_wait(async_answer2(stdexec::just(42), stdexec::just())).value(); + std::cout << "The answer is " << i.value() << '\n'; + } + catch (std::exception& e) { + std::cout << e.what() << '\n'; } }; From acfb363f15a552db89394474f1103f7fb7a556e8 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Thu, 11 Jul 2024 16:45:14 -0500 Subject: [PATCH 307/309] Updated latch test. --- tests/coroutine_test/coroutine_test.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 3cfd7f7185..54922982e2 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -270,10 +270,22 @@ suite latch = [] { // Create and schedule tasks auto scheduled_latch_task = latch_task; + + /* TODO: + std::vector worker_tasks; + for (int i = 1; i <= 3; ++i) { + worker_tasks.push_back(make_worker_task(i)); + } + */ + auto scheduled_worker_tasks = ex::when_all(make_worker_task(1), make_worker_task(2), make_worker_task(3)); - // Run all tasks + auto start = std::chrono::high_resolution_clock::now(); ex::sync_wait(ex::when_all(scheduled_latch_task, scheduled_worker_tasks)); + auto end = std::chrono::high_resolution_clock::now(); + + std::chrono::duration diff = end - start; + std::cout << "Total execution time: " << diff.count() << " seconds\n"; return 0; }; From 2b7fbeac9b3e1121c1a119e376e7cd3c7a431300 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Fri, 12 Jul 2024 12:00:10 -0500 Subject: [PATCH 308/309] Updated tests. I am observing a random were one of the tasks remains locked. --- tests/coroutine_test/coroutine_test.cpp | 59 +++++++++++++++++++++---- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 54922982e2..5c8b53488a 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -1,14 +1,10 @@ -// Glaze Library -// For the license information refer to glaze.hpp - -#define UT_RUN_TIME_ONLY +#include +#include +#include +#include #include "glaze/coroutine.hpp" -// #include "glaze/network.hpp" - -#include - #include "exec/async_scope.hpp" #include "exec/finally.hpp" #include "exec/static_thread_pool.hpp" @@ -626,6 +622,7 @@ suite server_client_test = [] { }; #endif +/*Old with co-routines template exec::task async_answer(S1 s1, S2 s2) { @@ -656,6 +653,52 @@ suite stdexec_coroutine_test = [] { std::cout << e.what() << '\n'; } }; +*/ + +template +auto async_answer(S1&& s1, S2&& s2) +{ + return stdexec::let_value(std::forward(s2), [s1 = std::forward(s1)]() mutable { return std::move(s1); }); +} + +template +auto async_answer2(S1&& s1, S2&& s2) +{ + return stdexec::stopped_as_optional(async_answer(std::forward(s1), std::forward(s2))); +} + +inline auto async_stop_token(exec::async_scope& scope) { return stdexec::just(scope.get_stop_token()); } + +suite stdexec_sender_composition_test = [] { + exec::static_thread_pool pool(4); + auto scheduler = pool.get_scheduler(); + + auto s1 = stdexec::just(42); + auto s2 = stdexec::just(); + + auto result = stdexec::sync_wait(stdexec::on(scheduler, async_answer2(std::move(s1), std::move(s2)))); + + if (result) { + if (auto& value_opt = std::get<0>(*result)) { + std::cout << "Result: " << *value_opt << std::endl; + } + else { + std::cout << "Operation was stopped" << std::endl; + } + } + else { + std::cout << "Operation failed" << std::endl; + } + + exec::async_scope scope; + auto stop_token_result = stdexec::sync_wait(stdexec::on(scheduler, async_stop_token(scope))); + if (stop_token_result) { + std::cout << "Stop token obtained" << std::endl; + } + else { + std::cout << "Stop token operation failed" << std::endl; + } +}; int main() { From 99145437eea7a90704a3e9e9f40e628b4e42fb84 Mon Sep 17 00:00:00 2001 From: "bill.berry" Date: Fri, 12 Jul 2024 14:01:47 -0500 Subject: [PATCH 309/309] Corrected latch test race condition. --- tests/coroutine_test/coroutine_test.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/coroutine_test/coroutine_test.cpp b/tests/coroutine_test/coroutine_test.cpp index 5c8b53488a..1014a83ed3 100644 --- a/tests/coroutine_test/coroutine_test.cpp +++ b/tests/coroutine_test/coroutine_test.cpp @@ -1,10 +1,10 @@ +#include "glaze/coroutine.hpp" + #include #include #include #include -#include "glaze/coroutine.hpp" - #include "exec/async_scope.hpp" #include "exec/finally.hpp" #include "exec/static_thread_pool.hpp" @@ -243,10 +243,13 @@ suite latch = [] { // Create a latch with count 3 (for 3 worker tasks) std::latch l(3); + std::atomic wait_start{false}; + // Define the latch task auto latch_task = ex::let_value(ex::schedule(scheduler), [&]() { return ex::just() | ex::then([&]() { std::cout << "latch task is now waiting on all children tasks...\n"; + wait_start = true; l.wait(); std::cout << "latch task dependency tasks completed, resuming.\n"; }); @@ -254,6 +257,7 @@ suite latch = [] { // Define the worker task auto make_worker_task = [&](int64_t i) { + while (wait_start) std::this_thread::sleep_for(std::chrono::milliseconds(10)); return ex::let_value(ex::schedule(scheduler), [i, &l]() { return ex::just() | ex::then([i]() { std::cout << "worker task " << i << " is working...\n"; @@ -669,7 +673,7 @@ auto async_answer2(S1&& s1, S2&& s2) inline auto async_stop_token(exec::async_scope& scope) { return stdexec::just(scope.get_stop_token()); } -suite stdexec_sender_composition_test = [] { +suite stdexec_sender_composition_test = [] { exec::static_thread_pool pool(4); auto scheduler = pool.get_scheduler();